Skip to content

Commit 1de7d13

Browse files
author
CodeWhale Bot
committed
fix: finish plugin consent and configured MCP reconnect
Persist explicit plugin suggestion dismissals and explain matched triggers. Use keyboard/mouse confirmation controls with the existing content-bound trust and automation-delete tokens. Reconnect an authenticated configured MCP server by exact name without replacing credentials or restarting siblings. Preserve native Windows paths during deny-policy expansion. Repair changed composer fixtures, isolate the retry-policy mock from loopback timing, and allow the lifecycle mock to settle under loaded CI. Sync 0.9.13 release notes. Validation on staged tree 596772e: - npm test: 521 passed, 0 failed (66 wrapper + 9 SDK + 446 web). - npm run check:web: passed; cargo fmt --all --check: passed. - Prior full Rust run: 15,398 passed, 2 timing failures, 21 skipped. Those two fixture repairs are included here; the full rerun and hosted CI are pending. MCP reconnect and all confirmation acceptance tests passed in the prior run. Four unchanged lifecycle repetitions passed separately. No release or final installed-artifact qualification is claimed.
1 parent 66f022f commit 1de7d13

43 files changed

Lines changed: 1129 additions & 300 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
## [0.9.13] - 2026-09-11
10+
## [0.9.13] - 2026-09-12
1111

