Skip to content

Release v6.3.5#88

Merged
code-crusher merged 11 commits into
mainfrom
release/v6.3.5
Jun 8, 2026
Merged

Release v6.3.5#88
code-crusher merged 11 commits into
mainfrom
release/v6.3.5

Conversation

@code-crusher

Copy link
Copy Markdown
Member

What's Changed

Features

  • Command Approval: Configurable approval mode (ask/approve-for-me/full-access) with UI dropdown in chat textarea
  • Speech-to-Text: Microphone capture for voice input via extension host
  • Terminal Persistence: Working directory now persists across execa commands
  • Model Config: Removed pro-high model tier; defaults to high always

Fixes

  • File-Edit: Prevent silent file corruption from escape-sequence mismatches
  • Assistant Message: Prevent JSON corruption when parsing tool arguments
  • Auto-Approval: Extract command from ask text before matching for auto-approval
  • ChatView: Fix type consistency for ChatViewProps and ImageAttachment[]

Documentation

  • executeCommand tool: added isDangerous parameter documentation

Chores

  • Bumped extension version to 6.3.5

Full Changelog: v6.2.6...v6.3.5

code-crusher and others added 11 commits June 3, 2026 21:44
…uments

Introduce tryParseToolArguments() that parses valid JSON verbatim and only
repairs genuinely malformed JSON conservatively. Previously, a lenient regex
that ran before parsing also matched "key": value patterns inside string values
(e.g. "Content-Type": mimeType), turning valid JSON into invalid JSON and
forcing fallback to regex-based partial extraction that preserved literal "\n"
instead of intended newlines, corrupting file edits.

Also fix removePartialBlock to match by toolUseId only instead of requiring
partial === true, because by finalization time Task.ts has already flipped all
partial blocks to partial:false.
…ismatches

Split replacement strategies into two tiers: SAFE_REPLACERS (exact match,
whitespace/normalization-flexible strategies) whose matches can be applied
without risk, and ESCAPE_FUZZY_REPLACERS that locate old_string by normalizing
escape sequences but deliberately do NOT apply the edit — instead they fail
loudly with guidance to re-send old_string verbatim.

In multiFileEditTool, remove the double-unescaping of old_string/new_string
since escape sequences are already decoded at the transport boundary (JSON.parse
for native tool calls). Also add a guard that rejects edits whose old_string
matches text just inserted by a prior edit to the same file.
Execa spawns a fresh subprocess per command, so `cd` changes were lost between
commands. Append `pwd -P` after each command, write the result to a temp file,
and read it back to update the terminal's tracked cwd. Only applies on POSIX.

Also set GIT_EDITOR=true in the subprocess env to prevent interactive git
operations (commit/rebase) from hanging a non-interactive subprocess forever.
…one capture

Introduce SpeechToTextRecorder that captures microphone audio and transcribes
via the KiloCode API. Wire it through webviewMessageHandler with start/stop
recording message types. Add useAudioRecorder hook on the webview side and
associated message types (speechToTextRequest/Response). Language defaults to
"en" to prevent Whisper from auto-detecting filler words as other languages.
When execute_command uses a custom message, the ask text includes a
MESSAGE: prefix (e.g. 'MESSAGE:Installing\n---\nnpm install'). The
auto-approval prefix matching was comparing this raw ask text against
the stored allowedCommands patterns, so the match always failed for
commands with custom messages. The 'Don't ask again' feature saved the
command correctly but the subsequent auto-approval check never
recognized it.

Added extractCommandFromAskText() helper that strips the MESSAGE prefix
and Output suffix, and applied it in getCommandDecisionForMessage and
getDeniedPrefix so prefix matching operates on the actual command text.
…sk/approve-for-me/full-access

Introduce a three-tier command approval setting that users can select from
the chat textarea:

  - "ask"          -> prompt before every command
  - "approveForMe" -> auto-approve commands the model marks as non-dangerous;
                     prompt for dangerous ones (default)
  - "fullAccess"   -> auto-approve every command without asking

