@sema-agent/core 7.6.3 → 7.8.0

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.
Files changed (164) hide show
  1. package/CHANGELOG.md +70 -10
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/send-message-tool.js +2 -1
  7. package/dist/agents/subagent-steps.js +5 -4
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/brain/anthropic.js +8 -2
  10. package/dist/brain/open-responses.js +5 -3
  11. package/dist/brain/openai.js +31 -8
  12. package/dist/brain/reasoning.d.ts +32 -0
  13. package/dist/brain/reasoning.js +18 -0
  14. package/dist/core/ask-question.js +2 -2
  15. package/dist/core/auto-mode-defaults.d.ts +16 -0
  16. package/dist/core/auto-mode-defaults.js +1 -0
  17. package/dist/core/auto-mode.d.ts +19 -0
  18. package/dist/core/auto-mode.js +74 -56
  19. package/dist/core/checkpoint-execution-record.d.ts +110 -0
  20. package/dist/core/checkpoint-execution-record.js +49 -0
  21. package/dist/core/checkpoint-store.d.ts +114 -2
  22. package/dist/core/checkpoint-store.js +35 -2
  23. package/dist/core/context-edit.d.ts +0 -22
  24. package/dist/core/context-edit.js +2 -11
  25. package/dist/core/engine-notice.d.ts +17 -0
  26. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  27. package/dist/core/fs-write-gate-policy.js +7 -7
  28. package/dist/core/gate-fold.js +2 -2
  29. package/dist/core/gate-lanes.js +4 -3
  30. package/dist/core/governance-codes.d.ts +1 -1
  31. package/dist/core/governance-codes.js +8 -0
  32. package/dist/core/hooks.d.ts +8 -2
  33. package/dist/core/hooks.js +1 -1
  34. package/dist/core/lsp.js +2 -2
  35. package/dist/core/mcp-server-spec.d.ts +17 -0
  36. package/dist/core/mcp.js +3 -1
  37. package/dist/core/memory-engine/tools.js +4 -12
  38. package/dist/core/permission-rules.d.ts +32 -2
  39. package/dist/core/permission-rules.js +43 -37
  40. package/dist/core/present-plan-tool.js +3 -4
  41. package/dist/core/runner/active-skill-scope.js +7 -4
  42. package/dist/core/runner/clock-and-limits.d.ts +117 -0
  43. package/dist/core/runner/clock-and-limits.js +118 -0
  44. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  45. package/dist/core/runner/contracts.d.ts +37 -3
  46. package/dist/core/runner/decide-continuation.d.ts +98 -0
  47. package/dist/core/runner/decide-continuation.js +133 -0
  48. package/dist/core/runner/execution-record.d.ts +26 -0
  49. package/dist/core/runner/execution-record.js +19 -0
  50. package/dist/core/runner/git-leg-delivery.d.ts +28 -0
  51. package/dist/core/runner/git-leg-delivery.js +94 -0
  52. package/dist/core/runner/initial-run-state.d.ts +14 -0
  53. package/dist/core/runner/initial-run-state.js +11 -0
  54. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  55. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  56. package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
  57. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  58. package/dist/core/runner/prepare-defer-classify.js +10 -10
  59. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  60. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  61. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  62. package/dist/core/runner/prepare-gate-stations.js +3 -1
  63. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  64. package/dist/core/runner/prepare-hands-readface.js +3 -2
  65. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  66. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  67. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  68. package/dist/core/runner/prepare-lsp.js +3 -2
  69. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  70. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  71. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  72. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  73. package/dist/core/runner/prepare-park-ask.js +4 -1
  74. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  75. package/dist/core/runner/prepare-project-context.js +7 -5
  76. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  77. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  78. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  79. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  80. package/dist/core/runner/prepare-question-face.js +5 -3
  81. package/dist/core/runner/prepare-safety-scan.js +42 -2
  82. package/dist/core/runner/prepare-task.js +37 -19
  83. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  84. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  87. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  88. package/dist/core/runner/run-harness-handlers.js +345 -0
  89. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  90. package/dist/core/runner/run-turn-boundary.js +694 -0
  91. package/dist/core/runner/runtask.d.ts +0 -68
  92. package/dist/core/runner/runtask.js +66 -1486
  93. package/dist/core/runner/session-rule-policy.js +9 -4
  94. package/dist/core/runner/steer-admission.d.ts +17 -0
  95. package/dist/core/runner/steer-admission.js +17 -0
  96. package/dist/core/runner/synthetic-tools.js +5 -8
  97. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  98. package/dist/core/runner/tool-disclosure.js +9 -5
  99. package/dist/core/runner/tool-end-body.d.ts +71 -0
  100. package/dist/core/runner/tool-end-body.js +75 -0
  101. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  102. package/dist/core/runner/tool-face-overlay.js +125 -0
  103. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  104. package/dist/core/runner/tool-output-projection.js +33 -10
  105. package/dist/core/sensitive-path-policy.d.ts +3 -0
  106. package/dist/core/sensitive-path-policy.js +6 -5
  107. package/dist/core/shared-memory/tools.js +3 -6
  108. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
  109. package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
  110. package/dist/core/task-event.d.ts +10 -0
  111. package/dist/core/task-registry.js +4 -5
  112. package/dist/core/task-spec.d.ts +8 -0
  113. package/dist/core/tool-catalog-entries.d.ts +48 -0
  114. package/dist/core/tool-catalog-entries.js +174 -0
  115. package/dist/core/tool-conformance.d.ts +22 -0
  116. package/dist/core/tool-conformance.js +37 -0
  117. package/dist/core/tool-face.d.ts +171 -0
  118. package/dist/core/tool-face.js +25 -0
  119. package/dist/core/tool-policy.d.ts +24 -0
  120. package/dist/core/tool-policy.js +4 -2
  121. package/dist/core/tool-registry.d.ts +114 -0
  122. package/dist/core/tool-registry.js +114 -0
  123. package/dist/core/tool-result-budget.d.ts +0 -3
  124. package/dist/core/tool-result-budget.js +4 -3
  125. package/dist/core/tool-result-store.d.ts +0 -18
  126. package/dist/core/tool-result-store.js +2 -18
  127. package/dist/core/tool-roster.d.ts +450 -0
  128. package/dist/core/tool-roster.js +497 -0
  129. package/dist/core/tool-spec.d.ts +54 -3
  130. package/dist/core/tools.js +7 -0
  131. package/dist/core/trace.d.ts +24 -0
  132. package/dist/core/wiring-manifest.d.ts +13 -0
  133. package/dist/core/wiring-manifest.js +2 -1
  134. package/dist/core/write-protect.d.ts +2 -1
  135. package/dist/core/write-protect.js +5 -4
  136. package/dist/engine/compaction/utils.d.ts +6 -0
  137. package/dist/engine/compaction/utils.js +19 -19
  138. package/dist/engine/loop/types.d.ts +6 -0
  139. package/dist/index.d.ts +20 -8
  140. package/dist/index.js +19 -7
  141. package/dist/orchestration/goal.js +2 -1
  142. package/dist/orchestration/run-spec.js +3 -5
  143. package/dist/orchestration/run-workflow-tool.js +2 -2
  144. package/dist/prompt-assembly/event-registry.js +3 -3
  145. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  146. package/dist/stores/file/checkpoint-store.d.ts +7 -0
  147. package/dist/stores/file/checkpoint-store.js +20 -1
  148. package/dist/tools/fs/fs-bash.d.ts +1 -1
  149. package/dist/tools/fs/fs-bash.js +7 -11
  150. package/dist/tools/fs/fs-read.js +2 -2
  151. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  152. package/dist/tools/fs/fs-search-tools.js +3 -16
  153. package/dist/tools/fs/fs-write.js +4 -6
  154. package/dist/tools/fs/repo-map.js +2 -2
  155. package/dist/tools/fs/safety.d.ts +2 -1
  156. package/dist/tools/fs/safety.js +6 -5
  157. package/dist/tools/monitor.js +2 -2
  158. package/dist/tools/scheduler-tools.js +5 -8
  159. package/dist/tools/task-list.js +5 -8
  160. package/dist/tools/todo.js +2 -2
  161. package/dist/tools/web.js +3 -6
  162. package/dist/tools/worktree.js +3 -4
  163. package/package.json +4 -1
  164. package/test/export-surface.snapshot.json +183 -7
@@ -32,67 +32,85 @@ export function createAutoModeDecider(opts) {
32
32
  }
33
33
  }
34
34
  };
35
- return {
36
- breakerOpen: () => open,
37
- consecutiveFailures: () => consecutiveFailures,
38
- async decide(input, signal) {
39
- if (open)
40
- return { kind: "unavailable", cause: "breaker_open" };
41
- let timer;
42
- const inner = new AbortController();
43
- const onOuterAbort = () => inner.abort();
44
- if (signal !== undefined) {
45
- if (signal.aborted)
46
- inner.abort();
47
- else
48
- signal.addEventListener("abort", onOuterAbort, { once: true });
49
- }
35
+ const decide = async (input, signal) => {
36
+ const startedAt = performance.now();
37
+ const verdict = await decideUnreported(input, signal);
38
+ if (opts.onClassified !== undefined) {
50
39
  try {
51
- const raw = await new Promise((resolve, reject) => {
52
- let settled = false;
53
- timer = setTimeout(() => {
54
- if (!settled) {
55
- settled = true;
56
- inner.abort();
57
- reject(new AutoModeTimeout());
58
- }
59
- }, timeoutMs);
60
- opts.classify(input, inner.signal).then((v) => {
61
- if (!settled) {
62
- settled = true;
63
- resolve(v);
64
- }
65
- }, (e) => {
66
- if (!settled) {
67
- settled = true;
68
- reject(e);
69
- }
70
- });
40
+ opts.onClassified({
41
+ toolCallId: input.req.toolCallId,
42
+ ms: Math.max(0, Math.round(performance.now() - startedAt)),
43
+ verdict: verdict.kind,
44
+ ...(verdict.kind === "unavailable" ? { cause: verdict.cause } : {}),
71
45
  });
72
- if (open)
73
- return { kind: "unavailable", cause: "breaker_open" };
74
- const verdict = parseAutoModeResponse(raw);
75
- if (verdict.kind === "parse_error")
76
- recordFailure("parse_error");
77
- else
78
- consecutiveFailures = 0;
79
- return verdict;
80
46
  }
81
- catch (e) {
82
- if (signal?.aborted)
83
- return { kind: "unavailable", cause: "error" };
84
- const timedOut = e instanceof AutoModeTimeout;
85
- recordFailure(timedOut ? "timeout" : "error");
86
- return { kind: "unavailable", cause: timedOut ? "timeout" : "error" };
87
- }
88
- finally {
89
- if (timer !== undefined)
90
- clearTimeout(timer);
91
- if (signal !== undefined)
92
- signal.removeEventListener("abort", onOuterAbort);
47
+ catch {
93
48
  }
94
- },
49
+ }
50
+ return verdict;
51
+ };
52
+ return {
53
+ breakerOpen: () => open,
54
+ consecutiveFailures: () => consecutiveFailures,
55
+ decide,
95
56
  };
57
+ async function decideUnreported(input, signal) {
58
+ if (open)
59
+ return { kind: "unavailable", cause: "breaker_open" };
60
+ let timer;
61
+ const inner = new AbortController();
62
+ const onOuterAbort = () => inner.abort();
63
+ if (signal !== undefined) {
64
+ if (signal.aborted)
65
+ inner.abort();
66
+ else
67
+ signal.addEventListener("abort", onOuterAbort, { once: true });
68
+ }
69
+ try {
70
+ const raw = await new Promise((resolve, reject) => {
71
+ let settled = false;
72
+ timer = setTimeout(() => {
73
+ if (!settled) {
74
+ settled = true;
75
+ inner.abort();
76
+ reject(new AutoModeTimeout());
77
+ }
78
+ }, timeoutMs);
79
+ opts.classify(input, inner.signal).then((v) => {
80
+ if (!settled) {
81
+ settled = true;
82
+ resolve(v);
83
+ }
84
+ }, (e) => {
85
+ if (!settled) {
86
+ settled = true;
87
+ reject(e);
88
+ }
89
+ });
90
+ });
91
+ if (open)
92
+ return { kind: "unavailable", cause: "breaker_open" };
93
+ const verdict = parseAutoModeResponse(raw);
94
+ if (verdict.kind === "parse_error")
95
+ recordFailure("parse_error");
96
+ else
97
+ consecutiveFailures = 0;
98
+ return verdict;
99
+ }
100
+ catch (e) {
101
+ if (signal?.aborted)
102
+ return { kind: "unavailable", cause: "error" };
103
+ const timedOut = e instanceof AutoModeTimeout;
104
+ recordFailure(timedOut ? "timeout" : "error");
105
+ return { kind: "unavailable", cause: timedOut ? "timeout" : "error" };
106
+ }
107
+ finally {
108
+ if (timer !== undefined)
109
+ clearTimeout(timer);
110
+ if (signal !== undefined)
111
+ signal.removeEventListener("abort", onOuterAbort);
112
+ }
113
+ }
96
114
  }
97
115
  class AutoModeTimeout extends Error {
98
116
  constructor() {
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The execution-outcome record's vocabulary (layer 0, beside the checkpoint store it serves): the closed
3
+ * answer set of `CheckpointStore.recordExecutionOutcome`, its one disposition table, the conflict error,
4
+ * the shared write disposition and reopen predicate every backend routes through, the structural
5
+ * equality the idempotency arm uses, and the one read face of the column. The column itself
6
+ * (`CheckpointRow.executionOutcome` / `executionAtMs`) and the verb's contract are declared on the store
7
+ * (`checkpoint-store.ts`); this module names no store type — the row facts it reads are spelled
8
+ * structurally — so the store reaches DOWN for it and nothing here reaches back.
9
+ */
10
+ import { type AssertAllKeysHandled } from "./ask-origin.js";
11
+ import type { GateOutcome } from "./gate-outcome.js";
12
+ /**
13
+ * The closed answer set of `CheckpointStore.recordExecutionOutcome` — what the store did with the
14
+ * execution record it was handed. One word per way the write can end; a caller keys on the word, never
15
+ * on an absence:
16
+ * - `recorded` — the CAS won: the row was `resolved` with no record yet, and now carries this one.
17
+ * - `already_recorded` — the row already carries a record EQUAL to the one supplied (a retry of the
18
+ * same write; idempotent, nothing changed). A record that DIFFERS is not a word — it is the
19
+ * {@link ExecutionOutcomeConflictError} throw.
20
+ * - `not_resolved` — the row exists in this scope but is not `resolved` (still pending, or expired):
21
+ * there is no decision an execution could be the outcome of, so nothing was written.
22
+ * - `absent` — no row under this token in this scope (a missing row and a wrong-scope row alike — the
23
+ * isolation rule every verb of the store keeps).
24
+ */
25
+ export declare const EXECUTION_OUTCOME_RECORD_WORDS: readonly ["recorded", "already_recorded", "not_resolved", "absent"];
26
+ export type ExecutionOutcomeRecordWord = (typeof EXECUTION_OUTCOME_RECORD_WORDS)[number];
27
+ /** Membership test for {@link ExecutionOutcomeRecordWord} — a deployment store answers across a process
28
+ * boundary, so the consumer validating its word must not hand-roll the set. */
29
+ export declare function isExecutionOutcomeRecordWord(v: unknown): v is ExecutionOutcomeRecordWord;
30
+ /**
31
+ * The one disposition table over {@link ExecutionOutcomeRecordWord}: does this answer mean the row now
32
+ * carries NO execution record — i.e. the leg's disposition of the decided action is not on file? The
33
+ * engine's belt reads it to decide whether to announce `checkpoint.execution_outcome_unrecorded` (the
34
+ * execution result itself is never changed by the answer: the record is an account of what ran, not a
35
+ * gate on it). A new word must take a row here (the fence below reds `tsc` otherwise).
36
+ */
37
+ export declare const EXECUTION_RECORD_LEAVES_ROW_UNRECORDED: {
38
+ readonly recorded: false;
39
+ readonly already_recorded: false;
40
+ readonly not_resolved: true;
41
+ readonly absent: true;
42
+ };
43
+ export type ExecutionRecordTableCoversEveryWord = AssertAllKeysHandled<Exclude<ExecutionOutcomeRecordWord, keyof typeof EXECUTION_RECORD_LEAVES_ROW_UNRECORDED>>;
44
+ /**
45
+ * The read face of `CheckpointRow.executionOutcome`: what a reader of a resolved row can say about how
46
+ * the decided action was disposed. `executed` carries the record the resumed leg filed; `unknown` is the
47
+ * ONE meaning of the column's absence (the leg has not reached its resolution yet, the row was consumed
48
+ * by a worker that could not record, or the store answered `not_resolved`/`absent` and the engine
49
+ * announced it). Never `allowed` by default — the pre-execution decision lives on `resolvedOutcome`.
50
+ */
51
+ export type ExecutionVerdict = {
52
+ kind: "executed";
53
+ gate: GateOutcome;
54
+ } | {
55
+ kind: "unknown";
56
+ };
57
+ /**
58
+ * `CheckpointStore.recordExecutionOutcome` was handed a record for a row that already carries a DIFFERENT
59
+ * one. The record is physically immutable (see `CheckpointRow.executionOutcome`), so this is never
60
+ * resolved by overwriting: two different accounts of one execution mean the engine ran, or believes it
61
+ * ran, the same decided action twice — an engine defect the belt must surface, not a store condition it
62
+ * may absorb. Carries both records so the operator can read what was on file and what tried to replace
63
+ * it. Code `checkpoint.execution_outcome_conflict`; a deployment store may throw any error carrying the
64
+ * same `code` — the code is the contract, this class is its in-tree form.
65
+ */
66
+ export declare class ExecutionOutcomeConflictError extends Error {
67
+ readonly code: "checkpoint.execution_outcome_conflict";
68
+ /** The record on file — a COPY: a backend hands this constructor its live row value, and a caller that
69
+ * catches the error must not be able to rewrite the row through it (the record is immutable by CAS,
70
+ * not by convention). */
71
+ readonly recorded: GateOutcome;
72
+ /** The record that tried to replace it — copied for the same reason. */
73
+ readonly attempted: GateOutcome;
74
+ constructor(recorded: GateOutcome, attempted: GateOutcome);
75
+ }
76
+ /** The row facts the record's disposition reads — spelled structurally so a backend can hand a partial
77
+ * read-back (a Pg row) as easily as a full `Checkpoint`. */
78
+ export interface ExecutionRecordRowFacts {
79
+ scope: string;
80
+ status: "pending" | "resolved" | "expired";
81
+ executionOutcome?: GateOutcome;
82
+ }
83
+ /** `true` iff the row carries an execution record — the reopen verbs' extra predicate (a settled action
84
+ * is not re-openable) and the record verb's own idempotency read. Pure; shared by every backend. */
85
+ export declare function checkpointExecutionRecorded(cp: Pick<ExecutionRecordRowFacts, "executionOutcome">): boolean;
86
+ /** Structural equality of two execution records as the JSON they round-trip as — key-order insensitive
87
+ * (a jsonb column reorders keys) and indifferent to freezing (the engine's record is minted frozen; a
88
+ * store's copy is not). A record is a plain data graph by construction, so nothing here needs the
89
+ * accessor/topology defences a caller-supplied value would. */
90
+ export declare function sameExecutionRecord(a: GateOutcome, b: GateOutcome): boolean;
91
+ /**
92
+ * THE one disposition of an execution-record write, shared by every backend so the CAS predicate
93
+ * `status = 'resolved' AND execution_outcome IS NULL` and its two idempotency/conflict arms cannot drift
94
+ * between stores (the same posture as the shared row predicate `checkpointRowMatches`). Pure: it decides
95
+ * what the write means for THIS row and this record; the backend keeps its own commit half (an in-place
96
+ * assignment, an appended ledger event, an `UPDATE … WHERE` that folds the same predicate into SQL).
97
+ * - `commit` — the row is `resolved` in this scope with no record: write it (the answer is `recorded`).
98
+ * - `already_recorded` / `not_resolved` / `absent` — the {@link ExecutionOutcomeRecordWord} to answer.
99
+ * - `conflict` — the row carries a different record: throw {@link ExecutionOutcomeConflictError}.
100
+ * Order is load-bearing: a record on file is judged BEFORE the status arm, so a retry against a row whose
101
+ * status a concurrent verb has since moved still answers by the record it carries.
102
+ */
103
+ export declare function executionRecordDisposition(cp: ExecutionRecordRowFacts | undefined, scope: string, gate: GateOutcome): "commit" | "conflict" | Exclude<ExecutionOutcomeRecordWord, "recorded">;
104
+ /**
105
+ * The ONE read of `CheckpointRow.executionOutcome`: `executed` with the record when the row carries one,
106
+ * `unknown` otherwise. A consumer rebuilding a run from its rows (an audit face, a supervisor inbox after
107
+ * a stream break) reads this rather than the column, so "no column" and "column not yet written" have
108
+ * exactly one spelling between them and no reader invents a default disposition.
109
+ */
110
+ export declare function executionVerdict(cp: Pick<ExecutionRecordRowFacts, "executionOutcome">): ExecutionVerdict;
@@ -0,0 +1,49 @@
1
+ import {} from "./ask-origin.js";
2
+ export const EXECUTION_OUTCOME_RECORD_WORDS = ["recorded", "already_recorded", "not_resolved", "absent"];
3
+ const EXECUTION_OUTCOME_RECORD_WORD_SET = new Set(EXECUTION_OUTCOME_RECORD_WORDS);
4
+ export function isExecutionOutcomeRecordWord(v) {
5
+ return EXECUTION_OUTCOME_RECORD_WORD_SET.has(v);
6
+ }
7
+ export const EXECUTION_RECORD_LEAVES_ROW_UNRECORDED = {
8
+ recorded: false,
9
+ already_recorded: false,
10
+ not_resolved: true,
11
+ absent: true,
12
+ };
13
+ export class ExecutionOutcomeConflictError extends Error {
14
+ code = "checkpoint.execution_outcome_conflict";
15
+ recorded;
16
+ attempted;
17
+ constructor(recorded, attempted) {
18
+ super("the checkpoint already carries an execution outcome that differs from the one being recorded — the record is immutable, so this is an engine defect (one decided action settled twice), not a retry");
19
+ this.name = "ExecutionOutcomeConflictError";
20
+ this.recorded = structuredClone(recorded);
21
+ this.attempted = structuredClone(attempted);
22
+ }
23
+ }
24
+ export function checkpointExecutionRecorded(cp) {
25
+ return cp.executionOutcome !== undefined;
26
+ }
27
+ export function sameExecutionRecord(a, b) {
28
+ return canonicalJson(a) === canonicalJson(b);
29
+ }
30
+ function canonicalJson(v) {
31
+ if (v === null || typeof v !== "object")
32
+ return JSON.stringify(v) ?? "undefined";
33
+ if (Array.isArray(v))
34
+ return `[${v.map(canonicalJson).join(",")}]`;
35
+ const keys = Object.keys(v).filter((k) => v[k] !== undefined).sort();
36
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(v[k])}`).join(",")}}`;
37
+ }
38
+ export function executionRecordDisposition(cp, scope, gate) {
39
+ if (cp === undefined || cp.scope !== scope)
40
+ return "absent";
41
+ if (cp.executionOutcome !== undefined)
42
+ return sameExecutionRecord(cp.executionOutcome, gate) ? "already_recorded" : "conflict";
43
+ if (cp.status !== "resolved")
44
+ return "not_resolved";
45
+ return "commit";
46
+ }
47
+ export function executionVerdict(cp) {
48
+ return cp.executionOutcome !== undefined ? { kind: "executed", gate: cp.executionOutcome } : { kind: "unknown" };
49
+ }
@@ -1,3 +1,5 @@
1
+ import type { GateOutcome } from "./gate-outcome.js";
2
+ import { type ExecutionOutcomeRecordWord } from "./checkpoint-execution-record.js";
1
3
  import type { CheckpointPause } from "./pause-registry.js";
2
4
  import type { SystemInjectionPriority } from "./task-notification.js";
3
5
  import { type QuestionAnswer } from "./ask-question.js";
@@ -820,6 +822,36 @@ export type PendingAction = {
820
822
  * pre-1a.2 checkpoint (deserialized without it), in which case the resume skips the hash check.
821
823
  */
822
824
  boundInputHash: string;
825
+ /**
826
+ * design/388 §2.5 boundary ② (B18; additive — no checkpoint-version bump, the `previewWithheld`
827
+ * precedent: an optional group an older reader ignores): the IDENTITY of the row the park minted
828
+ * this action against, read off the leg's tool roster at the suspend station — `capabilityId`
829
+ * (engine-minted over {source, peer, name, contractId}), `contractId`, `shapeDigest`,
830
+ * `implementationRevision` — plus the roster `digest` of the leg that parked. The resume finds the
831
+ * same-name row on ITS leg and compares the FOUR identity members (`judgeParkedToolIdentity`): any
832
+ * mismatch is a replacement wearing the name and the resume refuses it
833
+ * (`resume.tool_contract_mismatch`, the `tool_unavailable` reopen family). All four ABSENT ⇒ a row
834
+ * minted before the members existed ⇒ the name-only path, never widened by their absence.
835
+ */
836
+ capabilityId?: string;
837
+ contractId?: string;
838
+ shapeDigest?: string;
839
+ implementationRevision?: string;
840
+ rosterDigest?: string;
841
+ /**
842
+ * design/388 B6 × B18 (additive; the identity group's precedent): the FACE the parked call was judged under —
843
+ * display `family` + declared `pathTarget` — off the same roster row at the same mint. The pre-CAS resume-edit
844
+ * recheck has no roster yet; it rebuilds the call face from these (`parkedCallFaceOf`) and judges the edit as
845
+ * the live gate judged the original (merge-seam H1: without them a caller slot fell to the catalog's slot for
846
+ * the NAME). Any member absent ⇒ pre-388 row ⇒ the recheck falls to the catalog, never a widened grant.
847
+ */
848
+ family?: import("./tool-face.js").ToolFamily;
849
+ pathTarget?: {
850
+ param: string;
851
+ aliases: string[];
852
+ access: "read" | "create" | "edit";
853
+ skillScopeEligible: boolean;
854
+ };
823
855
  /**
824
856
  * #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
825
857
  * older reader ignores. Unlike that precedent seat — which resume DOES read, through its
@@ -1062,7 +1094,7 @@ export interface CheckpointState {
1062
1094
  activeWorktree?: ActiveWorktreeSession;
1063
1095
  /**
1064
1096
  * Parent effective-policy inheritance (tighten-only): the durable projection of the run's
1065
- * {@link import("./runner/prepare-task.js").InheritedGate}. Only the serializable DATA half persists —
1097
+ * {@link import("./runner/contracts.js").InheritedGate}. Only the serializable DATA half persists —
1066
1098
  * `ancestorRules` (each ancestor's session-rule snapshot: sessionId/principal/rev/rules, re-compiled on
1067
1099
  * resume against the resumed leg's env) and `shellGate` (the chain's max-rank shell doctrine). The OPAQUE
1068
1100
  * half (`parentConstraints` — live ToolPolicy/onAsk closures) CANNOT persist; `requiresParentConstraint`
@@ -1202,7 +1234,7 @@ export interface CheckpointState {
1202
1234
  /**
1203
1235
  * #281 件B follow-up — `true` iff the suspended leg ran as a DELEGATED CHILD (the trusted
1204
1236
  * `RunInternals.isDelegatedChild` fact, read off the leg's own minted
1205
- * {@link import("./runner/prepare-task.js").Prepared.hookIdentity} so a re-suspend chain carries it
1237
+ * {@link import("./runner/contracts.js").Prepared.hookIdentity} so a re-suspend chain carries it
1206
1238
  * forward). Without it a deps-only resume (`resume(token, outcome, config)` — no trusted internals
1207
1239
  * re-supplied) re-minted the resumed leg's identity with `isDelegatedChild: false`, so the
1208
1240
  * delegation-lifecycle observer's emission gate stayed shut and the resumed leg emitted NEITHER
@@ -1743,6 +1775,26 @@ export interface CheckpointRow {
1743
1775
  scope: string;
1744
1776
  ttlMs?: number;
1745
1777
  };
1778
+ /**
1779
+ * @contract checkpoint.execution_outcome — the settlement record of how the DECIDED action was
1780
+ * finally disposed on the resumed leg: the {@link GateOutcome} the resumed call's `tool_end` frame
1781
+ * carried (an executed allow, a person's refusal delivered, or a post-CAS belt's veto of a person's
1782
+ * yes), written by {@link CheckpointStore.recordExecutionOutcome} exactly once per row, on every
1783
+ * resolution (an `allowed` disposition is recorded too — the column's absence has ONE meaning:
1784
+ * the execution outcome is not known, never "it was allowed"). Independent of
1785
+ * {@link resolvedOutcome}, which stays the immutable record of the DECISION: a row can read
1786
+ * `resolvedOutcome.gateOutcome.disposition.kind === "allowed"` beside
1787
+ * `executionOutcome.disposition = { kind: "denied", deniedBy: "policy" }` — the person approved,
1788
+ * the deployment's belt refused the edited call — and a reader rebuilding the run from the row
1789
+ * sees both facts instead of the pre-execution one alone. The record is physically immutable:
1790
+ * a second write of a different record is a conflict, never an overwrite, and a row carrying one
1791
+ * cannot be {@link CheckpointStore.reopen | reopened} (the action is settled; a reopen would
1792
+ * re-run or re-decide work whose outcome is already on file). Read through
1793
+ * `executionVerdict` (checkpoint-execution-record.ts). Present together with {@link executionAtMs}.
1794
+ */
1795
+ executionOutcome?: GateOutcome;
1796
+ /** Epoch ms the store stamped {@link executionOutcome} at. Present iff `executionOutcome` is. */
1797
+ executionAtMs?: number;
1746
1798
  }
1747
1799
  /**
1748
1800
  * design/80 assistant-scheduler seam #1: a LIGHTWEIGHT projection of one PENDING
@@ -1967,6 +2019,10 @@ export declare class CheckpointError extends Error {
1967
2019
  * checkpoint exists. Surfaced instead of silently treating the failed reopen as committed, so the
1968
2020
  * caller's compensation path settles the token as terminally consumed. */
1969
2021
  | "checkpoint.reopen_failed"
2022
+ /** {@link CheckpointStore.recordExecutionOutcome} met a row that already carries a DIFFERENT
2023
+ * execution record — see `ExecutionOutcomeConflictError` (checkpoint-execution-record.ts), the
2024
+ * in-tree form of this code. Never a retry condition. */
2025
+ | "checkpoint.execution_outcome_conflict"
1970
2026
  /** design/80 D-A: {@link CheckpointStore.setPendingSteer} was handed `text` containing a
1971
2027
  * `</system-reminder>` close tag — it would escape the model-facing `<system-reminder>` wrapper a
1972
2028
  * trusted steer rides on resume. Rejected fail-closed so a dirty steer NEVER enters {@link CheckpointState}
@@ -2151,6 +2207,10 @@ export declare class CheckpointError extends Error {
2151
2207
  * checkpoint exists. Surfaced instead of silently treating the failed reopen as committed, so the
2152
2208
  * caller's compensation path settles the token as terminally consumed. */
2153
2209
  | "checkpoint.reopen_failed"
2210
+ /** {@link CheckpointStore.recordExecutionOutcome} met a row that already carries a DIFFERENT
2211
+ * execution record — see `ExecutionOutcomeConflictError` (checkpoint-execution-record.ts), the
2212
+ * in-tree form of this code. Never a retry condition. */
2213
+ | "checkpoint.execution_outcome_conflict"
2154
2214
  /** design/80 D-A: {@link CheckpointStore.setPendingSteer} was handed `text` containing a
2155
2215
  * `</system-reminder>` close tag — it would escape the model-facing `<system-reminder>` wrapper a
2156
2216
  * trusted steer rides on resume. Rejected fail-closed so a dirty steer NEVER enters {@link CheckpointState}
@@ -2456,6 +2516,20 @@ export interface CheckpointStore {
2456
2516
  reopen: boolean;
2457
2517
  validatingLease?: boolean;
2458
2518
  };
2519
+ /**
2520
+ * The EXECUTION-OUTCOME declaration — REQUIRED, and required to be exactly `{ outcome: true }`: the
2521
+ * store promises that {@link recordExecutionOutcome} is the real CAS this contract describes (the
2522
+ * predicate, the four words, the conflict throw) and that {@link reopen} honours the record. Same
2523
+ * "declaration, never duck-typing" posture as the three optional axes above, but not optional: the
2524
+ * durable resume lane records how every decided action was disposed, and a store that cannot keep
2525
+ * that record would leave every resolved row it holds silently unreadable on that axis. A store
2526
+ * without the declaration is refused at the FIRST read of the store seat
2527
+ * ({@link resolveCheckpointStore}, code `config.invalid_checkpoint_store`) — a JavaScript caller that
2528
+ * satisfies no type is told at prepare what the type would have told it.
2529
+ */
2530
+ readonly execution: {
2531
+ readonly outcome: true;
2532
+ };
2459
2533
  /** Create-once. Throws {@link CheckpointError} `already_exists` on a token collision (never a silent
2460
2534
  * overwrite — a reused token would clobber a live suspension). */
2461
2535
  put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
@@ -2505,8 +2579,35 @@ export interface CheckpointStore {
2505
2579
  * `tool_unavailable` (P-7: the approved tool vanished, the action may now be invalid → a human re-decides,
2506
2580
  * so a FRESH decision is allowed). The store RECORDS the reason on the row as {@link Checkpoint.reopenReason}
2507
2581
  * and PRESERVES the persisted winner across the reopen (never clears it).
2582
+ *
2583
+ * **A row that carries an execution record is NOT reopenable** (`… AND execution_outcome IS NULL` in the
2584
+ * WHERE; `checkpointExecutionRecorded` is the shared predicate): the decided action's disposition is on
2585
+ * file — see {@link CheckpointRow.executionOutcome} — and reopening it would either re-run an action that
2586
+ * already ran or re-decide one already refused. Answers `false` like every other predicate miss; the
2587
+ * reasons and the winner are untouched.
2508
2588
  */
2509
2589
  reopen?(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
2590
+ /**
2591
+ * Record how the DECIDED action was finally disposed on the resumed leg — the {@link GateOutcome} its
2592
+ * `tool_end` frame carried (see {@link CheckpointRow.executionOutcome} for what the record means and why
2593
+ * it is a column of its own beside the immutable decision). REQUIRED (declared by {@link execution}).
2594
+ *
2595
+ * The CAS: `UPDATE … SET execution_outcome=?, execution_at_ms=? WHERE token=? AND scope=? AND
2596
+ * status='resolved' AND execution_outcome IS NULL`. The answer is a word of the closed
2597
+ * `ExecutionOutcomeRecordWord` set — `recorded` (the CAS won), `already_recorded` (the row already
2598
+ * carries an EQUAL record: a retry, idempotent), `not_resolved` (pending/expired: no decision to be the
2599
+ * outcome of), `absent` (no such row in this scope). A row carrying a DIFFERENT record throws
2600
+ * `ExecutionOutcomeConflictError` (`checkpoint.execution_outcome_conflict`) — the record is immutable
2601
+ * and two accounts of one execution are an engine defect, never a store overwrite. Equality is
2602
+ * structural over the record's JSON (key order and freezing are not part of the value —
2603
+ * `sameExecutionRecord`); every backend takes its disposition from `executionRecordDisposition`
2604
+ * (checkpoint-execution-record.ts) and keeps only the commit half itself.
2605
+ *
2606
+ * Never touches `status`, `rev`, `resolvedOutcome` or `reopenReason`: the record is a fact about the
2607
+ * execution, not a transition of the decision. Its one effect on the row's other verbs is that
2608
+ * {@link reopen} refuses a recorded row.
2609
+ */
2610
+ recordExecutionOutcome(token: CheckpointToken, scope: string, gate: GateOutcome): Promise<ExecutionOutcomeRecordWord>;
2510
2611
  /**
2511
2612
  * design/80 D-A: park a durable mid-task STEER on a DURABLY-SUSPENDED task's checkpoint. CAS-style update
2512
2613
  * `SET state.pendingSteerQueue=? WHERE token=? AND scope=? AND status='pending'`: returns `true` iff the
@@ -2692,6 +2793,12 @@ export declare function resolveCheckpointStore(spec: {
2692
2793
  }, deps: {
2693
2794
  checkpointStore?: CheckpointStore;
2694
2795
  }): CheckpointStore | undefined;
2796
+ /** The ONE refusal of a store seat whose {@link CheckpointStore} does not declare `execution = { outcome: true }`
2797
+ * (named by seat and by the store's constructor when it has one). Same door, same code, as the retired-`null`
2798
+ * refusal above: a configuration the run cannot honour is refused at the first read, never carried. */
2799
+ export declare function checkpointStoreExecutionUndeclared(seat: "TaskSpec.checkpointStore" | "RunnerDeps.checkpointStore", store: object): Error & {
2800
+ code: "config.invalid_checkpoint_store";
2801
+ };
2695
2802
  /** The ONE refusal of the retired `checkpointStore: null` spelling (the resolver and the workflow baseline overlay both throw it). */
2696
2803
  export declare function retiredCheckpointStoreNull(): Error & {
2697
2804
  code: "config.invalid_checkpoint_store";
@@ -2803,6 +2910,10 @@ export declare class InMemoryCheckpointStore implements CheckpointStore {
2803
2910
  readonly redecision: {
2804
2911
  readonly reopen: true;
2805
2912
  };
2913
+ /** The execution-outcome record verb below is the contract's CAS, and `reopen` honours the record. */
2914
+ readonly execution: {
2915
+ readonly outcome: true;
2916
+ };
2806
2917
  private cps;
2807
2918
  private fault;
2808
2919
  put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
@@ -2855,6 +2966,7 @@ export declare class InMemoryCheckpointStore implements CheckpointStore {
2855
2966
  * atomic unit this method exists to provide. (A wrapper that OMITS the member opts out
2856
2967
  * cleanly: the consumer's presence probe then keeps the two-step path, hooks intact.) */
2857
2968
  claimTerminal(token: CheckpointToken, scope: string, intent: TerminalClaimIntent): Promise<TerminalClaimOutcome>;
2969
+ recordExecutionOutcome(token: CheckpointToken, scope: string, gate: GateOutcome): Promise<ExecutionOutcomeRecordWord>;
2858
2970
  reopen(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
2859
2971
  setPendingSteer(token: CheckpointToken, scope: string, steer: PendingSteerInput): Promise<boolean>;
2860
2972
  expire(token: CheckpointToken, scope: string): Promise<boolean>;
@@ -4,6 +4,7 @@ import { PROBE_CAUSE_PATH_MAX, inlineUntrusted, sanitizeUntrustedText } from "./
4
4
  import { carriesBidiControls } from "./tool-policy.js";
5
5
  import { readDenialLimitFallback } from "./auto-mode.js";
6
6
  import { isAskOrigin } from "./ask-origin.js";
7
+ import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
7
8
  import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
8
9
  import { redactSecrets } from "./untrusted-egress.js";
9
10
  import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
@@ -367,7 +368,26 @@ export function resolveCheckpointStore(spec, deps) {
367
368
  throw retiredCheckpointStoreNull();
368
369
  if (spec.checkpointStore === "disabled")
369
370
  return undefined;
370
- return spec.checkpointStore ?? deps.checkpointStore;
371
+ if (spec.checkpointStore !== undefined) {
372
+ if (spec.checkpointStore.execution?.outcome !== true)
373
+ throw checkpointStoreExecutionUndeclared("TaskSpec.checkpointStore", spec.checkpointStore);
374
+ return spec.checkpointStore;
375
+ }
376
+ if (deps.checkpointStore !== undefined && deps.checkpointStore.execution?.outcome !== true) {
377
+ throw checkpointStoreExecutionUndeclared("RunnerDeps.checkpointStore", deps.checkpointStore);
378
+ }
379
+ return deps.checkpointStore;
380
+ }
381
+ export function checkpointStoreExecutionUndeclared(seat, store) {
382
+ const ctor = store.constructor?.name;
383
+ const name = typeof ctor === "string" && ctor.length > 0 && ctor !== "Object" ? ` (${ctor})` : "";
384
+ const e = new Error(`${seat}: the wired CheckpointStore${name} does not declare \`execution = { outcome: true }\` — the execution-outcome ` +
385
+ "record (recordExecutionOutcome, and reopen refusing a recorded row) is a required member of the store contract; " +
386
+ "a store without it cannot record how a resumed approval was disposed, so the run is refused here rather than " +
387
+ "resolving rows no reader can settle. Implement the verb and add the declaration (the in-tree stores and the " +
388
+ "store-contract kit are the reference).");
389
+ e.code = "config.invalid_checkpoint_store";
390
+ return e;
371
391
  }
372
392
  export function retiredCheckpointStoreNull() {
373
393
  const e = new Error('TaskSpec.checkpointStore: null is not a value any more — write checkpointStore: "disabled" to run without a ' +
@@ -540,6 +560,7 @@ export class InMemoryCheckpointStore {
540
560
  durability = "process-local";
541
561
  fidelity = "structured-clone";
542
562
  redecision = { reopen: true };
563
+ execution = { outcome: true };
543
564
  cps = new Map();
544
565
  fault = null;
545
566
  async put(token, cp) {
@@ -618,9 +639,21 @@ export class InMemoryCheckpointStore {
618
639
  }
619
640
  return out;
620
641
  }
642
+ async recordExecutionOutcome(token, scope, gate) {
643
+ const cp = this.cps.get(token);
644
+ const disposition = executionRecordDisposition(cp, scope, gate);
645
+ if (disposition === "conflict")
646
+ throw new ExecutionOutcomeConflictError(cp.executionOutcome, gate);
647
+ if (disposition !== "commit")
648
+ return disposition;
649
+ const record = structuredClone(gate);
650
+ cp.executionOutcome = record;
651
+ cp.executionAtMs = Date.now();
652
+ return "recorded";
653
+ }
621
654
  async reopen(token, scope, reason) {
622
655
  const cp = this.cps.get(token);
623
- if (!checkpointRowMatches(cp, scope, "resolved")) {
656
+ if (!checkpointRowMatches(cp, scope, "resolved") || checkpointExecutionRecorded(cp)) {
624
657
  return false;
625
658
  }
626
659
  cp.status = "pending";
@@ -130,28 +130,6 @@ export declare const MIN_CLEAR_SAVINGS_TOKENS = 20000;
130
130
  /** Minimum kept results — CC `EUs` :397710 `Math.max(1, keepRecent)`. See
131
131
  * {@link ContextEditOptions.keepRecentToolResults} for why both degenerate ends are unusable. */
132
132
  export declare const MIN_KEEP_RECENT_TOOL_RESULTS = 1;
133
- /**
134
- * checklist #54 (CC microCompact.ts:41-50 `COMPACTABLE_TOOLS` parity): the INCLUSION whitelist of
135
- * tools whose results may be content-cleared by the stale-result pass. CC's set is exactly
136
- * { Read, Bash, PowerShell (SHELL_TOOL_NAMES), Grep, Glob, WebSearch, WebFetch, Edit, Write } —
137
- * cheap-to-regenerate I/O.
138
- *
139
- * RE-VERIFIED against the 2.1.250 corpus (design/374 slice 4, member-by-member — a set is exactly
140
- * the shape where "the count still matches" is not evidence): `eQt = new Set([wt, ...VD, Yo, Xo,
141
- * yO, Yr, tn, er])` at cli250.js:41714, whose aliases resolve to `wt`="Read", `VD`=[`Xe`="Bash",
142
- * `Nt`="PowerShell"], `Yo`="Grep", `Xo`="Glob", `yO`="WebSearch", `Yr`="WebFetch", `tn`="Edit",
143
- * `er`="Write" — nine members, byte-identical to the set below, zero drift from the 223 reading.
144
- * The exclusion side is the load-bearing half and is unchanged too: everything NOT here is
145
- * PRESERVED (Agent/Task/Skill/ToolSearch/TodoWrite/Monitor/SendMessage/NotebookEdit and ALL MCP
146
- * tools are never content-cleared by this pass, upstream or here).
147
- *
148
- * Everything else is PRESERVED: expensive/decision-bearing results
149
- * (Agent subagent reports, Task* registry state, TodoWrite acks, Monitor, MCP tools, custom
150
- * ToolSpecs) survive to the harder defenses (compaction summarizes them instead of blanking them).
151
- * PowerShell is kept for CC fidelity even though sema does not mount it. Overridable per call via
152
- * {@link ContextEditOptions.compactableTools} for custom-tool-heavy embedders.
153
- */
154
- export declare const COMPACTABLE_TOOLS: ReadonlySet<string>;
155
133
  export interface ContextEditOptions {
156
134
  /** Start clearing once estimated context tokens exceed this. */
157
135
  budgetTokens: number;
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { DEFAULT_CHARS_PER_TOKEN, estimateContextTokens, estimateTokens } from "../internal/harness.js";
3
3
  import { isToolResult } from "./message-utils.js";
4
4
  import { PERSISTED_OUTPUT_PREFIX, offloadPagebackHint } from "./tool-result-store.js";
5
+ import { compactableToolNames } from "./tool-registry.js";
5
6
  const CLEARED_MARKER = "[tool result cleared to save context]";
6
7
  const CC_CLEARED_MARKER = "[Old tool result content cleared]";
7
8
  const LEGACY_CLEARED_EXTENDED_PREFIX = "[tool result cleared to save context —";
@@ -46,17 +47,7 @@ function resolveKeepRecentToolResults(value, defaultValue) {
46
47
  }
47
48
  return Math.max(MIN_KEEP_RECENT_TOOL_RESULTS, value);
48
49
  }
49
- export const COMPACTABLE_TOOLS = new Set([
50
- "Read",
51
- "Bash",
52
- "PowerShell",
53
- "Grep",
54
- "Glob",
55
- "WebSearch",
56
- "WebFetch",
57
- "Edit",
58
- "Write",
59
- ]);
50
+ const COMPACTABLE_TOOLS = compactableToolNames();
60
51
  function soleMarkerText(m) {
61
52
  const content = m.content;
62
53
  if (!Array.isArray(content) || content.length !== 1)