1212
Codewhale v0.9.13 addresses integrity issues in 0.9.12:
1313
multiline paste is one paste again, truncated tool arguments can no longer execute, strict
@@ -26,9 +26,28 @@ reconnect.
2626
cannot resurrect the broken binding (#6102).
2727
- Permission checks distinguish literal heredoc data from executable commands,
2828
including substitutions and shell stdin (#6098).
29-
- Compaction durably saves original history and the model-written handoff before
30-
replacing context; pressure metadata shows estimated tokens and the actual
31-
configured trigger (#5620). Session artifact publication uses confined handles.
29+
- Automatic compaction runs quietly from live context pressure, preserving the
30+
current task and recent tool exchanges while keeping the system/tool prefix
31+
stable. Original history and the handoff are saved before context replacement;
32+
failed or canceled compaction retains the conversation (#5620, #6047).
33+
- Custom and gateway providers can override context limits for each exact model,
34+
so switching models also switches the meter and compaction budget (#6108).
35+
- Plugin suggestions explain their matching term and remember explicit dismissals
36+
across restarts. Generic words and repository-host domains no longer trigger
37+
unrelated installation prompts (#6031).
38+
- Plugin trust and automation deletion have keyboard and mouse confirmation
39+
controls bound to the exact reviewed content; users can still copy the command
40+
and changed content requires a fresh review (#6039).
41+
- The model-facing MCP start tool can reconnect an existing configured name after
42+
login without changing its credential key or restarting healthy siblings (#6030).
43+
- Missed automation occurrences coalesce without overlapping a running job;
44+
restart reconciles durable receipts without replaying accepted work. Damaged
45+
neighboring records are isolated while preserving their original bytes.
46+
- The bundled first-party marketplace lists the actual plugin bundles and uses
47+
the existing install, review, trust and update paths. A read-only connection
48+
check verifies catalog and skill mirrors on changes and weekly.
49+
- Windows deny checks preserve native path separators while retaining the
50+
conservative POSIX scan for shell wrappers and substitutions.
3251
- The config example agrees with the telemetry disclosure: usage analytics are
3352
optional and enabled by default; local diagnostics do not require telemetry (#6011).
3453

crates/execpolicy/src/shell_expand.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,32 @@ const SHELL_NAMES: &[&str] = &[
5656
///
5757
/// Results contain word-split commands, substitutions and wrapper payloads.
5858
/// Literal data, including quoted heredoc bodies, is not treated as code.
59+
/// Windows scans retain native path separators as well as POSIX candidates.
5960
pub fn expanded_commands(command: &str) -> Vec<String> {
61+
expanded_commands_for_platform(command, cfg!(windows))
62+
}
63+
64+
fn expanded_commands_for_platform(command: &str, windows: bool) -> Vec<String> {
6065
let mut expander = Expander {
6166
out: Vec::new(),
6267
seen: HashSet::new(),
68+
literal_backslashes: windows,
6369
};
70+
// Native Windows shells preserve path separators. Also retain the POSIX
71+
// interpretation for Bash/WSL commands. Both passes use the same bounded,
72+
// heredoc-aware parser and only contribute deny targets, never grants.
6473
expander.expand(command, 0);
74+
if windows {
75+
expander.literal_backslashes = false;
76+
expander.expand(command, 0);
77+
}
6578
expander.out
6679
}
6780

6881
struct Expander {
6982
out: Vec<String>,
7083
seen: HashSet<String>,
84+
literal_backslashes: bool,
7185
}
7286

7387
impl Expander {
@@ -126,6 +140,11 @@ impl Expander {
126140
// A backslash outside quotes escapes exactly one character,
127141
// including an operator: `echo a\;b` is one word, not two
128142
// commands. A backslash-newline is a line continuation.
143+
'\\' if self.literal_backslashes => {
144+
word.push('\\');
145+
started = true;
146+
i += 1;
147+
}
129148
'\\' => {
130149
if i + 1 < n {
131150
if chars[i + 1] != '\n' {
@@ -156,6 +175,10 @@ impl Expander {
156175
i += 1;
157176
while i < n && chars[i] != '"' {
158177
match chars[i] {
178+
'\\' if self.literal_backslashes => {
179+
word.push('\\');
180+
i += 1;
181+
}
159182
'\\' if i + 1 < n => {
160183
word.push(chars[i + 1]);
161184
i += 2;
@@ -705,7 +728,46 @@ mod tests {
705728
use super::*;
706729

707730
fn expand(command: &str) -> Vec<String> {
708-
expanded_commands(command)
731+
// Exercise the POSIX grammar consistently on every test host.
732+
expanded_commands_for_platform(command, false)
733+
}
734+
735+
#[test]
736+
fn windows_scan_retains_native_paths_and_posix_deny_candidates() {
737+
for (command, expected) in [
738+
(
739+
r"C:\Windows\System32\cat.exe ~/.ssh/id_rsa",
740+
r"C:\Windows\System32\cat.exe ~/.ssh/id_rsa",
741+
),
742+
(r"del /f c:\users\x\file", r"del /f c:\users\x\file"),
743+
(
744+
r"echo safe & xcopy /e /y c:\src d:\dst",
745+
r"xcopy /e /y c:\src d:\dst",
746+
),
747+
(
748+
r#""C:\Program Files\cat.exe" "c:\path with spaces\file""#,
749+
r"C:\Program Files\cat.exe c:\path with spaces\file",
750+
),
751+
(r"del relative\file", r"del relative\file"),
752+
(
753+
r"\\server\share\cat.exe file",
754+
r"\\server\share\cat.exe file",
755+
),
756+
(r"bash -c 'rm -rf \/'", "rm -rf /"),
757+
] {
758+
let targets = expanded_commands_for_platform(command, true);
759+
assert!(
760+
targets.iter().any(|target| target == expected),
761+
"missing {expected:?} from {targets:?}"
762+
);
763+
assert!(targets.len() <= MAX_COMMANDS);
764+
}
765+
let targets =
766+
expanded_commands_for_platform("cat <<'EOF'\ndel c:\\users\\x\\file\nEOF", true);
767+
assert!(
768+
!targets.iter().any(|target| target.starts_with("del ")),
769+
"literal heredoc data must stay inert: {targets:?}"
770+
);
709771
}
710772

711773
fn contains(command: &str, expected: &str) -> bool {

crates/localization/locales/ca.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@
484484
"PluginCtaInstallPrompt": "Instal·lar el connector {name}?",
485485
"PluginCtaReview": "Revisa",
486486
"PluginCtaDismiss": "Descarta",
487+
"PluginCtaDismissSaveFailed": "Ocult durant aquesta sessió; no s'ha pogut desar la preferència del connector.",
488+
"PluginSuggestionReason": "Coincideix amb «{trigger}»",
487489
"CmdPluginBundleDetail": "{name}\n========================================\nID: {id}\nVersió: {version}\nFont: {origin} ({scope})\nEstat: {state}\nConfiança: {trust}\nComponents: {inventory}\nPermisos sol·licitats: {permissions}\nServidors MCP: {mcp}\nNo compatible/inactiu: {unsupported}\nHash de contingut: {content_hash}\nHash de capacitats: {capability_hash}\nRuta: {path}",
488490
"CmdPluginBundleDiagnosticsHeader": "Diagnòstics de plugins ({count}):",
489491
"CmdPluginBundleMutationSuccess": "Paquet de plugins '{name}': {action}.",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "S'ha bloquejat '{tool}': aquesta sessió no permet eines que requereixen aprovació.",
22122214
"NotificationWebApproved": "Aprovat al web.",
22132215
"NotificationWebDenied": "Denegat al web.",
2214-
"NotificationInputSubmitFailed": "No s'ha pogut enviar la resposta: {error}. Torna-ho a provar."
2216+
"NotificationInputSubmitFailed": "No s'ha pogut enviar la resposta: {error}. Torna-ho a provar.",
2217+
"PagerActionConfirm": "Confirma"
22152218
}

crates/localization/locales/de.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@
484484
"PluginCtaInstallPrompt": "{name}-Plugin installieren?",
485485
"PluginCtaReview": "Prüfen",
486486
"PluginCtaDismiss": "Verwerfen",
487+
"PluginCtaDismissSaveFailed": "Für diese Sitzung ausgeblendet; die Plugin-Einstellung konnte nicht gespeichert werden.",
488+
"PluginSuggestionReason": "Treffer für „{trigger}“",
487489
"CmdPluginBundleDetail": "{name}\n========================================\nID: {id}\nVersion: {version}\nQuelle: {origin} ({scope})\nStatus: {state}\nVertrauen: {trust}\nKomponenten: {inventory}\nAngeforderte Berechtigungen: {permissions}\nMCP-Server: {mcp}\nNicht unterstützt/inaktiv: {unsupported}\nInhalts-Hash: {content_hash}\nFähigkeits-Hash: {capability_hash}\nPfad: {path}",
488490
"CmdPluginBundleDiagnosticsHeader": "Plugin-Diagnose ({count}):",
489491
"CmdPluginBundleMutationSuccess": "Plugin-Bundle '{name}': {action}.",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "'{tool}' wurde blockiert: Diese Sitzung erlaubt keine Tools, die eine Genehmigung erfordern.",
22122214
"NotificationWebApproved": "Im Web genehmigt.",
22132215
"NotificationWebDenied": "Im Web abgelehnt.",
2214-
"NotificationInputSubmitFailed": "Deine Antwort konnte nicht gesendet werden: {error}. Versuche es erneut."
2216+
"NotificationInputSubmitFailed": "Deine Antwort konnte nicht gesendet werden: {error}. Versuche es erneut.",
2217+
"PagerActionConfirm": "Bestätigen"
22152218
}

crates/localization/locales/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@
487487
"PluginCtaInstallPrompt": "Install {name} plugin?",
488488
"PluginCtaReview": "Review",
489489
"PluginCtaDismiss": "Dismiss",
490+
"PluginCtaDismissSaveFailed": "Hidden for this session; could not save the plugin preference.",
491+
"PluginSuggestionReason": "Matched “{trigger}”",
490492
"CmdPluginBundleDetail": "{name}\n========================================\nID: {id}\nVersion: {version}\nSource: {origin} ({scope})\nState: {state}\nTrust: {trust}\nComponents: {inventory}\nRequested permissions: {permissions}\nMCP servers: {mcp}\nUnsupported/inactive: {unsupported}\nContent hash: {content_hash}\nCapability hash: {capability_hash}\nPath: {path}",
491493
"CmdPluginBundleDiagnosticsHeader": "Plugin diagnostics ({count}):",
492494
"CmdPluginBundleMutationSuccess": "Plugin bundle '{name}': {action}.",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "Blocked '{tool}': this session does not allow tools that require approval.",
22122214
"NotificationWebApproved": "Approved on the web.",
22132215
"NotificationWebDenied": "Denied on the web.",
2214-
"NotificationInputSubmitFailed": "Could not submit your answer: {error}. Try again."
2216+
"NotificationInputSubmitFailed": "Could not submit your answer: {error}. Try again.",
2217+
"PagerActionConfirm": "Confirm"
22152218
}

crates/localization/locales/es-419.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@
487487
"PluginCtaInstallPrompt": "¿Instalar el plugin {name}?",
488488
"PluginCtaReview": "Revisar",
489489
"PluginCtaDismiss": "Descartar",
490+
"PluginCtaDismissSaveFailed": "Oculto durante esta sesión; no se pudo guardar la preferencia del complemento.",
491+
"PluginSuggestionReason": "Coincide con «{trigger}»",
490492
"CmdPluginBundleDetail": "{name}\n========================================\nID: {id}\nVersión: {version}\nFuente: {origin} ({scope})\nEstado: {state}\nConfianza: {trust}\nComponentes: {inventory}\nPermisos solicitados: {permissions}\nServidores MCP: {mcp}\nNo compatible/inactivo: {unsupported}\nHash de contenido: {content_hash}\nHash de capacidades: {capability_hash}\nRuta: {path}",
491493
"CmdPluginBundleDiagnosticsHeader": "Diagnósticos de plugins ({count}):",
492494
"CmdPluginBundleMutationSuccess": "Paquete de plugin '{name}': {action}.",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "Se bloqueó '{tool}': esta sesión no permite herramientas que requieren aprobación.",
22122214
"NotificationWebApproved": "Aprobado en la web.",
22132215
"NotificationWebDenied": "Rechazado en la web.",
2214-
"NotificationInputSubmitFailed": "No se pudo enviar tu respuesta: {error}. Inténtalo de nuevo."
2216+
"NotificationInputSubmitFailed": "No se pudo enviar tu respuesta: {error}. Inténtalo de nuevo.",
2217+
"PagerActionConfirm": "Confirmar"
22152218
}

crates/localization/locales/fr.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@
484484
"PluginCtaInstallPrompt": "Installer le plugin {name} ?",
485485
"PluginCtaReview": "Examiner",
486486
"PluginCtaDismiss": "Ignorer",
487+
"PluginCtaDismissSaveFailed": "Masqué pour cette session ; impossible d’enregistrer la préférence du plugin.",
488+
"PluginSuggestionReason": "Correspond à « {trigger} »",
487489
"CmdPluginBundleDetail": "{name}\n========================================\nID : {id}\nVersion : {version}\nSource : {origin} ({scope})\nÉtat : {state}\nConfiance : {trust}\nComposants : {inventory}\nPermissions demandées : {permissions}\nServeurs MCP : {mcp}\nNon pris en charge/inactif : {unsupported}\nHash du contenu : {content_hash}\nHash des capacités : {capability_hash}\nChemin : {path}",
488490
"CmdPluginBundleDiagnosticsHeader": "Diagnostics des plugins ({count}) :",
489491
"CmdPluginBundleMutationSuccess": "Bundle de plugins '{name}' : {action}.",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "« {tool} » a été bloqué : cette session n'autorise pas les outils qui nécessitent une approbation.",
22122214
"NotificationWebApproved": "Approuvé sur le Web.",
22132215
"NotificationWebDenied": "Refusé sur le Web.",
2214-
"NotificationInputSubmitFailed": "Impossible d'envoyer votre réponse : {error}. Réessayez."
2216+
"NotificationInputSubmitFailed": "Impossible d'envoyer votre réponse : {error}. Réessayez.",
2217+
"PagerActionConfirm": "Confirmer"
22152218
}

crates/localization/locales/hi.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@
484484
"PluginCtaInstallPrompt": "{name} प्लगइन इंस्टॉल करें?",
485485
"PluginCtaReview": "समीक्षा",
486486
"PluginCtaDismiss": "बंद करें",
487+
"PluginCtaDismissSaveFailed": "इस सत्र के लिए छिपाया गया; प्लगइन की प्राथमिकता सहेजी नहीं जा सकी।",
488+
"PluginSuggestionReason": "“{trigger}” से मेल खाता है",
487489
"CmdPluginBundleDetail": "{name}\n========================================\nID: {id}\nसंस्करण: {version}\nस्रोत: {origin} ({scope})\nस्थिति: {state}\nट्रस्ट: {trust}\nघटक: {inventory}\nअनुरोधित अनुमतियाँ: {permissions}\nMCP सर्वर: {mcp}\nअसमर्थित/निष्क्रिय: {unsupported}\nकंटेंट हैश: {content_hash}\nक्षमता हैश: {capability_hash}\nपथ: {path}",
488490
"CmdPluginBundleDiagnosticsHeader": "प्लगिन डायग्नोस्टिक्स ({count}):",
489491
"CmdPluginBundleMutationSuccess": "प्लगिन बंडल '{name}': {action}।",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "'{tool}' को ब्लॉक किया गया: इस सत्र में मंज़ूरी की ज़रूरत वाले टूल की अनुमति नहीं है।",
22122214
"NotificationWebApproved": "वेब पर मंज़ूरी दी गई।",
22132215
"NotificationWebDenied": "वेब पर अस्वीकार किया गया।",
2214-
"NotificationInputSubmitFailed": "आपका जवाब भेजा नहीं जा सका: {error}। फिर से कोशिश करें।"
2216+
"NotificationInputSubmitFailed": "आपका जवाब भेजा नहीं जा सका: {error}। फिर से कोशिश करें।",
2217+
"PagerActionConfirm": "पुष्टि करें"
22152218
}

crates/localization/locales/id.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@
484484
"PluginCtaInstallPrompt": "Pasang plugin {name}?",
485485
"PluginCtaReview": "Tinjau",
486486
"PluginCtaDismiss": "Tutup",
487+
"PluginCtaDismissSaveFailed": "Disembunyikan untuk sesi ini; preferensi plugin tidak dapat disimpan.",
488+
"PluginSuggestionReason": "Cocok dengan “{trigger}”",
487489
"CmdPluginBundleDetail": "{name}\n========================================\nID: {id}\nVersi: {version}\nSumber: {origin} ({scope})\nStatus: {state}\nKepercayaan: {trust}\nKomponen: {inventory}\nIzin yang diminta: {permissions}\nServer MCP: {mcp}\nTidak didukung/nonaktif: {unsupported}\nHash konten: {content_hash}\nHash kapabilitas: {capability_hash}\nJalur: {path}",
488490
"CmdPluginBundleDiagnosticsHeader": "Diagnostik plugin ({count}):",
489491
"CmdPluginBundleMutationSuccess": "Bundel plugin '{name}': {action}.",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "'{tool}' diblokir: sesi ini tidak mengizinkan alat yang memerlukan persetujuan.",
22122214
"NotificationWebApproved": "Disetujui di web.",
22132215
"NotificationWebDenied": "Ditolak di web.",
2214-
"NotificationInputSubmitFailed": "Jawaban Anda tidak dapat dikirim: {error}. Coba lagi."
2216+
"NotificationInputSubmitFailed": "Jawaban Anda tidak dapat dikirim: {error}. Coba lagi.",
2217+
"PagerActionConfirm": "Konfirmasi"
22152218
}

crates/localization/locales/ja.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@
487487
"PluginCtaInstallPrompt": "{name} プラグインをインストールしますか?",
488488
"PluginCtaReview": "確認",
489489
"PluginCtaDismiss": "閉じる",
490+
"PluginCtaDismissSaveFailed": "このセッションでは非表示にしました。プラグインの設定を保存できませんでした。",
491+
"PluginSuggestionReason": "「{trigger}」に一致",
490492
"CmdPluginBundleDetail": "{name}\n========================================\nID: {id}\nバージョン: {version}\n出所: {origin} ({scope})\n状態: {state}\n信頼: {trust}\nコンポーネント: {inventory}\n要求権限: {permissions}\nMCP サーバー: {mcp}\n未対応/無効: {unsupported}\nコンテンツハッシュ: {content_hash}\n権限ハッシュ: {capability_hash}\nパス: {path}",
491493
"CmdPluginBundleDiagnosticsHeader": "プラグイン診断 ({count}):",
492494
"CmdPluginBundleMutationSuccess": "プラグインバンドル '{name}': {action}。",
@@ -2211,5 +2213,6 @@
22112213
"ApprovalNeverPostureBlocked": "「{tool}」をブロックしました。このセッションでは承認が必要なツールは許可されていません。",
22122214
"NotificationWebApproved": "ウェブで承認されました。",
22132215
"NotificationWebDenied": "ウェブで拒否されました。",
2214-
"NotificationInputSubmitFailed": "回答を送信できませんでした:{error}。もう一度お試しください。"
2216+
"NotificationInputSubmitFailed": "回答を送信できませんでした:{error}。もう一度お試しください。",
2217+
"PagerActionConfirm": "確認"
22152218
}

0 commit comments

Comments
 (0)