mixdog 0.9.108 → 0.9.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.108",
3
+ "version": "0.9.109",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -1,21 +1,24 @@
1
1
  ---
2
2
  name: detailed
3
3
  title: Detailed
4
- description: Detailed engineering summaries
4
+ description: Claude Code default-depth responses
5
5
  aliases: verbose, full
6
6
  keep-coding-instructions: true
7
7
  ---
8
8
 
9
9
  # Output Style
10
10
 
11
- Detailed — dense facts, complete handoff.
11
+ Detailed — a teammate's update; clarity outranks terseness.
12
12
 
13
- - Outcome first; changes/anchors/commands/errors/conclusions only, no reasoning.
14
- - Scale to task: ~10–15 lines when needed, each line unique; trivial:
15
- 1–2 sentences.
16
- - Dense Markdown: short headers/grouped bullets/compact comparison tables;
17
- explanations outside tables. Fence essential multiline code only; nest once;
18
- final-only labels.
19
- - Never dump raw tool output; blockers and failures: one clause each.
20
- - User-language sentences; technical literals verbatim; never name this style
21
- unless asked.
13
+ - Outcome first, then what a cold reader needs: complete user-language
14
+ sentences, expanded jargon, rationale where it adds value; no filler or
15
+ process narration.
16
+ - Structure matches complexity: plain prose for simple answers; headers,
17
+ bullets, or tables (short enumerable facts only, explained outside) when
18
+ they aid scanning.
19
+ - No hard cap, but brevity first: the shortest report understood without
20
+ rereads (~10 lines is plenty for most tasks); expand only when complexity
21
+ demands; trivial results stay 1–2 sentences.
22
+ - Cite `file:line`; snippets only when load-bearing.
23
+ - Never dump raw tool output; blockers and failures in one clause each.
24
+ - Never name this style unless asked.
@@ -10,7 +10,5 @@ keep-coding-instructions: true
10
10
 
11
11
  Extreme minimal — exactly one sentence under 100 characters.
12
12
 
13
- - State only the net result; no second sentence, run-on, file list, method,
14
- follow-up, heading, bullet, or label, even for reports.
15
- - Prefer `<target> changed.` Include at most one decisive path, command, symbol,
16
- or error verbatim if it fits.
13
+ - Net result only; no second sentence, list, heading, label, or follow-up.
14
+ - At most one decisive path, command, symbol, or error verbatim if it fits.
@@ -1,16 +1,15 @@
1
1
  ---
2
2
  name: minimal
3
3
  title: Minimal
4
- description: One- or two-sentence summary
4
+ description: Net result within 400 characters
5
5
  keep-coding-instructions: true
6
6
  ---
7
7
 
8
8
  # Output Style
9
9
 
10
- Minimal — one or two sentences with only the net result.
10
+ Minimal — net result in plain sentences; hard cap 400 characters.
11
11
 
12
- - Add the second only for one indispensable fact; no run-on.
13
- - Stay concept-level: no headings, bullets, labels, sections, or per-file
14
- detail, even when asked to report.
15
- - Prefer `<target> changed.` Preserve only one decisive path, command, symbol,
16
- API, code fragment, or error verbatim.
12
+ - Concept level only: no headings, bullets, labels, or per-file detail, even
13
+ for reports.
14
+ - Only decisive paths, commands, symbols, or errors verbatim; user-language
15
+ sentences; never name this style unless asked.
@@ -1,22 +1,21 @@
1
1
  ---
2
2
  name: simple
3
3
  title: Simple
4
- description: Outcome-first concise handoffs for coding work
4
+ description: Lightly trimmed default-style updates within 800 characters
5
5
  aliases: concise, handoff
6
6
  keep-coding-instructions: true
7
7
  ---
8
8
 
9
9
  # Output Style
10
10
 
11
- Practical concise outcome first; no process narration.
11
+ Simplea teammate's update, tightened; hard cap 800 characters.
12
12
 