Key changes:

  - Add `commandApprovalMode` to the global settings schema and wire it
    through `ExtensionState`, `WebviewMessage`, and `ClineProvider`.
  - Refactor the `askApproval` branch in `presentAssistantMessage`:
    extract the common "auto-approve without blocking" pattern into a
    reusable helper that still surfaces the tool UI row but resolves the ask
    promise immediately via setImmediate/yesButtonClicked.
  - Only `execute_command` (ask type "command") now triggers a real
    Run/Cancel prompt; every other tool always auto-approves.
  - Add `isDangerous` param to `ExecuteCommandToolUse` in the shared type
    definitions and the native-tool JSON schema.
  - `executeCommandTool` reads the model-supplied `isDangerous` flag and
    stashes it on `Task.pendingCommandIsDangerous`.
  - The `presentAssistantMessage` command branch reads the mode + danger
    flag to decide whether to auto-approve or prompt.
  - Switching away from "fullAccess" clears the per-task
    `autoApproveAllCommands` override so the new mode takes effect
    immediately.
…chat textarea

Implement the frontend side of the configurable command approval mode:

  - Add `CommandApprovalSelector` component with a dropdown offering the
    three modes: Ask for Approval, Approve for Me (default), Full Access.
    Each option displays an icon and description.
  - Add HandIcon, ShieldUserIcon, and SecurityWarningIcon SVGs in customIcons
    for the approval mode dropdown options.
  - Register new icons in the select-dropdown icon map.
  - Replace the KiloModeSelector with the CommandApprovalSelector in the
    chat textarea bottom controls.
  - Wire `commandApprovalMode` and `setCommandApprovalMode` through the
    ExtensionStateContext with a default of "approveForMe".
  - Add i18n strings for the three mode labels and descriptions.
…l prompts

Document the new `isDangerous` parameter in both the XML-style
(execute-command.ts) and native-tool JSON (execute_command.ts) prompt
templates:

  - XML prompt: add `isDangerous` to Parameters list and Usage example.
  - Native JSON schema: add boolean `isDangerous` property with description
    of what makes a command dangerous (deletes/overwrites, force-push,
    database migrations, permission changes, etc.).
Update package.json version from 6.3.0 to 6.3.5 to reflect the command
approval mode feature and related changes.
@matterai-app

matterai-app Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Context

This PR introduces robust tool argument parsing to prevent file corruption, implements a granular command approval system, enables terminal CWD persistence across commands, and adds extension-host speech-to-text capabilities.

Implementation

The core change involves a refactored AssistantMessageParser that uses a two-phase parsing strategy: strict JSON.parse for verbatim decoding followed by a conservative repair for malformed LLM output. The fileEditTool now categorizes replacers into 'SAFE' and 'ESCAPE-FUZZY' to prevent silent corruption of escaped characters. Terminal CWD persistence is achieved by appending a pwd trailer to shell commands and reading the result from a temporary file. Speech-to-text is implemented via a Node-based ffmpeg recorder to bypass webview permission restrictions.

Screenshots

before after
N/A N/A

How to Test

  1. Terminal Persistence: Open a terminal, run cd src, then run ls. The second command should execute within the src directory.
  2. Escape Handling: Use a tool to edit a file containing literal \n strings and verify they are not converted to real newlines.
  3. Speech-to-Text: Click the microphone icon in the chat area (if enabled) and verify transcription appears in the input.

Get in Touch

@matter-ai-agent

Summary By MatterAI MatterAI logo

🔄 What Changed

  • Robust Parsing: Refactored AssistantMessageParser to handle native tool call escapes verbatim, preventing \n corruption.
  • Command Safety: Added commandApprovalMode (ask, approveForMe, fullAccess) and isDangerous flags for CLI tools.
  • Terminal CWD: Implemented CWD persistence in ExecaTerminalProcess using shell trailers and temp files.
  • Voice Input: Added SpeechToTextRecorder using ffmpeg for extension-host audio capture.
  • Model Updates: Updated metadata for axon-code-2-5-mini.

🔍 Impact of the Change

  • Reliability: Eliminates silent file corruption during AI-driven edits.
  • UX: Improves terminal flow by maintaining directory state and adds accessibility via voice.
  • Security: Provides users with granular control over potentially destructive CLI commands.

📁 Total Files Changed

Click to Expand
File ChangeLog
AssistantMessageParser.ts Implemented robust JSON parsing and repair logic for tool arguments.
fileEditTool.ts Added escape-fuzzy detection to prevent corrupting file content.
ExecaTerminalProcess.ts Added CWD tracking via pwd trailers and GIT_EDITOR safety.
SpeechToTextRecorder.ts New integration for audio capture via ffmpeg in the extension host.
presentAssistantMessage.ts Integrated new command approval logic and non-blocking auto-approval.
ChatTextArea.tsx Added audio recorder hooks and command approval UI selector.

