@sema-agent/core 7.7.0 → 7.9.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.
- package/CHANGELOG.md +88 -0
- package/dist/agents/agent-transcript-tool.js +2 -2
- package/dist/agents/list-agents-tool.js +2 -3
- package/dist/agents/observer.js +2 -2
- package/dist/agents/send-message-tool.js +2 -1
- package/dist/agents/subagent-steps.js +5 -4
- package/dist/agents/subagent.d.ts +1 -1
- package/dist/agents/subagent.js +2 -3
- package/dist/core/ask-origin.d.ts +12 -1
- package/dist/core/ask-origin.js +5 -1
- package/dist/core/ask-question.js +2 -2
- package/dist/core/checkpoint-store.d.ts +64 -24
- package/dist/core/context-edit.d.ts +0 -22
- package/dist/core/context-edit.js +2 -11
- package/dist/core/engine-notice.d.ts +6 -0
- package/dist/core/fs-write-gate-policy.d.ts +7 -1
- package/dist/core/fs-write-gate-policy.js +7 -7
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +53 -26
- package/dist/core/gate-outcome.d.ts +8 -4
- package/dist/core/gate-outcome.js +2 -1
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +64 -33
- package/dist/core/hooks.js +1 -1
- package/dist/core/lsp.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +17 -0
- package/dist/core/mcp.js +3 -1
- package/dist/core/memory-engine/tools.js +4 -12
- package/dist/core/permission-rule-consent.d.ts +29 -36
- package/dist/core/permission-rule-consent.js +110 -60
- package/dist/core/permission-rule-model.d.ts +254 -55
- package/dist/core/permission-rule-model.js +323 -43
- package/dist/core/permission-rule-org.d.ts +9 -8
- package/dist/core/permission-rule-org.js +6 -15
- package/dist/core/permission-rule-provider.d.ts +10 -6
- package/dist/core/permission-rule-provider.js +12 -8
- package/dist/core/permission-rule-session.d.ts +7 -6
- package/dist/core/permission-rule-session.js +29 -10
- package/dist/core/permission-rule-store.d.ts +40 -18
- package/dist/core/permission-rule-store.js +68 -42
- package/dist/core/permission-rule-sync.d.ts +9 -3
- package/dist/core/permission-rule-sync.js +29 -25
- package/dist/core/permission-rule-syntax.d.ts +30 -0
- package/dist/core/permission-rule-syntax.js +44 -0
- package/dist/core/permission-rules.d.ts +85 -33
- package/dist/core/permission-rules.js +104 -88
- package/dist/core/persisted-rule-arms.d.ts +56 -0
- package/dist/core/persisted-rule-arms.js +48 -0
- package/dist/core/present-plan-tool.js +3 -4
- package/dist/core/runner/active-skill-scope.js +7 -4
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/contracts.d.ts +46 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +33 -26
- package/dist/core/runner/permission-rule-lanes.js +27 -21
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +16 -11
- package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
- package/dist/core/runner/prepare-defer-classify.js +10 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
- package/dist/core/runner/prepare-delegation-surface.js +15 -17
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
- package/dist/core/runner/prepare-gate-stations.js +4 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-lsp.d.ts +4 -3
- package/dist/core/runner/prepare-lsp.js +3 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
- package/dist/core/runner/prepare-memory-engine-session.js +4 -2
- package/dist/core/runner/prepare-offload-wrappers.js +7 -5
- package/dist/core/runner/prepare-park-ask.d.ts +4 -0
- package/dist/core/runner/prepare-park-ask.js +4 -1
- package/dist/core/runner/prepare-project-context.d.ts +5 -23
- package/dist/core/runner/prepare-project-context.js +7 -5
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
- package/dist/core/runner/prepare-protocol-tools.js +38 -21
- package/dist/core/runner/prepare-question-face.d.ts +4 -3
- package/dist/core/runner/prepare-question-face.js +5 -3
- package/dist/core/runner/prepare-run-refs.d.ts +16 -0
- package/dist/core/runner/prepare-run-refs.js +6 -0
- package/dist/core/runner/prepare-safety-scan.js +48 -2
- package/dist/core/runner/prepare-task.js +37 -19
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
- package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
- package/dist/core/runner/prepare-wiring-manifest.js +3 -8
- package/dist/core/runner/resume-admission.d.ts +53 -0
- package/dist/core/runner/resume-admission.js +83 -0
- package/dist/core/runner/resume-apply.d.ts +50 -0
- package/dist/core/runner/resume-apply.js +184 -0
- package/dist/core/runner/resume-checkpoint-screen.d.ts +18 -0
- package/dist/core/runner/resume-checkpoint-screen.js +108 -0
- package/dist/core/runner/resume-claim.d.ts +32 -0
- package/dist/core/runner/resume-claim.js +27 -0
- package/dist/core/runner/resume-internals-and-config.d.ts +33 -0
- package/dist/core/runner/resume-internals-and-config.js +50 -0
- package/dist/core/runner/resume-policy-outcome.d.ts +31 -0
- package/dist/core/runner/resume-policy-outcome.js +127 -0
- package/dist/core/runner/resume-preflight.d.ts +40 -0
- package/dist/core/runner/resume-preflight.js +122 -0
- package/dist/core/runner/resume-review-outcome.d.ts +30 -0
- package/dist/core/runner/resume-review-outcome.js +88 -0
- package/dist/core/runner/run-harness-handlers.d.ts +46 -0
- package/dist/core/runner/run-harness-handlers.js +345 -0
- package/dist/core/runner/run-turn-boundary.d.ts +69 -0
- package/dist/core/runner/run-turn-boundary.js +694 -0
- package/dist/core/runner/runtask.d.ts +20 -0
- package/dist/core/runner/runtask.js +129 -1774
- package/dist/core/runner/session-rule-policy.js +9 -4
- package/dist/core/runner/synthetic-tools.js +5 -8
- package/dist/core/runner/tool-disclosure.d.ts +4 -0
- package/dist/core/runner/tool-disclosure.js +9 -5
- package/dist/core/runner/tool-end-body.d.ts +12 -5
- package/dist/core/runner/tool-end-body.js +8 -7
- package/dist/core/runner/tool-face-overlay.d.ts +40 -0
- package/dist/core/runner/tool-face-overlay.js +125 -0
- package/dist/core/runner/tool-output-projection.d.ts +14 -1
- package/dist/core/runner/tool-output-projection.js +24 -18
- package/dist/core/runner/turn-attachments.d.ts +2 -2
- package/dist/core/sensitive-path-policy.d.ts +3 -0
- package/dist/core/sensitive-path-policy.js +6 -5
- package/dist/core/shared-memory/tools.js +3 -6
- package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +29 -10
- package/dist/core/task-event.d.ts +10 -0
- package/dist/core/task-registry.js +4 -5
- package/dist/core/task-spec.d.ts +8 -0
- package/dist/core/tool-catalog-entries.d.ts +48 -0
- package/dist/core/tool-catalog-entries.js +174 -0
- package/dist/core/tool-conformance.d.ts +22 -0
- package/dist/core/tool-conformance.js +37 -0
- package/dist/core/tool-face.d.ts +171 -0
- package/dist/core/tool-face.js +25 -0
- package/dist/core/tool-policy.d.ts +24 -0
- package/dist/core/tool-policy.js +4 -2
- package/dist/core/tool-registry.d.ts +114 -0
- package/dist/core/tool-registry.js +114 -0
- package/dist/core/tool-result-budget.d.ts +0 -3
- package/dist/core/tool-result-budget.js +4 -3
- package/dist/core/tool-result-store.d.ts +0 -18
- package/dist/core/tool-result-store.js +2 -18
- package/dist/core/tool-roster.d.ts +459 -0
- package/dist/core/tool-roster.js +507 -0
- package/dist/core/tool-spec.d.ts +54 -3
- package/dist/core/tools.js +7 -0
- package/dist/core/wiring-manifest.d.ts +13 -0
- package/dist/core/wiring-manifest.js +2 -1
- package/dist/core/write-protect.d.ts +2 -1
- package/dist/core/write-protect.js +5 -4
- package/dist/engine/compaction/utils.d.ts +6 -0
- package/dist/engine/compaction/utils.js +19 -19
- package/dist/engine/loop/types.d.ts +6 -0
- package/dist/index.d.ts +17 -6
- package/dist/index.js +17 -6
- package/dist/orchestration/goal.js +2 -1
- package/dist/orchestration/run-spec.js +3 -5
- package/dist/orchestration/run-workflow-tool.js +2 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
- package/dist/stores/file/checkpoint-store.js +1 -1
- package/dist/stores/file/permission-rule-store.d.ts +28 -23
- package/dist/stores/file/permission-rule-store.js +64 -16
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +7 -11
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +2 -2
- package/dist/tools/fs/fs-search-tools.d.ts +0 -7
- package/dist/tools/fs/fs-search-tools.js +3 -16
- package/dist/tools/fs/fs-write.js +4 -6
- package/dist/tools/fs/notebook.d.ts +1 -1
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/safety.d.ts +3 -2
- package/dist/tools/fs/safety.js +6 -5
- package/dist/tools/monitor.js +2 -2
- package/dist/tools/scheduler-tools.js +5 -8
- package/dist/tools/task-list.js +5 -8
- package/dist/tools/todo.js +2 -2
- package/dist/tools/web.js +3 -6
- package/dist/tools/worktree.js +3 -4
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +189 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.9.0 — 2026-09-08
|
|
4
|
+
|
|
5
|
+
The resume decision ladder leaves runtask.ts, and the two `runtask.ts` seams design/388 slice 1 shipped unwired in 7.8.0 are wired.
|
|
6
|
+
|
|
7
|
+
### BREAKING — content-form deny/ask rules get their engine lane (#625; cli B-043 root cause)
|
|
8
|
+
- **One three-state rule model.** A persisted rule row carries `behavior: "deny" | "ask" | "allow"` (closed set `RULE_BEHAVIORS`, one precedence table `RULE_BEHAVIOR_PRECEDENCE`: deny > ask > allow, read by every adjudication). Identity is the (behavior, text, scope) triple. Renames: `PersistedAllowRule`→`PersistedRule`, `ParsedAllowRule`→`ParsedRule`, `StoredAllowRules`→`StoredRules`; `parseAllowRuleText`→`parseRuleText(text, behavior)` (no default), `formatAllowRuleText`→`formatRuleText`. Removed exports: those five names. Same-name reshapes: `PersistedRuleHit.behavior`; `ToolGateInput.persistedRules.admits`→`adjudicate` (the answer is a verdict; a read failure MUST answer `{ unreadable: true }`); `RuleTombstone` / `RuleAddDelta` / `QuarantinedRuleAdd` / `RuleSyncDrop` / `RuleSyncJoinDelta.quarantine[]` / `SessionRuleAdd` / `RemovedPermissionRule` / `RuleCandidate` / `RedeemResult` / `RedeemedBatchMember` carry `behavior`; `removePersistedRule({ behavior })`; `RuleApprovalRecord.schema` 3→4; `ImportPreview.uncovered.denyAskBuckets` removed; `PermissionRulePolicyOptions.bashPrefixLane` removed, `pathBases` added; `PersistedRuleTool` is `string` (tools derive from the catalog's path targets, `ruleToolGrammarOf`); `PersistedRuleMatch` +`path`; `directoryRuleAdmits` no longer reads the tool.
|
|
9
|
+
- **Store rows (@server two stores + contract kit):** every row / tombstone / quarantine / dropped entry carries `behavior` (required on the wire: a row without it is not shapeable and is skipped with a warning; a tombstone without it withholds this round's add). File store schema 3→4 (`.rule-schema-4`; a schema-3 bucket migrates once with `behavior: "allow"` on every row and tombstone; a behavior word outside the set is left for a person, refused whole). Sync contract kit +3 cases. `RuleSyncResponseBody.dropped[].behavior` required; `PermissionRuleSyncResult.resurrected[].behavior`.
|
|
10
|
+
- **Closed sets (@cli @client-core @server readers):** `DeniedBy` +`persisted_rule` (a person's own standing deny row); `AskOrigin` +`rule_store_unavailable` (a wired persisted-rule store could not be read: the ask is fail-closed, the classifier does not answer it, real-approval derived); `PermissionRuleIssue.code` −`unsupported.bash_prefix_rules_store` +`invalid.command_rule` / `unsupported.path_base`.
|
|
11
|
+
- **The lane.** The persisted-rule lane reads the store once per decision (not only on a surviving ask) and adjudicates: a deny row refuses outright (`deniedBy: "persisted_rule"`, a veto row in the station table); an ask row asks with `matchedAskRule` (origin `ask_rule`, a person's standing "ask me each time" that the classifier never answers, by design); an allow row is the prior predicate. The edit round re-adjudicates. Path family: `Edit(~/.sema/settings.json)` / `Write(//tmp/secret/**)` deny/ask rules judge the call's path target (deny/ask read the protective slot, allow the declared slot: the 7.8.0 two-reader rule); pattern grammar `//abs`, `~/`, `/root-relative`, cwd-relative, `*` within a segment, `**` across (narrower than CC's gitignore semantics: `?` / `[]` / `{}` / `!` refused, disclosed). Precedence across authorities: org deny > org ask > personal deny > personal ask > personal allow > bare ask.
|
|
12
|
+
- **The compiler** (`createPermissionRulePolicy`) no longer refuses a Bash prefix deny/ask (`Bash(rm -r:*)` in `permissions.deny` / `permissions.ask` compiles; the retired re-coding never appears, tripwire pinned); an allow in content form still refuses `unsupported.bash_prefix`. Redirections are stripped on the tighten side before matching (`> file`, `2>`, `>|`, fd duplications; the remaining forms are #631).
|
|
13
|
+
- **@cli:** the shell's escort of content-form ask/deny rules into bare tool-name asks (B-043's amplification) retires whole; `permissions.deny` / `permissions.ask` content rules go up the wire as written. Judgement: `permissions.ask: ["Bash(rm -r:*)"]` + auto ⇒ `date` zero cards and zero classifier calls, `rm -r x` a real card of origin `ask_rule`; deny likewise.
|
|
14
|
+
|
|
15
|
+
### Fixes over the merged tree (main-tree adversarial round over #625 × 393 S3)
|
|
16
|
+
- **#632 — a deny row imported while a call was PARKED now vetoes the resumed execution.** The resumed execution boundary re-adjudicates the person's persisted-rule lane in the same posture as the deny-narrowing layers: a matching deny row (or an ask row, or an unreadable store — the durable path cannot re-ask) ends the call with `deniedBy: "persisted_rule"`, the approval is spent, the call is re-issued. `Prepared.persistedRuleLane` (internal seat) carries the lane to the driver. Pin: park → import deny → resume ⇒ zero executions; control without the row executes once.
|
|
17
|
+
- **Schema-4 rule buckets are strict on every row's `behavior`.** A correctly checksummed schema-4 file carrying a rule, tombstone or quarantined row whose behavior word is outside deny/ask/allow is refused WHOLE (`list()` rejects, the lane reads `unreadable` ⇒ the ask carries `rule_store_unavailable`), never read as "no rule" beside a live allow row.
|
|
18
|
+
- **Known limits, disclosed (tickets, not this release):** #631 — the tighten-side command reader is a hand-written stripper: `command rm -r x`, `rm "-r" x`, `rm -r x; echo "$HOME"` (expansion makes segmentation fail) do not reach a `Bash(rm -r:*)` deny/ask; direction = a real shell lexer with wrappers, and an unreadable command asks instead of no-match. #635 — the path lane reads the call's path slot, not the search tools' effective target: `Grep(//proj/**)` with the path omitted (tool default = root), `Glob` with an absolute pattern, and a relative Grep path resolved against liveCwd where the tool resolves against root, are not reached; direction = one effective-target resolver shared with tool execution. Both are under-blocking relative to the rule's intent, not wider than 7.8.0 (which had no engine-side deny at all).
|
|
19
|
+
|
|
20
|
+
### Wire (additive — new frames start being pushed; the vocabulary shipped in 7.8.0)
|
|
21
|
+
- `tool_roster_delta` frames are now PUSHED. The driver subscribes to the leg's roster-delta seat at the point it pushes `wiring_manifest` (the roster on the manifest is the baseline a delta diffs against; a delta the seat buffered before the subscription flushes first, in publish order) and forwards each run-time roster change (the RefreshMcpTools seam) as its own identity-minted frame: `delta.fromDigest` = the digest the consumer holds, `delta.roster` the whole post-change roster, `delta.summary` the added/removed/changed names. One refresh that changes the roster ⇒ exactly one frame; a refresh that changes nothing publishes none. A consumer that adopted 7.8.0's tolerant default (adopt the carried roster, record a skew when `fromDigest` mismatches) needs nothing new. @server (live projection of the roster: the frame is on the wire now), @cli (the two frames `wiring_manifest.manifest.tools` / `tool_roster_delta` parse as already claimed).
|
|
22
|
+
- `tool_end.structured` is projected through the LEG's card projector (`Prepared.structuredProjector`: the engine card set ∪ the caller rows' declared `renderHints.resultCards`) on every arm that mints a tool_end body — the live loop, the resumed batch's executed and refused arms, the deferred-sibling close, the two orphan reconciles. Effect on the wire: a caller tool's DECLARED card now rides its frame (7.8.0 dropped it — only engine cards passed); an undeclared `details.type` is still dropped and is now announced ONCE per leg as `config.tool_card_undeclared` (audience operator), which 7.8.0 minted at the seat but never reached from the loop.
|
|
23
|
+
- 7.8.0's "Not wired" disclosure retires whole: the roster delta frame, the structured projector and the manifest-side subscription are wired; the `resume` identity arm was already wired in 7.8.0.
|
|
24
|
+
|
|
25
|
+
### Narrowing
|
|
26
|
+
- **design/388 §2.2 ④ — a caller's `renderHints.resultCards` must live under the tool's OWN card namespace** (the row's `cardId`: `caller:<contractId>` — one mint, `rosterCardId`, read by the row and by the prepare door). A declaration naming another contract's namespace, an engine card (`builtin:*` or a bare engine card type) or a bare name is refused at prepare, `config.tool_face_invalid`. Load-bearing now that the leg projector admits every DECLARED card onto the wire (a 7.8.0 tree accepted the declaration and dropped the card at projection; the S3 tree would have shipped it as a foreign card). @cli @server: a caller tool that declared a foreign card namespace now fails prepare instead of mounting.
|
|
27
|
+
|
|
28
|
+
### Internal (byte-invariant on the runtime — design/393 S3)
|
|
29
|
+
- `Runner.resumeStream`'s pre-CAS ladder (E1–E10 of the decomposition map, 1 918 lines) moved verbatim into eight `resume-*.ts` lanes (layer 3), each behind ONE Input: admission (+ wake shaping), review-outcome (+ gate match, park twins), policy-outcome, checkpoint-screen (+ abort / handle / constraint rungs), internals-and-config, apply (+ settlement mint), preflight, claim. E11 (the post-claim hand-off) stays the driver's. The four async rungs enter their successor in their own last continuation (a `next` seat — the tool gate's fold→lanes shape), and the internals-and-config rung hands the ladder's one window await back as a started promise the driver awaits where the ladder did, so no host read of the ladder moved off its tick. runtask.ts 9 186 → 7 470 lines; dist `runtask.js` −730 / +50 with every moved line accounted for.
|
|
30
|
+
- `toolEndBodyFrom` / `reconciledToolEndBody` (internal machinery, not on the public surface) take the projector as a required parameter; the module-level `structuredFrom` is gone — `createStructuredProjector()` with no options is the engine set alone (the fixture form). `ToolRosterDeltaSeat.unsubscribe()` (additive) closes the lane with the leg.
|
|
31
|
+
|
|
32
|
+
### Pins
|
|
33
|
+
- `test/design393-s3-resume-lanes.test.ts` (WIRED / Input shape / await boundaries — five in-body mutations red), `test/design393-s3-388-seams.test.ts` (the two seams end to end on a real stdio MCP server and a caller tool with a declared card — three mutations red), the run-lane residents pins, G5's generic-Input pin.
|
|
34
|
+
|
|
35
|
+
### Gates (#627 — server [6626] door request)
|
|
36
|
+
- **A `checkpoint.unsupported_version` refusal always names its arm (#627).** The word rides an OPTIONAL
|
|
37
|
+
`detail.reason`, so nothing red when a throw site omitted it — and the file ledger's replay refusal
|
|
38
|
+
(`FileCheckpointStore`, an event kind written by a newer worker) had omitted it since it was written. It
|
|
39
|
+
now carries `reason: "ledger_event_unknown"`, the tenth member of the closed set: the one arm that is NOT
|
|
40
|
+
row-scoped — no row under that data root is readable by this binary, so the host rolls the binary forward
|
|
41
|
+
rather than routing one token to another worker. **@server**: additive word on an optional member; a
|
|
42
|
+
consumer holding a total table over `reason` gains a row.
|
|
43
|
+
- The word list leaves the constructor's inline type for a named closed set, `CheckpointErrorReason`
|
|
44
|
+
(exported from `src/core/checkpoint-store.ts`; the public export surface is unchanged), registered in
|
|
45
|
+
`docs/CLOSED-SETS.md` with a new **mint-obligation** leg in `gate:closed-sets`: every
|
|
46
|
+
`new CheckpointError("checkpoint.unsupported_version", …)` under `src/` must pass a detail object literal
|
|
47
|
+
whose `reason` is spelled AT the site (a literal, or a conditional whose branches are) and is a member of
|
|
48
|
+
the set — a missing detail, a missing/shorthand/computed word, or a word outside the set reds with
|
|
49
|
+
`file:line`, and a per-row population floor reds when the walker stops seeing the sites at all. The
|
|
50
|
+
registry's two G8 floors were re-measured in the same pass (rows 8 → 10, switches 1 → 3; both had drifted
|
|
51
|
+
below the census, which is the blindness a floor exists to make loud).
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## 7.8.0 — 2026-09-08
|
|
55
|
+
|
|
56
|
+
One roster instead of 29 name tables. `ToolSpec` already described a tool completely; the engine, the server and the shells still re-asserted each tool's identity and axes by NAME in hand-kept tables (a retired name lived on as a key in five of them). This version mints ONE effect roster per prepared leg from the mounted instances, derives every remaining table from a static catalog, and closes the mount array behind a builder.
|
|
57
|
+
|
|
58
|
+
### BREAKING
|
|
59
|
+
- `HAND_TOOL_EFFECTS`, `DEFAULT_BUDGET_EXEMPT_TOOLS`, `COMPACTABLE_TOOLS` leave `src/index.ts` (B1). Replacement: `describeToolCatalog()` — the hand band is `.filter((e) => e.mountedBy.includes("hands"))` (or `handBandEffects()`), the two policy sets `budgetExemptToolNames()` / `compactableToolNames()`. A host deriving its hand band (server `HANDS_BAND_TOOL_NAMES`) derives it over `HANDS_BAND_TAGS`; the derived band equals today's server list plus `ListAgents` (the peer-lane definition the hand-kept list predates) — the server's list is the stale side.
|
|
60
|
+
- `Prepared.tools` is `readonly AgentTool[]` (the builder's read face); a prepare phase that wrote the array takes `roster: RosterBuilder`.
|
|
61
|
+
|
|
62
|
+
### Narrowings (each a two-arm pin)
|
|
63
|
+
- **B6/B17** — the policy faces read the call's OBJECT face (`ToolCallRequest.face`, stamped at the gate entry off the live roster and carried through the fold's re-mint over the final args). The active-skill `allowPaths` face judges `pathTarget.skillScopeEligible` and confines the DECLARED target's value (`pathTargetValue`: the face's `param` + aliases — an in-fence `file_path` beside an out-of-fence declared param is a decoy, not the write; a declared param absent from the args is denied as unresolvable): a bare request (a policy invoked outside a Runner) or a caller tool that merely took a built-in's name (`Write`) is an unconfirmable write and is denied where it used to pass by name. The transcript-directory gate reads the face and falls back to the catalog's declaration for the name (never wider than 7.7.0).
|
|
64
|
+
- **B15** — the permission-rule lint's diagnostic set is closed: `unsupported.param_unknown` (renamed from `unsupported.unknown_param`), `unsupported.param_non_scalar` (`Edit(edits:…)` and every carried object/array key), `unsupported.tool_unknown` (only under a leg-supplied `ruleFaces` resolver — `ruleFacesFromRoster(prepared.toolRoster)`; without one the catalog alone judges and an unknown tool stays permissive). `RuleFaceView` / `catalogRuleFaceView` / `PermissionRulePolicyOptions.ruleFaces` are the seats.
|
|
65
|
+
- **B14** — the micro-compaction whitelist is derived from the catalog's `compactable` declarations: eight members, no `PowerShell` row (no sema definition ever carried the name).
|
|
66
|
+
- **B20** — a caller tool name / alias over 128 characters, or a contract member over 64, is refused typed at prepare (`config.tool_name_too_long` / `config.tool_contract_too_long`); an MCP assembled name can never exceed the bound — the namespacing helper already budgets it (peer segment absorbs the clamp, digest-suffixed; provider bound 64), so the design's "drop that tool" arm is satisfied by construction and no drop arm exists (design/388 S49's "core has no bound on the synthetic name" premise is stale as of RB-83/RB-107).
|
|
67
|
+
- The compaction file-op summary reads the live names' path access off the catalog and the three pre-rename names off the frozen `DURABLE_HISTORY_NAMES`; a persisted `MultiEdit` block contributes no file-op metadata any more (the retired name is a key nowhere — G3).
|
|
68
|
+
- **Every write fence judges the call's DECLARED slot** — two named readers for the two kinds of fence, shared by all of them (`writeTargetPath(call, target)`): a DENY-LIST (sensitive paths, frozen paths, write protection, the transcript gate, edit tracking) reads `protectivePathTargetOf` — the object face's declaration first, the catalog's declaration for the NAME as the floor (a bare request, or a face that declares none): reading the catalog for an undeclared same-name tool can only tighten a deny-list (the pre-388 posture, never wider). A GRANT that admits a write BY its path (session `allowDirs`, the fs-write gate's accept/exempt dirs, the active-skill allowPaths eligibility) reads `declaredPathTargetOf` — the face's OWN declaration only: a same-name tool that declares no path target is an unconfirmable write and the fence fails closed (allowDirs denies "declares no path target to confine"; the fs-write gate asks; the catalog's slot never buys an admission); a bare request (no face at all — a policy invoked outside a Runner) reads the catalog for the name. `isProtectedWrite` / `isDeclaredWrite` are the two membership predicates. The sensitive-path guard's DEFAULT membership is the same predicate (a caller tool under any name whose face declares a write target is guarded); an explicit `tools` list stays a name list. A decoy `file_path` beside an out-of-fence declared param is not the target; a declared param absent from the args is "no resolvable path" (containment fences deny/ask as they always did for an unresolvable write; the additive deny-lists — sensitive paths, frozen paths — see no target and pass, as their unresolvable arm always did). The cross-slot fallback that once read `notebook_path` for a Write, or `file_path` for a NotebookEdit, is gone (the tool's own schema refuses such a call before any write).
|
|
69
|
+
- **Merge-seam adversarial rounds (codex r1/r2 over the merged 388 × 393 S1/S2 × #602 tree)** — (H1) the durable resume-edit rechecks judge under the call's face: the park row carries `family` + `pathTarget` beside its B18 identity members (additive optional; `parkedToolFace` mints them at the same station, `parkedCallFaceOf` rebuilds the face — present iff face + all four identity members are on the row), the pre-CAS recheck judges the edited args under the ROW face and the two post-CAS rechecks under the live roster face; a decoy in-fence `file_path` beside an out-of-fence declared `destination` is refused `resume.constraint_rejected` pre-CAS (the checkpoint stays pending). **@server**: two additive optional keys on the checkpoint row (JSON rows: zero DDL); the store-contract kit gained a seven-member round-trip run your pg store must pass. (H2) MCP intake keys the overlay's results by tool NAME — a tool the overlay refused is not mounted at all (previously an index pairing mounted it bare and shifted later tools onto a neighbour's face). (F2) the `resume` identity arm is wired (see the disclosure below): a same-name tool whose contract / shape / revision differ from the parked row's is refused `resume.tool_contract_mismatch` through the non-executing reopen path (`tool_unavailable` family), never executed under the new face. (F3) the park-time projection re-adjudication (a JSON-fidelity store moving the args) carries the call face too. Two review proposals were REJECTED on the documented contract (#549 ①, HRD-PRM-1, #286, CLS-C-4): protective fences judge the tool's declared/catalog slot only — no "legacy spelling floor" when the slot is empty and no "judge every candidate" union; a caller whose implementation writes another slot declares its face.
|
|
70
|
+
- **B20 (keys)** — a schema property name is a SEMANTIC key every fence reads by and is never truncated on a roster row: a caller declaration over `TOOL_KEY_MAX_CHARS` (256) or `TOOL_KEYS_MAX` (1024 keys) is refused typed at prepare (`config.tool_schema_bound`); a caller face that does not fit its schema (a `pathTarget.param` outside the top-level keys, a `ruleFace` param outside the derived vocabulary — scalar keys ∪ the declared path target's param + aliases —, an approval card whose required keys are missing, a declared key or display hint over its bound) is refused `config.tool_face_invalid`; no display hint is truncated on a row either — the same validator the MCP `toolFaces` overlay passes; an MCP tool whose schema breaks the key bound is dropped with a disclosed reason (its siblings mount). `writeProtectionCheck(toolName, args, face?)` gains the call's face (additive).
|
|
71
|
+
- The sensitive-path guard / frozen-path deny / edited-file tracking / `allowDirs` all derive their covered set from the catalog's path targets: `MultiEdit` is no longer a member of any of them (it mounts nowhere).
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
- `ToolSpec.family` / `pathTarget` / `ruleFace` / `renderHints` / `compactable` / `budgetExempt` (additive; `defineTool` forwards them + `modelGate` onto the materialized face); `TaskSpec.excludeAllTools: true` (the whole face unmounts; `"*"` stays a legal caller name); `McpServerSpec.toolFaces` (a per-tool face overlay for MCP tools, validated against the materialized schema at every listing).
|
|
75
|
+
- The tool FACE vocabulary (`TOOL_FAMILIES`, `TOOL_MOUNT_TAGS`, `TOOL_APPROVAL_CARDS`, `TOOL_PATH_ACCESSES`, the render-hint bounds, `TOOL_WIRE_NAME_MAX_CHARS` / `TOOL_CONTRACT_MAX_CHARS`, `ToolPathTarget` / `ToolRuleFace` / `ToolRenderHints` / `ToolFaceDeclaration` / `ToolCatalogEntry`), the static catalog (`describeToolCatalog`, `catalogEntriesNamed`, `catalogToolNames`, `catalogRuleFaceOf`, `pathTargetOf`, `pathTargetValue`, the derived predicates, `engineCardTypes`, `predictMountedToolNames`, `HANDS_BAND_TAGS` / `MOUNT_TAG_IN_HANDS_BAND`), the roster (`ToolRosterEntry` / `ToolRoster` / `ToolRosterDelta` / `ToolRosterRenderHints` as typebox schemas AND `Static` types, `ROSTER_SOURCES`, `mintCapabilityId`, `toolIdentityOf`, `diffToolRoster`, `judgeParkedToolIdentity`, `parkedToolIdentity`, `ruleFacesFromRoster`, `toolCallFaceOf`, `inputKeysOf`, `ruleFaceOfInstance`, `ToolRosterDeltaSeat`), `TOOL_CONFORMANCE_VECTORS` / `describeToolConformanceVectors`, `applyMcpToolFaces` / `toolFaceProblem` / `APPROVAL_CARD_REQUIRED_KEYS`, `createStructuredProjector`, `ASK_USER_QUESTION_TOOL_NAME` (B5), `DURABLE_HISTORY_NAMES`.
|
|
76
|
+
- **Wire (@server @cli @client-core @web-admin @desktop)**: `WiringManifest.tools?: ToolRoster` on every effective manifest (OUTSIDE `configFingerprint`; `schemaVersion` unchanged); `TaskEvent` arm `tool_roster_delta { delta: ToolRosterDelta }` (a run-time roster change: the whole post-change roster + a summary usable when the consumer holds `fromDigest`; a mismatch means adopt the snapshot, never reject); `ToolCallRequest.face?: ToolCallFace`; `FsWriteGateWiring.isExempt(toolName, canonicalPath, identity?)` (B22 — the identity triple beside the name for a store keyed on `capabilityId`); `PendingAction{kind:"tool_approval"}` gains `capabilityId? / contractId? / shapeDigest? / implementationRevision? / rosterDigest?` (B18; additive, no checkpoint-version bump; a row without them follows the name-only path); three `engine_notice` codes (B2, audience operator): `config.tool_card_undeclared`, `config.tool_face_undeclared`, `config.tool_face_invalid`.
|
|
77
|
+
- Gates (all in `gate:all`): `gate:tool-catalog` (every core-owned definition spreads its catalog face; a face under another name, a ghost id, a row without family, a card minted by a file that does not list it — red, named), `gate:tool-names` (L1: every retained tool-name table registered in `docs/TOOL-NAME-TABLES.json`, disjoint from the retired names, inside the catalog), `gate:tool-docs` (the `ToolSpec` field reference in docs/sdk/04 is generated from the declaration — `scripts/gen-tool-spec-fields.mjs --check`). `ToolMountTag` registered in `docs/CLOSED-SETS.md`.
|
|
78
|
+
- The catalog is data: every row is deep-frozen at module init and `toolFace(id)` hands out a deep COPY — two factory products never share a `pathTarget`, and a host adapting one instance's face cannot move another run's fences (or the catalog).
|
|
79
|
+
- An MCP `toolFaces` overlay that does not fit the tool's materialized schema (at intake or after a refresh) means the tool is NOT mounted on that leg — announced once (`config.tool_face_invalid`); it never mounts bare (a bare mount would silently drop the write protection the operator declared, and an MCP name has no catalog floor). The approval-edit re-check and the edit ask carry the stamped face (a decoy in an approved edit is confined by the declared slot).
|
|
80
|
+
- A deferred tool's placeholder carries the real tool's `aliases` (dispatch by alias resolves to the same slot before and after activation, the roster's name→row resolution agrees with the dispatcher's — wire name first, then alias, in mount order — and a transcript call under an alias activates the canonical entry on replay). The MCP `toolFaces` overlay binds to the RAW accepted tool name (`Tool.mcpRemoteName`, a typed carry the materializer stamps): two raw spellings folding onto one minted name cannot make a face land on the other tool — the unaccepted spelling is announced undeclared. `TOOL_KEY_MAX_CHARS` / `TOOL_KEYS_MAX`; `ToolCallLike` / `callPathTargetOf` / `isPathConfinableWrite` / `fileOpAccessOf` (the compaction summary records file ops for the FILE families only — a search tool's `path` is a root, not a file read).
|
|
81
|
+
- The roster carries every name the model can call: the ToolSearch instance the deferred lane appends to the harness list rides the roster's ENGINE LANE (`RosterBuilder.mountEngineLane`) as the trailing row, minted after the disclosure lane (which reads the mount array and never writes it) — the prompt manifest's tool rows are the mount array; the roster is that plus the engine lane. The effect map follows the instance the harness dispatches to (a later same-name mount's declared effect is the name's truth). `gate:tool-catalog` rule ②b: a resolvable `defineTool({ name })` with no catalog row is red (per-file `HOST_FACTORY_DEFINITIONS` exemption list, empty today).
|
|
82
|
+
|
|
83
|
+
### Not wired in 7.8.0 — three runtask.ts seams deferred to design/393 S3 (the seats are minted; the driver does not yet consume them) — RETIRED in 7.9.0: all three are wired there (see that section)
|
|
84
|
+
- **Disclosure**: the three items below are the driver-side half of this slice. In 7.8.0 the roster, its delta seat and the structured projector all exist and are pinned at the seat/function level, but runtask.ts (design/393's domain, being decomposed in parallel) does not yet call them: no `tool_roster_delta` frame is pushed and `tool_end` still projects through the module-level card set. A consumer must not rely on `tool_roster_delta` frames until the version that lands 393 S3. (The `resume` identity arm — `judgeParkedToolIdentity` minting `resume.tool_contract_mismatch` — IS wired in 7.8.0: the merge-seam adversarial round showed a same-name replacement executing under a different face once the rechecks read faces, so it did not wait for S3.)
|
|
85
|
+
- `tool_end`: the body projection reads `prepared.structuredProjector` (the per-leg card set + the once-per-leg announce) in place of the module-level `structuredFrom` (tool-end-body.ts:77 via runtask).
|
|
86
|
+
- `wiring_manifest`: beside the manifest push (runtask.ts ≈:4149) subscribe `prepared.toolRosterDeltas.subscribe((delta) => queue.push({ type: "tool_roster_delta", delta, ...ident() }))`.
|
|
87
|
+
|
|
88
|
+
### Pins
|
|
89
|
+
- `test/tool-catalog.test.ts` (L5 snapshots + the gate's discriminance), `test/tool-roster.test.ts` (the builder, the row mint, the delta seat, G1 / G5 / G6 / G6c / G6e), `test/tool-conformance.test.ts` (L3 over four vectors on a real NodeExecutionEnv, L6 lockstep + every param matches, L4's once-per-leg announce, G6d), the retired-table tests re-pointed at the catalog.
|
|
90
|
+
|
|
3
91
|
## 7.7.0 — 2026-09-07
|
|
4
92
|
|
|
5
93
|
One version for the first refactor wave and its bug fixes (the 7.6.2 patch folded in: main already carries the store-contract change, so the fix ships with it). #618 / B-040: The classifier request shape changes on the wire; the explicit "off" tier changes on both brain lanes.
|
|
@@ -6,14 +6,14 @@ import { inlineUntrusted } from "../core/untrusted-text.js";
|
|
|
6
6
|
import { stepsFromMessages } from "./subagent-steps.js";
|
|
7
7
|
import { REPORT_FIELD_MAX } from "./subagent.js";
|
|
8
8
|
import { getSessionRetainLedger } from "./retain-ledger.js";
|
|
9
|
+
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
9
10
|
export const AGENT_TRANSCRIPT_TOOL_NAME = "AgentTranscript";
|
|
10
11
|
const AGENT_TRANSCRIPT_DEFAULT_N = 20;
|
|
11
12
|
const AGENT_TRANSCRIPT_MAX_N = 50;
|
|
12
13
|
export function createAgentTranscriptTool(opts) {
|
|
13
14
|
return defineTool({
|
|
14
15
|
name: AGENT_TRANSCRIPT_TOOL_NAME,
|
|
15
|
-
|
|
16
|
-
effect: "read",
|
|
16
|
+
...toolFace("agent-transcript"),
|
|
17
17
|
description: `Read the recent tool steps of a background agent you launched (task_id 'a…' from the Agent tool with ` +
|
|
18
18
|
`run_in_background), so you can see precisely where it got to — useful before SendMessage-ing a follow-up, ` +
|
|
19
19
|
`or to inspect a stopped agent's tail. Returns the last N steps (tool, target, outcome); it does not re-run ` +
|
|
@@ -4,15 +4,14 @@ import { inlineUntrusted } from "../core/untrusted-text.js";
|
|
|
4
4
|
import { formatPeerNameRef } from "./cross-session-ref.js";
|
|
5
5
|
import { entryAccessible } from "./roster-store.js";
|
|
6
6
|
import { mintPeerSessionCandidates, peerSessionBoxHandle } from "./peer-directory.js";
|
|
7
|
+
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
7
8
|
export const LIST_AGENTS_TOOL_NAME = "ListAgents";
|
|
8
9
|
export const LIST_AGENTS_TOOL_ALIAS = "ListPeers";
|
|
9
10
|
export const LIST_AGENTS_MAX_RESULT_CHARS = 10_000;
|
|
10
11
|
export function createListAgentsTool(opts) {
|
|
11
12
|
return defineTool({
|
|
12
13
|
name: LIST_AGENTS_TOOL_NAME,
|
|
13
|
-
|
|
14
|
-
contract: { contractId: "core.list_agents@1", implementationRevision: "1" },
|
|
15
|
-
effect: "read",
|
|
14
|
+
...toolFace("list-agents"),
|
|
16
15
|
isConcurrencySafe: () => true,
|
|
17
16
|
description: `List the agents and peer sessions you can message with SendMessage. Peer sessions are other sessions of this ` +
|
|
18
17
|
`engine running for the same user on this machine, shown as \`name [ref]\`; send to one with its name (or \`name [ref]\` ` +
|
package/dist/agents/observer.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { defineTool } from "../core/tools.js";
|
|
3
3
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
4
4
|
import { escapeEnvelopeTag } from "../core/untrusted-text.js";
|
|
5
|
+
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
5
6
|
export const OBSERVER_EVENT_TAGS = ["tool-call", "user-message", "tool-result", "turn-ended"];
|
|
6
7
|
const TAG_ESCAPE_RE = new RegExp(`<(?=/?(?:${OBSERVER_EVENT_TAGS.join("|")})(?:[>\\s/]|$))`, "gi");
|
|
7
8
|
export function escapeObserverTags(text) {
|
|
@@ -377,9 +378,8 @@ export function frameObserverReport(observerAgentName, report) {
|
|
|
377
378
|
export function createObserverReportToolSpec(opts) {
|
|
378
379
|
return {
|
|
379
380
|
name: OBSERVER_REPORT_TOOL_NAME,
|
|
380
|
-
|
|
381
|
+
...toolFace("observer-report"),
|
|
381
382
|
description: OBSERVER_REPORT_DESCRIPTION,
|
|
382
|
-
effect: "write",
|
|
383
383
|
parameters: Type.Object({
|
|
384
384
|
report: Type.String({
|
|
385
385
|
minLength: 1,
|
|
@@ -14,6 +14,7 @@ import { neutralizePeerBody } from "./cross-session-envelope.js";
|
|
|
14
14
|
import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getSessionRetainLedger } from "./retain-ledger.js";
|
|
15
15
|
import { createSubagentResume } from "./subagent.js";
|
|
16
16
|
import { appendHopToken, judgePeerAdmission, peerAxisToken, resolvePeerAdmissionConfig, PEER_MESSAGE_NOTICE, } from "./peer-admission.js";
|
|
17
|
+
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
17
18
|
export const SEND_MESSAGE_TOOL_NAME = "SendMessage";
|
|
18
19
|
let uplinkSeqGlobal = Date.now();
|
|
19
20
|
const UPLINK_RESULT_MAX = 8000;
|
|
@@ -134,7 +135,7 @@ export function createSendMessageTool(opts) {
|
|
|
134
135
|
};
|
|
135
136
|
return defineTool({
|
|
136
137
|
name: SEND_MESSAGE_TOOL_NAME,
|
|
137
|
-
|
|
138
|
+
...toolFace("send-message"),
|
|
138
139
|
executionMode: "parallel",
|
|
139
140
|
description: `Send a follow-up message to a previously spawned background agent. ` +
|
|
140
141
|
`Your plain text output is NOT visible to other agents — to communicate, you MUST call this tool. Messages addressed to you are delivered automatically; you don't check an inbox. ` +
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { redactSecrets } from "../core/untrusted-egress.js";
|
|
2
|
+
import { isProtectedWrite } from "../core/tool-registry.js";
|
|
3
|
+
import { writeTargetPath } from "../tools/fs/safety.js";
|
|
2
4
|
export const STEP_CAP = 10;
|
|
3
5
|
const FIELD_MAX = 80;
|
|
4
6
|
const EDITED_FILES_CAP = 32;
|
|
5
|
-
const EDITING_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
6
7
|
function firstLine(s) {
|
|
7
8
|
const nl = s.indexOf("\n");
|
|
8
9
|
return nl === -1 ? s : s.slice(0, nl);
|
|
@@ -57,10 +58,10 @@ export function extractTarget(args) {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
function editTargetPath(toolName, args) {
|
|
60
|
-
|
|
61
|
+
const call = { toolName, args };
|
|
62
|
+
if (!isProtectedWrite(call) || args === null || typeof args !== "object")
|
|
61
63
|
return undefined;
|
|
62
|
-
const
|
|
63
|
-
const p = a.file_path ?? a.path ?? a.notebook_path;
|
|
64
|
+
const p = writeTargetPath(call);
|
|
64
65
|
return typeof p === "string" && p.length > 0 ? p : undefined;
|
|
65
66
|
}
|
|
66
67
|
function outputFirstLine(output) {
|
|
@@ -248,7 +248,7 @@ export declare function classifySubagentError(terminal: TerminalCause): {
|
|
|
248
248
|
* delegation's landing report from `structured` and MUST NOT scrape the model-facing text. The
|
|
249
249
|
* `async_launched` receipt honored it (`type:"agent"`, on the allowlist); the **completed** delivery
|
|
250
250
|
* did not: its `details` was `{...child}` — a bare `TaskResult` spread with no `type` key at all — so
|
|
251
|
-
*
|
|
251
|
+
* the card projector's allowlist dropped it every time. The one card the contract names by name was the
|
|
252
252
|
* one card that could never arrive, and the only way to get the child's report was the thing the
|
|
253
253
|
* contract forbids.
|
|
254
254
|
*
|
package/dist/agents/subagent.js
CHANGED
|
@@ -60,6 +60,7 @@ import { recordRosterSpawn } from "./roster-store.js";
|
|
|
60
60
|
import { LAUNCH_RECEIPT_OWN_WORDS_CLAUSE, launchReceiptNoQuoteClause } from "./launch-receipt-contract.js";
|
|
61
61
|
import { ObserverDigestTap, ObserverPairing, createObserverReportToolSpec, markObserverTaskId, unmarkObserverTaskId, isObserverTaskId, ObserverResumeStateError, ObserverStoppedByUserError, observerFramingPrompt, observerSlug, resolveObserverDeclaration, } from "./observer.js";
|
|
62
62
|
import { SubagentStepRecorder } from "./subagent-steps.js";
|
|
63
|
+
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
63
64
|
const BG_AGENT_RESULT_MAX = 4_000;
|
|
64
65
|
const BG_AGENT_RESULT_FULL_MAX = 200_000;
|
|
65
66
|
function resultSettleFields(result) {
|
|
@@ -243,7 +244,6 @@ function createToolStatsCounter(delegationToolName) {
|
|
|
243
244
|
t.bashCount++;
|
|
244
245
|
break;
|
|
245
246
|
case "Edit":
|
|
246
|
-
case "MultiEdit":
|
|
247
247
|
t.editFileCount++;
|
|
248
248
|
recordEditLines(args);
|
|
249
249
|
break;
|
|
@@ -1392,13 +1392,12 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1392
1392
|
}));
|
|
1393
1393
|
return {
|
|
1394
1394
|
name: opts.name ?? DEFAULT_SUBAGENT_TOOL_NAME,
|
|
1395
|
+
...toolFace("agent"),
|
|
1395
1396
|
agentListing,
|
|
1396
1397
|
agentToolFaces,
|
|
1397
1398
|
...(agentToolPool !== undefined ? { agentToolPool } : {}),
|
|
1398
|
-
contentOrigin: "local",
|
|
1399
1399
|
...(rosterNames !== undefined ? { agentModels: rosterNames } : {}),
|
|
1400
1400
|
executionMode: "parallel",
|
|
1401
|
-
contract: { contractId: "core.agent@1", implementationRevision: "1" },
|
|
1402
1401
|
description: `Launch a new agent to handle complex, multi-step tasks. Each agent type has specific capabilities and tools available to it.\n` +
|
|
1403
1402
|
(opts.purpose ? `\nThis sub-agent is for: ${opts.purpose}.\n` : "") +
|
|
1404
1403
|
(hasAgents ? `\nAvailable agent types are listed in <system-reminder> messages in the conversation.\n` : "") +
|
|
@@ -41,6 +41,10 @@ import type { PermissionResult } from "./tool-policy.js";
|
|
|
41
41
|
* lane may answer it).
|
|
42
42
|
* - `org_rule` — an organization policy rule asked (or stamped an existing ask): "no configuration
|
|
43
43
|
* clears this, a person nods".
|
|
44
|
+
* - `rule_store_unavailable` — the deployment wired a persisted-rule store and could not READ it for
|
|
45
|
+
* this call (a read failure, a timeout): the person's own deny/ask rows cannot be enforced, so the
|
|
46
|
+
* call fails closed the way an unreadable org snapshot does — an allow tightens to an ask a person
|
|
47
|
+
* must clear, and no automatic lane may answer ("does one of my rules forbid this?" is unanswered).
|
|
44
48
|
* - `hook` — a deployment's PreToolUse screening face raised or co-raised the ask; the word is
|
|
45
49
|
* engine-stamped at the fold and is the deployment's own per-call adjudication.
|
|
46
50
|
* - `ask_rule` — an EXPLICIT `ask` permission rule matched: a person's standing "ask me each time",
|
|
@@ -61,7 +65,7 @@ import type { PermissionResult } from "./tool-policy.js";
|
|
|
61
65
|
* observer or an approval card can say WHICH engine layer raised the question, which one word
|
|
62
66
|
* could not.
|
|
63
67
|
*/
|
|
64
|
-
export declare const ASK_ORIGINS: readonly ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
68
|
+
export declare const ASK_ORIGINS: readonly ["content_question", "unresolvable", "org_unavailable", "org_rule", "rule_store_unavailable", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
65
69
|
export type AskOrigin = (typeof ASK_ORIGINS)[number];
|
|
66
70
|
/** Whether a value is a member of the closed origin set. Exported because the word crosses process
|
|
67
71
|
* boundaries: a host validating one it received must not hand-roll the set. */
|
|
@@ -87,6 +91,9 @@ export interface AskOriginFacts {
|
|
|
87
91
|
* read-back off the decision (a policy can put `decisionReason: "org_rule"` on its own ask; the
|
|
88
92
|
* gate's cell cannot be written by a policy). */
|
|
89
93
|
readonly org: "rule" | "unavailable" | undefined;
|
|
94
|
+
/** The gate's own record of its persisted-rule lane's READ: `"unavailable"` when the wired store could
|
|
95
|
+
* not be read for this call (the lane's deny/ask rows are unenforceable). Written only by the gate. */
|
|
96
|
+
readonly ruleStore?: "unavailable" | undefined;
|
|
90
97
|
/** Which of the gate's OWN post-fold tightens minted the standing ask, if one did — again the gate's
|
|
91
98
|
* record of its own layer (a tighten replaces an `allow` wholesale, so at most one fires per pass),
|
|
92
99
|
* never a read-back of `decisionReason: "safety"` off a decision a policy could have composed. */
|
|
@@ -127,6 +134,7 @@ export declare const ORIGIN_IMPLIES_REAL_APPROVAL: {
|
|
|
127
134
|
readonly unresolvable: false;
|
|
128
135
|
readonly org_unavailable: "at_derivation";
|
|
129
136
|
readonly org_rule: "at_derivation";
|
|
137
|
+
readonly rule_store_unavailable: "at_derivation";
|
|
130
138
|
readonly hook: false;
|
|
131
139
|
readonly ask_rule: false;
|
|
132
140
|
readonly denial_limit_fallback: "at_mint";
|
|
@@ -148,6 +156,8 @@ export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<As
|
|
|
148
156
|
* - `org_unavailable` / `org_rule`: judgment may clear a `requiresRealApproval` bit a POLICY minted,
|
|
149
157
|
* but not the org's — while adjudication is unavailable the org's answer is unknown, and an org
|
|
150
158
|
* ask carries the org's own "a person nods" semantic.
|
|
159
|
+
* - `rule_store_unavailable`: the person's own deny/ask rows are unreadable — whether one forbids this
|
|
160
|
+
* call is unknown, and a classifier verdict would answer that question in the person's stead.
|
|
151
161
|
* - `hook`: the deployment's own per-call adjudication; the classifier is its delegate for
|
|
152
162
|
* hesitation, not an override for its screening face.
|
|
153
163
|
* - `ask_rule`: a person's standing "ask me each time" — the classifier answering it would decide
|
|
@@ -169,6 +179,7 @@ declare const CLASSIFIER_MAY_ANSWER: {
|
|
|
169
179
|
readonly unresolvable: false;
|
|
170
180
|
readonly org_unavailable: false;
|
|
171
181
|
readonly org_rule: false;
|
|
182
|
+
readonly rule_store_unavailable: false;
|
|
172
183
|
readonly hook: false;
|
|
173
184
|
readonly ask_rule: false;
|
|
174
185
|
readonly denial_limit_fallback: false;
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const ASK_ORIGINS = ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
1
|
+
export const ASK_ORIGINS = ["content_question", "unresolvable", "org_unavailable", "org_rule", "rule_store_unavailable", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
2
2
|
const ASK_ORIGIN_SET = new Set(ASK_ORIGINS);
|
|
3
3
|
export function isAskOrigin(v) {
|
|
4
4
|
return ASK_ORIGIN_SET.has(v);
|
|
@@ -19,6 +19,8 @@ function originLadder(decision, facts) {
|
|
|
19
19
|
return "org_unavailable";
|
|
20
20
|
if (facts.org === "rule")
|
|
21
21
|
return "org_rule";
|
|
22
|
+
if (facts.ruleStore === "unavailable")
|
|
23
|
+
return "rule_store_unavailable";
|
|
22
24
|
if (decision.decisionReason === "hook")
|
|
23
25
|
return "hook";
|
|
24
26
|
if (decision.matchedAskRule !== undefined)
|
|
@@ -36,6 +38,7 @@ export const ORIGIN_IMPLIES_REAL_APPROVAL = {
|
|
|
36
38
|
unresolvable: false,
|
|
37
39
|
org_unavailable: "at_derivation",
|
|
38
40
|
org_rule: "at_derivation",
|
|
41
|
+
rule_store_unavailable: "at_derivation",
|
|
39
42
|
hook: false,
|
|
40
43
|
ask_rule: false,
|
|
41
44
|
denial_limit_fallback: "at_mint",
|
|
@@ -48,6 +51,7 @@ const CLASSIFIER_MAY_ANSWER = {
|
|
|
48
51
|
unresolvable: false,
|
|
49
52
|
org_unavailable: false,
|
|
50
53
|
org_rule: false,
|
|
54
|
+
rule_store_unavailable: false,
|
|
51
55
|
hook: false,
|
|
52
56
|
ask_rule: false,
|
|
53
57
|
denial_limit_fallback: false,
|
|
@@ -3,6 +3,7 @@ import { uuidv7 } from "../engine/session/uuid.js";
|
|
|
3
3
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
4
4
|
import { defineTool, errorResult } from "./tools.js";
|
|
5
5
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
6
|
+
import { toolFace } from "./tool-catalog-entries.js";
|
|
6
7
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
7
8
|
export function classifyQuestionOutcome(outcome) {
|
|
8
9
|
let unavailable;
|
|
@@ -170,7 +171,7 @@ export function createAskUserQuestionTool(onQuestion, source, opts) {
|
|
|
170
171
|
const continuationCard = askQuestionContinuationCard;
|
|
171
172
|
return defineTool({
|
|
172
173
|
name: ASK_USER_QUESTION_TOOL_NAME,
|
|
173
|
-
|
|
174
|
+
...toolFace("ask-user-question"),
|
|
174
175
|
description: "Ask the user a structured multiple-choice question when you are genuinely stuck or a decision is " +
|
|
175
176
|
"the user's to make (which approach, which of these). Provide 1-4 questions, each with 2-4 options " +
|
|
176
177
|
"(label + description). Use sparingly — not for anything you can determine yourself from the code or " +
|
|
@@ -226,7 +227,6 @@ export function createAskUserQuestionTool(onQuestion, source, opts) {
|
|
|
226
227
|
})),
|
|
227
228
|
}), { minItems: 1, maxItems: 4, description: "Questions to ask the user (1-4 questions)" }),
|
|
228
229
|
}),
|
|
229
|
-
effect: "read",
|
|
230
230
|
executionMode: "sequential",
|
|
231
231
|
execute: async (args, ctx) => {
|
|
232
232
|
const { questions } = args;
|
|
@@ -822,6 +822,36 @@ export type PendingAction = {
|
|
|
822
822
|
* pre-1a.2 checkpoint (deserialized without it), in which case the resume skips the hash check.
|
|
823
823
|
*/
|
|
824
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
|
+
};
|
|
825
855
|
/**
|
|
826
856
|
* #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
|
|
827
857
|
* older reader ignores. Unlike that precedent seat — which resume DOES read, through its
|
|
@@ -1937,6 +1967,30 @@ export interface CheckpointSummary {
|
|
|
1937
1967
|
* {@link ResourceLedger.spentMicroUsd}. Pure; reads no clock/random.
|
|
1938
1968
|
*/
|
|
1939
1969
|
export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
|
|
1970
|
+
/**
|
|
1971
|
+
* WHICH arm of a multi-arm {@link CheckpointError} `code` fired (requested 2026-08-10: a deployment retry
|
|
1972
|
+
* policy needs to tell "a newer worker can redeem this row" from "this row is damaged/caller-declared and
|
|
1973
|
+
* no worker ever will" — blanket-retriable and gate-shape heuristics were both refuted downstream, so the
|
|
1974
|
+
* throw site carries the fact it always knew).
|
|
1975
|
+
*
|
|
1976
|
+
* A CLOSED SET with one home, registered in `docs/CLOSED-SETS.md`. It is carried in an OPTIONAL member, so
|
|
1977
|
+
* the compiler is silent when a throw site omits it, and a site that omits it hands the host a refusal it
|
|
1978
|
+
* cannot classify — the registry's mint-obligation leg is what makes the omission loud (every
|
|
1979
|
+
* `checkpoint.unsupported_version` construction site must spell one of these words AT the site).
|
|
1980
|
+
*
|
|
1981
|
+
* · `unsupported_version` arms: `"version_newer"` (the row's format is newer than this worker's —
|
|
1982
|
+
* retryable on a newer worker), `"env_factory_missing"` (retryable on a factory-wired worker),
|
|
1983
|
+
* `"governed_unwired"` (retryable on an org-wired worker), `"origin_missing"` (a pending approval row
|
|
1984
|
+
* minted before the ask's origin was persisted on the park — terminal for the row's bytes; the host
|
|
1985
|
+
* decides the orphaned card itself, no worker version redeems it), `"ledger_event_unknown"` (a durable
|
|
1986
|
+
* LEDGER carries an event kind this binary does not know, so the replay is refused rather than served
|
|
1987
|
+
* incomplete — the one arm that is not row-scoped: NO row in that data directory is readable here, so
|
|
1988
|
+
* the host rolls the binary forward instead of routing one token elsewhere).
|
|
1989
|
+
* · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`, `"real_approval_forged"`,
|
|
1990
|
+
* `"read_face_damaged"`, `"read_face_forged"`, `"constraint_chain_missing"` (all terminal for the row's
|
|
1991
|
+
* current bytes — no worker version redeems a damaged row).
|
|
1992
|
+
*/
|
|
1993
|
+
export type CheckpointErrorReason = "version_newer" | "env_factory_missing" | "governed_unwired" | "origin_missing" | "ledger_event_unknown" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged" | "constraint_chain_missing";
|
|
1940
1994
|
/** A typed checkpoint-store error so callers branch on `code` (mirrors `SessionError`). */
|
|
1941
1995
|
export declare class CheckpointError extends Error {
|
|
1942
1996
|
readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
|
|
@@ -2112,18 +2166,11 @@ export declare class CheckpointError extends Error {
|
|
|
2112
2166
|
* gave one (same carrier, same semantics: retry the SAME token after the wait). Absent on
|
|
2113
2167
|
* every other code. */
|
|
2114
2168
|
retryAfterMs?: number;
|
|
2115
|
-
/** WHICH
|
|
2116
|
-
*
|
|
2117
|
-
*
|
|
2118
|
-
*
|
|
2119
|
-
|
|
2120
|
-
* · `unsupported_version` arms: `"version_newer"` (retryable on a newer worker),
|
|
2121
|
-
* `"env_factory_missing"` (retryable on a factory-wired worker), `"governed_unwired"`
|
|
2122
|
-
* (retryable on an org-wired worker).
|
|
2123
|
-
* · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`,
|
|
2124
|
-
* `"real_approval_forged"`, `"constraint_chain_missing"` (all terminal for the row's
|
|
2125
|
-
* current bytes — no worker version redeems a damaged row). */
|
|
2126
|
-
reason?: "version_newer" | "env_factory_missing" | "governed_unwired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged" | "constraint_chain_missing";
|
|
2169
|
+
/** WHICH arm fired, where one `code` covers several — the word list and what each word means to a
|
|
2170
|
+
* host live at {@link CheckpointErrorReason}, its one home. Additive and optional like `field`
|
|
2171
|
+
* (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
|
|
2172
|
+
* is a gate rather than a type: see the closed set's own doc. */
|
|
2173
|
+
reason?: CheckpointErrorReason;
|
|
2127
2174
|
} | undefined;
|
|
2128
2175
|
constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
|
|
2129
2176
|
/** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
|
|
@@ -2298,18 +2345,11 @@ export declare class CheckpointError extends Error {
|
|
|
2298
2345
|
* gave one (same carrier, same semantics: retry the SAME token after the wait). Absent on
|
|
2299
2346
|
* every other code. */
|
|
2300
2347
|
retryAfterMs?: number;
|
|
2301
|
-
/** WHICH
|
|
2302
|
-
*
|
|
2303
|
-
*
|
|
2304
|
-
*
|
|
2305
|
-
|
|
2306
|
-
* · `unsupported_version` arms: `"version_newer"` (retryable on a newer worker),
|
|
2307
|
-
* `"env_factory_missing"` (retryable on a factory-wired worker), `"governed_unwired"`
|
|
2308
|
-
* (retryable on an org-wired worker).
|
|
2309
|
-
* · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`,
|
|
2310
|
-
* `"real_approval_forged"`, `"constraint_chain_missing"` (all terminal for the row's
|
|
2311
|
-
* current bytes — no worker version redeems a damaged row). */
|
|
2312
|
-
reason?: "version_newer" | "env_factory_missing" | "governed_unwired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged" | "constraint_chain_missing";
|
|
2348
|
+
/** WHICH arm fired, where one `code` covers several — the word list and what each word means to a
|
|
2349
|
+
* host live at {@link CheckpointErrorReason}, its one home. Additive and optional like `field`
|
|
2350
|
+
* (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
|
|
2351
|
+
* is a gate rather than a type: see the closed set's own doc. */
|
|
2352
|
+
reason?: CheckpointErrorReason;
|
|
2313
2353
|
} | undefined);
|
|
2314
2354
|
}
|
|
2315
2355
|
/**
|
|
@@ -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
|
-
|
|
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)
|
|
@@ -17,6 +17,12 @@ export interface EngineNotice {
|
|
|
17
17
|
* the value in force; `detail: { knob, raw, usedMs }`.
|
|
18
18
|
* - `"config.materialize_env_discarded"` — `SEMA_TOOL_MATERIALIZE_STRATEGY` held a value outside the
|
|
19
19
|
* closed set in a seat where it is not in force; `detail: { raw, specStrategy? }`.
|
|
20
|
+
* - `"config.tool_card_undeclared"` (design/388 L4) — a tool minted a `details.type` card its definition
|
|
21
|
+
* (or its caller declaration's `renderHints.resultCards`) never declared; the card was dropped from
|
|
22
|
+
* `tool_end.structured`; once per leg per type; `detail: { type, sessionId, runId }`.
|
|
23
|
+
* - `"config.tool_face_undeclared"` / `"config.tool_face_invalid"` (design/388 §2.7) — an MCP server's
|
|
24
|
+
* `toolFaces` overlay names a tool the listing does not carry / does not fit the tool's materialized
|
|
25
|
+
* schema (the tool mounts bare); once per leg per tool; `detail: { server, tool, problem?, sessionId, runId }`.
|
|
20
26
|
* - `"tool_result.offload_put_failed"` (#167) — a clear-with-offload persist's fire-and-forget put
|
|
21
27
|
* failed; THIS attempt stored nothing (the failure arm reports, it never re-inserts under the
|
|
22
28
|
* ref) — an earlier attempt of the same idempotent re-put may already have stored the row, so
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
2
|
import type { ToolPolicy } from "./tool-policy.js";
|
|
3
|
+
/** design/388 B22 — the identity triple the exemption probe hands the store beside the name. */
|
|
4
|
+
export interface ToolCallIdentity {
|
|
5
|
+
capabilityId: string;
|
|
6
|
+
contractId: string;
|
|
7
|
+
shapeDigest: string;
|
|
8
|
+
}
|
|
3
9
|
/** Options for {@link createFsWriteGatePolicy}. */
|
|
4
10
|
export interface FsWriteGatePolicyOptions {
|
|
5
11
|
/** The task's real execution env (the one the hands run against) — canonicalization must see the
|
|
@@ -37,7 +43,7 @@ export interface FsWriteGatePolicyOptions {
|
|
|
37
43
|
* `combinePolicies` outranks this `allow` under the deny > ask > allow fold, and the two fail-closed
|
|
38
44
|
* asks for an UNRESOLVABLE target fire before any canonical key exists, so they are never exempted.
|
|
39
45
|
*/
|
|
40
|
-
isExempt?: (toolName: string, canonicalPath: string) => boolean | Promise<boolean>;
|
|
46
|
+
isExempt?: (toolName: string, canonicalPath: string, identity?: ToolCallIdentity) => boolean | Promise<boolean>;
|
|
41
47
|
/**
|
|
42
48
|
* design/380 O13 — the deployment HARD-CLOSE knob for guarded writes. Armed (`true`), a covered
|
|
43
49
|
* write whose env does NOT provide `writeFileGuarded` is REFUSED (deny, typed message) — never the
|