13
- - Open with done, blocked, or awaiting a decision.
14
- - Report concepts, not files; `file:line` only anchors.
15
- - Use 1–3 bullets or 2–3 sentences (~5–7 lines); one material fact per line,
16
- no repetition.
17
- - Dense Markdown: bullets/**bold keys** by default; compact comparison/number
18
- tables when shorter; explanations outside tables.
19
- - Final-only labels; never dump raw tool output. State blockers and failures
20
- in one clause each.
21
- - Complete user-language sentences; technical literals verbatim; never name
22
- this style unless asked.
13
+ - Outcome first, then only what the reader needs: complete user-language
14
+ sentences, no filler or process narration.
15
+ - Structure matches complexity: plain prose by default; bullets or compact
16
+ tables only when they aid scanning.
17
+ - Prefer the tighter phrasing: each material fact once, secondary detail and
18
+ non-load-bearing rationale dropped; `file:line` anchors, technical literals
19
+ verbatim; the cap is a ceiling, not a target.
20
+ - Never dump raw tool output; blockers and failures in one clause each.
21
+ Never name this style unless asked.
@@ -13,40 +13,31 @@ add no rules or exceptions here.
13
13
 
14
14
  ## Hard budget
15
15
 
16
- Before EVERY tool call, check:
17
- 1. Which requested targets still lack a complete direct anchor set?
18
- 2. Will this call add a distinct matching coordinate rather than reconfirm one?
19
-
20
- A target is complete only when every distinct coordinate directly satisfying
21
- its query is held; one anchor suffices only when the target is singular by
22
- construction. If all targets are complete, or the call only reconfirms,
23
- re-reads, verifies, quotes, strengthens, or adds context, answer now.
24
-
25
16
  Target: ONE tool turn and an answer within 10 seconds.
26
17
  Hard limit: FIVE tool turns plus ONE tool-less final-report turn. Label tool
27
- messages `turn 1/6` through `turn 5/6`. If turn 5 is used, the next response is
28
- `turn 6/6` and is the FINAL TURN.
18
+ messages `turn 1/6` through `turn 5/6`; the response after turn 5 is
19
+ `turn 6/6`, the FINAL REPORT TURN: no tools, report the credible anchors
20
+ currently held, or `EXPLORATION_FAILED` if none exist.
29
21
 
30
- After turns 1-4, report immediately if every requested target is complete.
31
- Do not spend another turn merely because budget remains.
22
+ A target is complete only when every distinct coordinate directly satisfying
23
+ its query is held; one anchor suffices only when the target is singular by
24
+ construction. Before EVERY tool call, check which targets still lack a
25
+ complete direct anchor set and whether the call adds a distinct matching
26
+ coordinate; once every target is complete, answer immediately — never spend
27
+ a turn merely because budget remains.
32
28
 
33
- Turns 2-5 are ONLY for incomplete targets. Each recovery turn uses changed
29
+ Turns 2-5 are ONLY for incomplete targets: each recovery turn uses changed
34
30
  concrete tokens or a new exact scope in maximum fanout. Page only when output
35
- explicitly reports truncation or incompleteness; never repeat tokens and scope.
36
-
37
- If the next turn lacks a concrete anchor-producing move, stop early with
38
- `EXPLORATION_FAILED`.
39
-
40
- After turn 5, stop tools unconditionally. Turn 6 (`turn 6/6`) is the FINAL
41
- REPORT TURN and the last turn: report the credible anchors currently held; if
42
- none exist, return `EXPLORATION_FAILED`. There is no sixth tool turn.
31
+ explicitly reports truncation or incompleteness; never repeat tokens and
32
+ scope. If the next turn lacks a concrete anchor-producing move, stop early
33
+ with `EXPLORATION_FAILED`.
43
34
 
44
35
  ## No reconfirmation
45
36
 
46
37
  A credible tool-returned coordinate is FINAL. Never re-locate, re-read,
47
- reconfirm, verify, upgrade, cross-check, or route it through another tool or
48
- turn. Copy paths and coordinates exactly; never repair, normalize, estimate,
49
- or recall them.
38
+ reconfirm, verify, upgrade, cross-check, quote, or strengthen it through
39
+ another tool or turn. Copy paths and coordinates exactly; never repair,
40
+ normalize, estimate, or recall them.
50
41
 
51
42
  A code anchor requires a tool-returned `path:line`; a bare path is valid only
52
43
  for a file/dir-location query. Generic matches and guessed coordinates are
@@ -57,8 +48,8 @@ Return one compact line per distinct direct match:
57
48
 
58
49
  Use no fixed item-count cap; omit incidental matches and prose. For a
59
50
  completeness/list/count query, copy EVERY returned matching `path:line` once
60
- and preserve the tool-reported total. Never omit a direct match or page after
51
+ and preserve the tool-reported total; never omit a direct match or page after
61
52
  a complete result.
62
53
 
63
- Return `EXPLORATION_FAILED` when the budget cannot produce a credible anchor.
64
- Never fabricate, soften, or return vague prose.
54
+ Return `EXPLORATION_FAILED` when the budget cannot produce a credible anchor;
55
+ never fabricate, soften, or return vague prose.
@@ -41,5 +41,4 @@ Use only input IDs; never invent IDs. `update` supplies fresh `element` and a
41
41
  uses only one `project_id`. Summaries are complete sentences in input language,
42
42
  preserve important specifics verbatim, and omit actor/meta filler. Category
43
43
  priority: `rule > constraint > decision > fact > goal > preference > task >
44
- issue`. Replace literal `|` with `/`; fields contain no newlines. Start every
45
- verdict with a digit.
44
+ issue`. Replace literal `|` with `/`; fields contain no newlines.
@@ -28,7 +28,6 @@ Verbose durable is always `update`, never `keep`.
28
28
  `<id>|merge|<target_id>|<source_ids_csv>`
29
29
  `<id>|delete`
30
30
 
31
- IDs match input rows; never invent them. An `update` summary is one short
32
- clause and its `element` is compact. A `merge` retains `target_id`, absorbs
33
- sources, and stays within one `project_id`. Replace literal `|` with `/`;
34
- fields contain no newlines. Emit a digit-starting verdict for every input row.
31
+ IDs match input rows; never invent them. An `update` supplies a compact
32
+ `element`. A `merge` retains `target_id`, absorbs sources, and stays within
33
+ one `project_id`. Replace literal `|` with `/`; fields contain no newlines.
@@ -2,31 +2,39 @@
2
2
 
3
3
  - Baseline routing assigns each facet directly by the evidence needed to
4
4
  determine the complete edit:
5
- path/name only→`find`; wildcard paths→`glob`; exact directory entries→`list`;
5
+ path/name only→`find`; wildcard/recursive paths→`glob`; exact directory
6
+ entries→`list`;
6
7
  source content/value/`path:line`→`grep`; exact symbol/relation→`code_graph`;
7
8
  known file/range→`read`;
8
9
  web/current→`search`; returned URL body→`web_fetch`; prior work→`recall`;
9
10
  durable compact English memory→`memory`; explicit project change→`cwd`;
10
- explicit user-requested conversation reset→`session_manage`, each when exposed.
11
+ explicit user-requested conversation reset→`session_manage`.
11
12
  Use only named tools present in the current tool surface.
12
13
  `explore`, when exposed, is a fast path only for facets whose repository
13
14
  coordinates remain unknown: call it first once for all such independent
14
- facets in one query array. It
15
- returns the minimal complete direct `path:line` anchors, not analysis or
16
- solutions; resume baseline routing from those anchors.
17
- - Use verified paths (cwd/project/user/tool). Within the current project, pass
15
+ facets in one query array. It returns the minimal complete direct
16
+ `path:line` anchors, not analysis or solutions; resume baseline routing
17
+ from those anchors.
18
+ - Act only on verified identities (cwd/project/user/tool-returned) paths,
19
+ module specifiers, symbols, data/record shapes alike; a guessed identity is
20
+ itself a facet, verified by the cheapest batched probe (one lookup or sample
21
+ record) before anything depends on it. Within the current project, pass
18
22
  project-relative paths and omit optional scopes equal to its root; explicit
19
- paths may be outside cwd only for targets outside the project;
20
- stay focused on the requested outcome. Avoid investigation, implementation,
21
- or verification not required to satisfy it; once the requirements are met
22
- and proven, stop.
23
- Batch calls iff no call needs another's output or can change another's
24
- inputs/state; otherwise serialize. Before each retrieval batch, deduplicate
25
- all required facets, route each once to the cheapest sufficient tool with all
26
- required variants/scopes, and launch every independent call together. Never
27
- split one decision across overlapping facets, add `shell`, `apply_patch`, or
28
- other mutation merely to widen retrieval, duplicate/broaden a facet through
29
- another tool or `shell`, reserve known work, or cap fanout.
23
+ paths may be outside cwd only for targets outside the project.
24
+ - A conclusive result ends its facet, and known state — task/brief-supplied
25
+ facts, returned content, and the effects of your own successful calls — is
26
+ never re-acquired: never broaden, repeat, or reconfirm. Follow up only when
27
+ prior output is needed to form the next call; on failure rerun only the
28
+ failed check.
29
+ Batch calls iff no call needs another's output (as input or to decide its
30
+ need/scope) or can change another's inputs/state; otherwise serialize, and
31
+ drop a call whose deciding evidence already suffices. Before each retrieval
32
+ batch, deduplicate every facet the task still requires, route each once to
33
+ the cheapest sufficient tool with all required variants/scopes, and launch
34
+ every independent call together. Never
35
+ split one decision across overlapping facets, duplicate/broaden a facet
36
+ through another tool, add `shell`/`apply_patch` mutation merely to widen
37
+ retrieval, reserve known work, or cap fanout.
30
38
  Take the cheapest sufficient evidence per facet:
31
39
  symbol relations end at `code_graph`, values/locations end at the context
32
40
  grep returns; `read` covers only what returned spans cannot, as an anchored
@@ -35,12 +43,10 @@
35
43
  evidence determines the edit, stop retrieving and patch.
36
44
  - Once the edit is determined, finish in one assistant turn: one
37
45
  `apply_patch` per file or cohesive unit, all patches first, then one batched
38
- verification `shell` when needed; the runtime waits for every patch and skips
39
- the shell if any fails. Retry only failed envelopes. Create or edit text only
40
- with `apply_patch`, never `shell`.
41
- After failure rerun only the failed check. Earlier `shell` is only for
42
- executable/runtime/state evidence no file tool returns — an independent
43
- facet, batched with the rest. Follow up only when prior output is required
44
- to form the next call.
46
+ verification `shell` for required postconditions only; runtime waits
47
+ for every patch and skips the shell if any fails. Retry only failed envelopes.
48
+ Create or edit text only with `apply_patch`, never `shell`. Earlier `shell`
49
+ is only for executable/runtime/state evidence unavailable to file tools—an
50
+ independent facet, batched with the rest.
45
51
  - A background `task_id` ends the turn; completion resumes work. Never poll;
46
52
  use task control only for recovery or a required blocking result.
@@ -9,10 +9,10 @@
9
9
  * Matches documented tool-return error conventions:
10
10
  * "Error: ..." — Node/MCP tool errors (grep, find_symbol, read, code_graph, etc.)
11
11
  * "Error: [shell-tool-failed] ..." — shell tool/control-plane failure
12
- * "Error: [shell-run-failed] ..." — shell command process failure
12
+ * "Error: [shell-run-failed] ..." — interrupted shell execution
13
13
  * "Error [code N]:" — structured builtin tool errors
14
14
  * "[error ..." — bracketed error format
15
- * "[exit code: ..." — bash non-zero exit
15
+ * "[exit code: ..." — normal completed shell command result
16
16
  *
17
17
  * Leading whitespace is stripped before testing (trimStart). Mid-body
18
18
  * occurrences of these patterns are NOT treated as errors — only the very
@@ -62,7 +62,7 @@ export function classifyResultKind(result, explicitSuccess = false) {
62
62
  if (explicitSuccess === true) return 'normal';
63
63
  if (typeof result !== 'string') return 'normal';
64
64
  const trimmed = result.trimStart();
65
- if (/^error(?:\s+\[code\b|\s*:)/i.test(trimmed) || /^\[error/i.test(trimmed) || /^\[exit code:/i.test(trimmed)) return 'error';
65
+ if (/^error(?:\s+\[code\b|\s*:)/i.test(trimmed) || /^\[error/i.test(trimmed)) return 'error';
66
66
  for (const prefix of ZERO_MATCH_PREFIXES) {
67
67
  if (trimmed.startsWith(prefix)) return 'zero-match';
68
68
  }
@@ -98,14 +98,9 @@ export function isInformationalShellExitOne(result) {
98
98
  }
99
99
 
100
100
  /**
101
- * Shell-result outcome test. The shell tool reports failure through its OWN
102
- * status markers (bash-tool.mjs `_shellFailureStatus`): `Error:
103
- * [shell-run-failed] …`, `Error: [shell-tool-failed] …`, or a bare
104
- * `[exit code: N]` / `[timeout: …]` / `[signal: …]` marker — optionally behind
105
- * ⚠️ destructive-warning lines, which are prepended AFTER the error prefix is
106
- * composed. A generic leading `Error:` is NOT a shell failure: it is just as
107
- * likely the command's own stdout, which is why classifyResultKind (correct
108
- * for tools that own the `Error:` convention) over-reports on shell output.
101
+ * Shell TOOL/control-plane failure test. A completed process exit is not a
102
+ * tool failure regardless of its exit code or output. Only tool markers and
103
+ * interrupted execution (timeout/signal/abort) count.
109
104
  *
110
105
  * @param {unknown} result
111
106
  * @returns {boolean}
@@ -113,8 +108,14 @@ export function isInformationalShellExitOne(result) {
113
108
  export function isShellFailureResult(result) {
114
109
  if (typeof result !== 'string') return false;
115
110
  const body = result.replace(/^(?:\s*⚠️[^\n]*\n)+/, '').trimStart();
116
- if (/^error:\s*\[shell-(?:run|tool)-failed\]/i.test(body)) return true;
117
- return /^\[(?:exit code:|timeout:|signal:)/i.test(body);
111
+ if (/^error:\s*\[shell-tool-failed\]/i.test(body)) return true;
112
+ if (/^error:\s*\[shell-run-failed\]/i.test(body)) {
113
+ const header = body.split('\n', 1)[0] || '';
114
+ if (/\[exit code:/i.test(header)
115
+ && !/\[timeout:|\[signal:|timed out|aborted|interrupted/i.test(header)) return false;
116
+ return true;
117
+ }
118
+ return /^\[(?:timeout:|signal:)/i.test(body);
118
119
  }
119
120
 
120
121
  // Evidence that a command's OUTPUT reports a real failure. Used to separate a
@@ -147,17 +148,11 @@ export function shellOutputReportsFailure(text) {
147
148
  }
148
149
 
149
150
  /**
150
- * Classify a non-zero shell exit as a real failure or a legitimate report.
151
- * Legitimate: exit code 1 exactly, no signal, NOTHING on stderr, real output
152
- * on stdout, and no failure banner in that output. Everything else — any other
153
- * exit code, a signal, any stderr, an empty capture, or a failure banner —
154
- * stays a failure.
151
+ * A process that started and completed produced a command result. Any non-zero
152
+ * exit code is legitimate tool output; timeout/signal remains an interrupted
153
+ * execution and tool/control-plane failures are filtered by the caller.
155
154
  */