🧪 Test Added/Recommended

Added

  • NativeToolCallEscapes.spec.ts: Verifies \n decoding and stream finalization.
  • performReplacement.spec.ts: Tests escape safety and exact match verbatim application.
  • ExecaTerminalCwdPersistence.spec.ts: Integration test for cd persistence and GIT_EDITOR environment.

Recommended

  • Add unit tests for isSilent RMS threshold logic in SpeechToTextRecorder.
  • Add E2E tests for the commandApprovalMode state transitions in the webview.

🔒 Security Vulnerabilities

  • Command Execution: Mitigated by the new isDangerous flag and user-configurable approval modes. 🛡️
  • Path Traversal: CWD persistence uses pwd -P and temp files; ensure temp file cleanup is guaranteed even on process crash. 📂

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 PR Review is completed: High-complexity release PR with substantive changes across parsing, tool execution, terminal cwd persistence, speech-to-text, and command approval modes. Found 3 issues: a race condition in speech recorder cleanup, a potential null dereference in the new tryParseToolArguments repair path, and a missing await in the terminal cwd persistence test helper that could mask real failures.

Skipped files
  • webview-ui/src/i18n/locales/en/chat.json: Skipped file pattern
⬇️ Low Priority Suggestions (4)
src/integrations/speech/SpeechToTextRecorder.ts (1 suggestion)

Location: src/integrations/speech/SpeechToTextRecorder.ts (Lines 225-228)

🔴 Concurrency / Resource Leak