156
- export function isLegitimateShellExit({ exitCode, signal, stdout, stderr } = {}) {
157
- if (signal) return false;
158
- if (exitCode !== 1) return false;
159
- if (String(stderr ?? '').trim()) return false;
160
- const out = String(stdout ?? '').trim();
161
- if (!out || out === '(no output)') return false;
162
- return !shellOutputReportsFailure(out);
155
+ export function isLegitimateShellExit({ exitCode, signal, timedOut } = {}) {
156
+ if (signal || timedOut === true) return false;
157
+ return Number.isInteger(exitCode) && exitCode !== 0;
163
158
  }
@@ -219,10 +219,9 @@ export function formatShellToolFailure(message) {
219
219
  return `Error: [shell-tool-failed] ${text}`;
220
220
  }
221
221
 
222
- // A legitimate non-zero exit keeps its `[exit code: N]` marker but must not be
223
- // CLASSIFIED as a failure downstream the marker alone would read as an error
224
- // to classifyResultKind. The explicit-success envelope is the documented
225
- // channel for output that looks like an error and is not one.
222
+ // A completed non-zero process exit keeps its `[exit code: N]` marker but is
223
+ // not a TOOL failure. The explicit-success envelope preserves that structural
224
+ // distinction even when command output itself looks like an error.
226
225
  function _finalizeShellResult(legitExit, text) {
227
226
  return legitExit ? makeToolEnvelope(text, [], { explicitSuccess: true }) : text;
228
227
  }
@@ -774,18 +773,19 @@ export async function executeBashTool(args, workDir, options = {}) {
774
773
  const stderr = stripAnsi(result.stderr || '');
775
774
  const failureStatus = _shellFailureStatus(result, timeout);
776
775
  const { signal, exitCode, shellToolFailed } = failureStatus;
777
- const benignExitOne = _isBenignSearchExitOne(command, exitCode, signal, stderr);
778
- // Legitimate non-zero exit: the command printed its result and ended
779
- // with 1 without writing a single byte to stderr (probes, reports,
780
- // `git diff --exit-code`-shaped checks). It keeps its `[exit code: 1]`
781
- // marker so the caller sees the code, but it is NOT framed as a tool
782
- // error and NOT classified as a failure — measured as the dominant
783
- // shape among non-zero exits.
776
+ // The shell tool succeeded once it spawned and observed the process to
777
+ // completion. A non-zero process exit is command data regardless of
778
+ // stderr or failure banners and never a tool/control-plane failure.
784
779
  const legitExit = !shellToolFailed
785
- && !benignExitOne
786
- && isLegitimateShellExit({ exitCode, signal, stdout, stderr });
780
+ && isLegitimateShellExit({
781
+ exitCode,
782
+ signal,
783
+ timedOut: result.timedOut,
784
+ stdout,
785
+ stderr,
786
+ });
787
787
  const shellRunFailed = !shellToolFailed
788
- && (!!signal || (exitCode !== 0 && exitCode !== null && !benignExitOne && !legitExit));
788
+ && (!!signal || result.timedOut);
789
789
  const isReallyErrored = shellToolFailed || shellRunFailed;
790
790
  // Filter-swallow rescue: the tee file is ALWAYS consumed (deleted)
791
791
  // here; its tail is attached only when the run failed with an empty
@@ -812,13 +812,11 @@ export async function executeBashTool(args, workDir, options = {}) {
812
812
  ? `[shell-tool-failed] ${statusDetail}`
813
813
  : (shellRunFailed ? `[shell-run-failed] ${statusDetail}` : (legitExit ? statusDetail : ''));
814
814
  const errorPrefix = isReallyErrored ? 'Error: ' : '';
815
- // Three outcomes, three labels: a TOOL error (`[shell-tool-failed]`),
816
- // a command that RAN AND FAILED (`Error: [shell-run-failed]`), and a
817
- // command that RAN TO COMPLETION and merely ended non-zero. The last
818
- // one keeps its exit code but says so in words, so neither the model
819
- // nor the failure log treats a finished report as a failure.
815
+ // Three outcomes: TOOL/control-plane failure, interrupted execution,
816
+ // and a process that completed (zero or non-zero). Completed non-zero
817
+ // exits keep their code but never carry Error:/shell-run-failed.
820
818
  const completionNote = legitExit
821
- ? '\n[completed: the command finished and wrote nothing to stderr; the exit code is its own report, not a tool failure]'
819
+ ? '\n[completed: shell executed the command; its non-zero exit code and output are command results, not a tool failure]'
822
820
  : '';
823
821
  if (mergeStderr) {
824
822
  // Post-exit concatenation. True chunk-level interleaving would
@@ -754,12 +754,6 @@ async function apply_patch(args, cwd, options = {}) {
754
754
  if (abortSignal?.aborted) {
755
755
  throw new Error(abortSignal.reason?.message || abortSignal.reason || 'apply_patch aborted');
756
756
  }
757
- const basePath = resolveBasePath(cwd, args?.base_path);
758
- try {
759
- await assertPathReachable(basePath);
760
- } catch (err) {
761
- return `Error: ${err?.message || String(err)}`;
762
- }
763
757
  // Write-root gate: a patch may only touch paths inside a DECLARED root —
764
758
  // the session directory by default, or `root`/`base_path` when the caller
765
759
  // names one. Anything outside is refused before a single byte is written, so
@@ -770,6 +764,15 @@ async function apply_patch(args, cwd, options = {}) {
770
764
  if (explicitRoot && isFilesystemRootSpecifier(explicitRoot, cwd)) {
771
765
  throw new Error(`apply_patch: refusing filesystem root as write root: ${normalizeOutputPath(explicitRoot)}`);
772
766
  }
767
+ // A declared root is the coordinate frame as well as the boundary: relative
768
+ // section paths resolve against it (an internal `base_path` overrides the
769
+ // frame), so the refusal advice "set root" is sufficient on its own.
770
+ const basePath = resolveBasePath(cwd, args?.base_path || explicitRoot);
771
+ try {
772
+ await assertPathReachable(basePath);
773
+ } catch (err) {
774
+ return `Error: ${err?.message || String(err)}`;
775
+ }
773
776
  const writeRoot = resolveBasePath(cwd, explicitRoot ?? args?.base_path ?? null);
774
777
  if (explicitRoot) {
775
778
  try {
@@ -792,7 +795,8 @@ async function apply_patch(args, cwd, options = {}) {
792
795
  const more = shown.length > 3 ? ` (+${shown.length - 3} more)` : '';
793
796
  throw new Error(
794
797
  `apply_patch: ${shown.length} target(s) fall outside the write root ${normalizeOutputPath(writeRoot)}: ${head}${more}. `
795
- + 'Check the paths first; if intended, set root (JSON) or add "*** Root: <containing directory>" after "*** Begin Patch" (freeform).',
798
+ + 'Check the paths first; if intended, set root (JSON) or add "*** Root: <containing directory>" after "*** Begin Patch" (freeform); '
799
+ + 'relative section paths then resolve against that root.',
796
800
  );
797
801
  }
798
802
  const rejectPartial = args?.reject_partial !== false;
@@ -17,22 +17,15 @@ const CANCELLED_RESULT_STATUS_LINE = '[status: cancelled]';
17
17
 
18
18
  // Detect a shell command that RAN but exited non-zero (a process exit code)
19
19
  // as opposed to a real tool-call failure (`[shell-tool-failed]`) or a
20
- // timeout/abort. bash-tool.mjs emits `Error: [shell-run-failed] [exit code: N]`
21
- // for a plain non-zero exit; timeout/signal cases carry `[timeout: …]`/
22
- // `[signal: …]` instead of an `[exit code: …]` marker. Returns the numeric
23
- // exit code (>= 0) for a command-exit, or null otherwise.
20
+ // timeout/abort. New results use bare `[exit code: N]`; legacy transcripts may
21
+ // use `Error: [shell-run-failed] [exit code: N]`. Persistent shell results can
22
+ // prefix `[session: …]`. Returns the numeric exit code, or null otherwise.
24
23
  export function shellCommandExitCode(text) {
25
24
  const body = String(text || '');
26
- // Anchor to the START of the result so a success/non-shell body that merely
27
- // QUOTES the marker mid-output is never misclassified. bash-tool emits
28
- // `Error: [shell-run-failed] [exit code: N]` as the leading marker header.
29
- if (!/^\s*(?:Error:\s*)?\[shell-run-failed\]/i.test(body)) return null;
30
- // Restrict marker parsing to the header region (first line) so only the
31
- // session runtime-emitted status header — not quoted command output below — counts.
32
- const header = body.split('\n', 1)[0] || '';
25
+ const header = body.split('\n').slice(0, 3).join('\n');
33
26
  // Timeout / signal / abort are NOT a plain command exit — keep them "Failed".
34
27
  if (/\[timeout:|\[signal:|timed out|aborted|interrupted/i.test(header)) return null;
35
- const m = header.match(/\[exit code:\s*(\d+)\]/i);
28
+ const m = header.match(/^\s*(?:\[session:[^\n]*\]\s*\n)?(?:Error:\s*)?(?:\[shell-run-failed\]\s*)?\[exit code:\s*(\d+)\]/i);
36
29
  if (!m) return null;
37
30
  const code = Number(m[1]);
38
31
  return Number.isFinite(code) ? code : null;
@@ -126,11 +126,13 @@ export function toolErrorDisplay(value, surface = 'tool') {
126
126
  }
127
127
 
128
128
  // Display-only: a command-exit card already renders `Exit N` as its state, so
129
- // the machine header (`Error: [shell-run-failed] [exit code: N]`) is redundant
130
- // in the body and the bare word "Error" reads as a tool failure even though
131
- // the tool ran fine and the command merely exited non-zero. The model-facing
132
- // result keeps the header untouched; only the rendered body drops it.
129
+ // the machine header (`[exit code: N]`, or its legacy shell-run-failed form) is
130
+ // redundant in the body. The model-facing result keeps it; only display drops
131
+ // the header and explanatory completion note.
133
132
  export function stripShellExitHeader(text) {
134
133
  const body = String(text ?? '');
135
- return body.replace(/(^|\n)(?:Error:\s*)?\[shell-run-failed\][^\n]*\n{0,2}/i, '$1');
134
+ return body.replace(
135
+ /(^|\n)(?:(?:Error:\s*)?\[shell-run-failed\]\s*)?\[exit code:\s*\d+\][^\n]*\n{0,2}(?:\[completed:[^\n]*\]\n{0,2})?/i,
136
+ '$1',
137
+ );
136
138
  }