Issue: stop() sets this.stopped = true, then immediately assigns this.proc = undefined, but the proc reference is captured in a closure for the exit listener and the timeout. If stop() is called twice in rapid succession (or if start() hasn't finished yet), the second call will see this.proc === undefined and skip the graceful shutdown, leaving the first call's proc potentially orphaned. More critically, the enqueue chain uses .catch(() => {}) which swallows all transcription errors silently, making debugging impossible.

Fix: Guard stop() with an early-return if already stopped, and avoid swallowing errors in the transcription chain.

Impact: Prevents orphaned ffmpeg processes and makes STT failures observable.

-  	async stop(): Promise<void> {
-  		this.stopped = true
-  		const proc = this.proc
-  		this.proc = undefined
+  	async stop(): Promise<void> {
+  		if (this.stopped) {
+  			return
+  		}
+  		this.stopped = true
+  		const proc = this.proc
+  		this.proc = undefined
+  
src/core/assistant-message/AssistantMessageParser.ts (1 suggestion)

Location: src/core/assistant-message/AssistantMessageParser.ts (Lines 53-53)

🟡 Null Safety / Logic Error

Issue: In tryParseToolArguments, the repair regex path does const repaired = rawArgs.replace(...) and then checks if (repaired !== rawArgs). However, if rawArgs is undefined or null (defensive coding), the .trim() on line 37 would throw before reaching here. More realistically: the catch on line 47 catches JSON.parse throwing, but if rawArgs is an empty string '', .trim() returns '', the startsWith check fails, and it returns undefined — that's fine. The real issue is that the repair regex can match inside string values that contain colons, e.g. "key": value patterns inside a JSON string value. The comment block above acknowledges this was the old bug, but the new regex /("([^"]+)"\s*:\s*)([a-zA-Z0-9_.*\/\-]+)(?=\s*[,\]}])/g still uses "([^"]+)" which will match any quoted key, including those inside string values, if the value after the colon is a bare scalar. The comment says "conservative repair" but the regex is still vulnerable to matching inside string values that happen to contain "something": scalar patterns.

Fix: Add a negative lookbehind to ensure we're not inside a string value, or more practically, validate that the match position is not inside a quoted string. Given the complexity, at minimum add a guard that the key is at the top level or inside an object, not inside a string value.

Impact: Prevents the exact corruption bug (literal in files) from reoccurring if the model emits nested JSON-like strings.

-  			const repaired = rawArgs.replace(/("([^"]+)"\s*:\s*)([a-zA-Z0-9_.*\/\\-]+)(?=\s*[,\]}])/g, '$1"$3"')
+  			const repaired = rawArgs.replace(/(?<![^\\]\\)("([^"]+)"\s*:\s*)([a-zA-Z0-9_.*\/\\-]+)(?=\s*[,\]}])/g, '$1"$3"')
+  
src/integrations/terminal/__tests__/ExecaTerminalCwdPersistence.spec.ts (1 suggestion)

Location: src/integrations/terminal/__tests__/ExecaTerminalCwdPersistence.spec.ts (Lines 16-30)

🟡 Async / Test Reliability

Issue: In runCommand, the onCompleted callback captures output = out ?? "", but the promise resolves immediately when terminal.runCommand(...).then(() => resolve(output)) fires. However, onCompleted is a callback that may fire before the process promise resolves, or there may be a race where output hasn't been assigned yet. More importantly, runCommand doesn't await anything inside the promise constructor — it just attaches .then to process. If terminal.runCommand resolves before onCompleted fires, output will still be empty. The helper should await the completion callback explicitly.

Fix: Use a Promise that resolves from onCompleted directly, not from process.then.

Impact: Prevents flaky tests where output is captured before the callback fires.

-  function runCommand(terminal: ExecaTerminal, command: string): Promise<string> {
-  	return new Promise<string>((resolve, reject) => {
-  		let output = ""
-  		const callbacks: RooTerminalCallbacks = {
-  			onLine: () => {},
-  			onCompleted: (out) => {
-  				output = out ?? ""
-  			},
-  			onShellExecutionStarted: () => {},
-  			onShellExecutionComplete: () => {},
-  		}
-  		const process = terminal.runCommand(command, callbacks)
-  		process.then(() => resolve(output)).catch(reject)
-  	})
-  }
+  function runCommand(terminal: ExecaTerminal, command: string): Promise<string> {
+  	return new Promise<string>((resolve, reject) => {
+  		let output = ""
+  		const callbacks: RooTerminalCallbacks = {
+  			onLine: () => {},
+  			onCompleted: (out) => {
+  				output = out ?? ""
+  				resolve(output)
+  			},
+  			onShellExecutionStarted: () => {},
+  			onShellExecutionComplete: () => {},
+  		}
+  		terminal.runCommand(command, callbacks).catch(reject)
+  	})
+  }
+  
src/core/tools/fileEditTool.ts (1 suggestion)

Location: src/core/tools/fileEditTool.ts (Lines 933-938)

🟡 Code Quality / Maintainability

Issue: The new scanReplacers function and performReplacement refactor introduce ReplacerScanResult and split replacers into SAFE_REPLACERS and ESCAPE_FUZZY_REPLACERS. However, sourceCodeEscapeReplacer (in ESCAPE_FUZZY_REPLACERS) still contains logic that can yield matches which, if applied, would corrupt escape sequences. The PR correctly prevents application by only using these replacers for detection, but the naming and separation is fragile — future maintainers might mistakenly move sourceCodeEscapeReplacer into SAFE_REPLACERS.

Fix: Add a prominent comment on sourceCodeEscapeReplacer itself warning that it must NEVER be in SAFE_REPLACERS, and rename ESCAPE_FUZZY_REPLACERS to DETECT_ONLY_ESCAPE_REPLACERS to make the intent unmistakable.

Impact: Prevents future regressions where escape-normalizing replacers get accidentally promoted to safe/application paths.

-  // ESCAPE-FUZZY replacers: match by normalizing escape sequences (literal "\n"
-  // vs real newline, escaped quotes, etc.). These are NEVER applied — a match
-  // here means old_string's escaping disagrees with the file, which is exactly
-  // the condition that produces silent file corruption. performReplacement uses
-  // them only to detect that case and fail loudly with guidance.
-  const ESCAPE_FUZZY_REPLACERS: Replacer[] = [sourceCodeEscapeReplacer, escapeNormalizedReplacer]
+  // ESCAPE-FUZZY replacers: match by normalizing escape sequences (literal "\n"
+  // vs real newline, escaped quotes, etc.). These are NEVER applied — a match
+  // here means old_string's escaping disagrees with the file, which is exactly
+  // the condition that produces silent file corruption. performReplacement uses
+  // them only to detect that case and fail loudly with guidance.
+  const DETECT_ONLY_ESCAPE_REPLACERS: Replacer[] = [sourceCodeEscapeReplacer, escapeNormalizedReplacer]
+  

@code-crusher code-crusher merged commit 5fc2a24 into main Jun 8, 2026
3 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant