@sema-agent/core 7.26.3 → 7.28.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 +59 -0
- package/dist/agents/builtin-agents.d.ts +6 -15
- package/dist/agents/cascade.d.ts +29 -33
- package/dist/agents/observer.d.ts +18 -20
- package/dist/agents/subagent.d.ts +58 -70
- package/dist/agents/tool-filter.d.ts +10 -12
- package/dist/agents/verify.d.ts +44 -49
- package/dist/brain/input-too-long.d.ts +10 -12
- package/dist/brain/media-degrade.d.ts +4 -5
- package/dist/brain/reasoning.d.ts +10 -12
- package/dist/brain/repetition.d.ts +7 -9
- package/dist/brain/request-params.d.ts +20 -25
- package/dist/brain/route-adjudicator.d.ts +4 -4
- package/dist/brain/route-conformance.d.ts +2 -2
- package/dist/brain/status-sink.d.ts +12 -14
- package/dist/brain/terminal-cause.d.ts +12 -15
- package/dist/brain/timeout.d.ts +17 -19
- package/dist/core/auto-mode-directive.d.ts +6 -4
- package/dist/core/auto-mode-directive.js +1 -5
- package/dist/core/engine-notice.d.ts +13 -6
- package/dist/core/memory-engine/consolidation.d.ts +11 -15
- package/dist/core/memory-engine/engine.d.ts +45 -54
- package/dist/core/memory-engine/tools.d.ts +17 -23
- package/dist/core/permission-rule-model.js +5 -3
- package/dist/core/runner/contracts.d.ts +31 -7
- package/dist/core/runner/denial-limit-arms.d.ts +17 -15
- package/dist/core/runner/denial-limit-arms.js +4 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +1 -2
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -2
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/tool-policy.d.ts +23 -2
- package/dist/core/tool-policy.js +106 -103
- package/dist/index.d.ts +6 -1
- package/dist/shell/lex.d.ts +279 -0
- package/dist/shell/lex.js +1253 -0
- package/dist/stores/file/background-agent-store.d.ts +4 -0
- package/dist/stores/file/background-agent-store.js +10 -0
- package/dist/stores/file/checkpoint-store.d.ts +3 -0
- package/dist/stores/file/checkpoint-store.js +18 -0
- package/dist/stores/file/corrupt-read-sink.d.ts +20 -0
- package/dist/stores/file/corrupt-read-sink.js +15 -0
- package/dist/stores/file/file-history-store.d.ts +1 -2
- package/dist/stores/file/file-history-store.js +3 -8
- package/dist/stores/file/fs-atomic.d.ts +8 -3
- package/dist/stores/file/fs-atomic.js +19 -2
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/mailbox-store.d.ts +3 -0
- package/dist/stores/file/mailbox-store.js +24 -18
- package/dist/stores/file/permission-rule-store.js +18 -1
- package/dist/stores/file/session-policy-store.d.ts +17 -1
- package/dist/stores/file/session-policy-store.js +3 -12
- package/dist/stores/file/session-store.d.ts +1 -5
- package/dist/stores/file/session-store.js +9 -14
- package/dist/stores/file/shared-ledger.d.ts +18 -0
- package/dist/stores/file/shared-ledger.js +16 -2
- package/dist/stores/file/tool-result-store.d.ts +9 -1
- package/dist/stores/file/tool-result-store.js +17 -2
- package/dist/stores/file/workflow-journal-store.d.ts +10 -1
- package/dist/stores/file/workflow-journal-store.js +8 -12
- package/dist/stores/file/workflow-run-store.d.ts +3 -0
- package/dist/stores/file/workflow-run-store.js +10 -0
- package/dist/tools/artifact/local-stub.js +16 -1
- package/dist/tools/fs/bash-program-position.d.ts +2 -4
- package/dist/tools/fs/bash-program-position.js +2 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -2
- package/dist/tools/fs/bash-readonly-classifier.js +1 -1
- package/dist/tools/fs/shell-read-check.js +1 -1
- package/dist/tools/fs/shell-read-establish.js +11 -1
- package/dist/tools/fs/shell-read-plan.d.ts +7 -2
- package/dist/tools/fs/shell-read-plan.js +48 -10
- package/package.json +2 -2
- package/test/export-surface.snapshot.json +3 -1
- package/wire-changes/core@7.27.0.json +152 -0
- package/wire-changes/core@7.28.0.json +594 -0
- package/dist/tools/fs/bash-lexer.d.ts +0 -152
- package/dist/tools/fs/bash-lexer.js +0 -627
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.28.0 — 2026-09-22
|
|
4
|
+
|
|
5
|
+
### One shell lexer mints word FACTS; the thirteen ANSI-C letters fold; an unterminated quote is a parse failure (#994 S0; behaviour WIDENING ×1 and NARROWING ×1; +0 exports; @cli @test)
|
|
6
|
+
|
|
7
|
+
- **What changed.** The read faces' lexer moved from `src/tools/fs/bash-lexer.ts` to `src/shell/lex.ts` (a layer-0 module that imports nothing) and grew the structure every later slice reads: each word carries `pieces` (literal / expansion, each with its source span and the quote that holds it), `quotedSpans`, `expansions` (parameter, command, process, arithmetic, tilde, brace, glob), an `assignment` fact (`name`, `op`, `subscript`, `value`, `bare`, `form`, `settlesWholeName`), an `unresolved` reason from a closed set, and `atCommandPosition`. `raw` / `folded` / `wordsOf` / the connectors are unchanged views, pinned byte-identical to the previous lexer over a 91-entry corpus except the deltas below (`test/backlog994-s0-lexer-baseline.test.ts`). No reader migrates in this slice; the position reader, the classifier and the read station read the same two spellings they read before.
|
|
8
|
+
- **WIDENING (ANSI-C).** `$'…'` bodies carrying one of the thirteen single-letter escapes (`\a \b \e \E \f \n \r \t \v \\ \' \" \?`) now fold to the shell's byte instead of refusing the word. The table is admitted by a differential test against the real shells (`test/backlog994-s0-lexer-real-shells.test.ts`: every bash found on the machine, plus `sh`; a machine with no bash or no `sh` FAILS that test rather than skipping). Every other escape — numeric, `\x`, `\u`/`\U`, `\c` — stays `unresolved` (its bytes differ between bash 3.2 and 5.x, or NUL truncates the region), and the refusing sentence is unchanged. Who is hurt: nobody — the thirteen values are fixed on every target shell.
|
|
9
|
+
- **NARROWING (unterminated quotes).** A command with an unclosed `'`, `"` or `$'` is a parse failure in every mode, refused with a sentence, where the strict faces used to re-read it quote-blind (more words, more segments, each vetted). Bash runs nothing on such a command. Consumers: the classify face refuses it (`splitShellCompoundSegments` returns the sentence), the read station's plan is `unreadable` (a card on the bash seats, a refusal headless), and the refused-command whitelist no longer vouches for it. Who is hurt: a deployment whose approvals relied on a quote-blind reading of a malformed command auto-passing as read-only — that reading vouched for text the shell would reject.
|
|
10
|
+
- **Rule import: a persisted-rule body with an unbalanced `$'…'` is refused as `invalid.unbalanced_quotes`, where 7.27.0 refused it as `invalid.not_simple_command`** (`src/core/permission-rule-model.ts`; `RuleRejectCode` gains no member — the code moves within the closed set to the one its `'` / `"` siblings already use, so an import report groups the three spellings together). Consumer flip for a report keyed on the old code (measured on both trees: G5).
|
|
11
|
+
- **An unresolved word refuses ITS reading, not the whole text (merged-tree adversarial round).** The read plan (`planShellReads`) now lexes to facts and keeps every segment beside a word it cannot settle (a compound array value, a subscript, an off-table `$'…'` escape), carrying the refusal sentence as the plan's `unreadable` floor; the fold raises that floor to `unestablishable` and a deny hit anywhere in the text still outranks it (`deny > ask`). Collapsing to an empty plan had let `A=(1 2); cat <deny row>` land on an approvable ask where `cat <deny row>` alone is refused. A PARSE failure is unchanged (no words, bash runs nothing). Pin: `test/backlog994-s0-deny-outranks-unresolved.test.ts`. Glob and brace carving is one walk over the pieces instead of a rebuild per character (a word of 8000 `*` took 1.6 s, now 13 ms; `test/backlog994-s0-lexer-baseline.test.ts` holds the byte-identical reading).
|
|
12
|
+
- **A word the lexer could not settle hands the judge no literal, and a lost working directory keeps the deny floor (round 2).** Every candidate minted from a refused-view unresolved word — a read operand, a `cd` target — is `unresolvable`; an unprovable move loses the base as before, and a relative read after it is ALSO judged for deny rows only against the bases the text was certainly in when it lost track (`ReadCandidate.floorBases`; the declared cwd when nothing had moved — `cd sub && cd $'\\x2e' && cat <row under sub>` is refused through the `sub` floor) (`cd $'\x2e' && cat <deny row>` — bash: `cd .` — had moved the base to a fictitious `/work/\x2e` and walked past the deny; it is refused on both full-shell seats now, and the same text without a deny row stays a mandated ask). Unmatched `{` / `[` no longer rescan the suffix: brace pairs are matched in one forward walk and `]` positions in one backward walk (32 000 unmatched braces: 3.3 s → 37 ms).
|
|
13
|
+
- **Facts minted, verdicts unchanged (until S1).** A subscripted assignment at command position pairs its brackets across blanks and operators as bash does (`A[1 + 1]=v cat f` is ONE word); the position reader still reads the word as an unknown program (a card), and a subscript that bash reads as arithmetic across an operator, a quoted `]`, or a pairing the two bash versions disagree on (after a leading redirection) is `unresolved`. A compound value `NAME=(…)` at command position — or as an operand of `alias`/`declare`/`eval`/`export`/`let`/`local`/`readonly`/`typeset` — is one word marked `unresolved` rather than a `(` group. `$"…"` carries `unresolved: "locale_string"` while the refused-reading view still folds it (refusing it today would turn a deny hit into a card).
|
|
14
|
+
- **Dialect.** The lexer declares `SHELL_LEX_DIALECT = "bash"`; `bashOnlyConstructsOf(lex)` lists the constructs a POSIX `sh` reads differently, for the execution environment's `sh` fallback to degrade on (the reader that consumes it lands in S1).
|
|
15
|
+
- **Retired.** The "any backslash in `$'…'` is unreadable" special case (folded into `unresolved`), and the quote-blind re-run (`scan`'s `quotesLiteral`). `SHELL_CONTROL_PREFIXES` has one home (the lexer; the position reader re-exports it).
|
|
16
|
+
|
|
17
|
+
### The verification debt: four binary pins have a real-green reading, the embedder pin is in the gate, and five comment ceilings come back down (#970; no behaviour change; +0 exports; @server @test)
|
|
18
|
+
|
|
19
|
+
- **Four pins that could only ever skip now have a measured green, in the lane that produces the ratchet's own report.** On the gate container (`scripts/remote-gate.sh`, node 24.21.0): ripgrep 13.0.0 — `fs-search-honesty` 156 passed / 0 skipped, including the three real-`rg` legs the baseline had been carrying; sharp 0.35.3 — the two live-sharp legs pass (their `sharp-absent` twin is the far half of a paired conditional and still skips, by design); a real Postgres with pgvector — `pg-live` 4 + `pg-stores` 1, all green, which is the first reading for the two things pg-mem cannot emulate (true `<=>` cosine ranking, real MVCC contention); real language servers over stdio — `lsp-live-servers` 6 passed / 0 failed with typescript-language-server 6.0.0 and pyright 1.1.414 installed into the lane (the five languages with no server installed skip, as the file's own gate intends).
|
|
20
|
+
- **The skip ratchet names its lane and loses five entries.** `test/skip-baseline.json` gained a `note` block (which machine this baseline is judged on, which binaries that lane carries, why an env-gated entry is a deliberate opt-in rather than lost coverage), and the rg ×3 / sharp ×2 entries are gone — they run in that lane, so a machine without those binaries now goes red, which is what a ratchet is for. The LSP and Postgres entries stay: they are opt-in on `LSP_LIVE` / `PG_LIVE` + `PG_URL`, and removing them would make the whole ratchet depend on those services being up. `verify-skip-baseline.mjs --update` / `--merge` used to rewrite the file as `{skipped}` alone, which would have deleted that note one run later; both legs now carry over every non-`skipped` key.
|
|
21
|
+
- **A required-green pin must be PRESENT and PASSED on a full run, not merely "not skipped".** `gate:skips` now passes `--full` to `scripts/verify-skip-baseline.mjs`; on a declared full run every `requiredGreen` entry has to appear in the report as `passed` — a file that was renamed, glob-missed or crashed at collection used to read as green through the skip-only check (main-session receipt counterexample). Scoped runs (no `--full`) keep absence as scope. The baseline `note` now states ONE lane: the gate container is the only lane the full suite runs in on the release path, and the list is re-minted from that lane's report at RC. Six pins spawn the gate on synthetic reports (`test/skip-gate-required-green.test.ts`).
|
|
22
|
+
- **The full-run check runs before the rewrite legs, and the live gate judges its registry on its own (merged-tree adversarial round).** `--full --update` / `--full --merge` refuse when a required pin is absent, skipped or failed (a rewrite must not bank the loss); `run-live-gate.mjs` fails by name when an ENABLED conditional file is missing from the tree and announces a disabled one whether or not the file exists (a renamed file used to vanish without a line). Pins: `test/skip-gate-required-green.test.ts` (+2), `test/live-gate-conditional-registry.test.ts`.
|
|
23
|
+
- **The live gate's verdict is per target (round 2).** `scripts/live-gate-verdict.mjs`: every selected file must be present in the JSON report with at least one passed case and nothing skipped or failed — a selected file vitest never collected used to pass on the aggregate counts. Pin: `test/live-gate-verdict.test.ts`.
|
|
24
|
+
- **The real-embedder pin enters the release gate by name.** `test/memory-embed-live.test.ts` → `test/live-memory-embed.test.ts`: `run-live-gate.mjs` selects the live surface by glob over `test/live-*.test.ts`, so the only real-embedder coverage in the tree had been structurally outside it. An embeddings endpoint is not the standing chat provider, so the file carries an admission condition rather than a flat exclusion — in the default surface when `EMB_LIVE_BASE_URL` is set (live surface 47 → 49 assertions), and when it is not, the gate prints the omission by name instead of silently globbing past it. **Measured while wiring it:** against an endpoint refusing every connection the SECOND pin passed green — its query shared the word `particles` with the note it expected to rank first, so the lexical path answered it alone. The query now shares no term with either body and the leg asserts two rows and a real cosine ordering (refused-endpoint reading: 1 failed / 1 passed before, 2 failed after).
|
|
25
|
+
- **Five comment-ratio ceilings come back off the 7.24.2 raises** (`src` 0.3003/264 → 0.3002/263, `src/examples` 0.1661/38 → 0.1653/37, `src/brain` 0.4227/227 → 0.4210/219, `src/core/memory-engine` 0.3672/954 → 0.3663/932, `src/agents` 0.4385/1056 → 0.4371/1038), by removing narrative rather than contract, and every one is banked in the commit that earned it. `docs/RELEASE-TIERS.md` gains the X-before fixed checklist (the full `gate-all --with-dist --rc` roster, the ratchet/bookkeeping pins, the silent-arm gate and the three failopen files) — gates whose only other chance to be red is after the tag.
|
|
26
|
+
- **Still owed, by their owners.** @server: the Pg/TiDB dialects across the 39-cell memory kit have no real-green reading yet; the lane and the DSN wiring above are the same ones that leg needs. @test: the two memory black-box fences pinned at `core@5.23.0` still exit 2 (SKIP), and the cross-session personal-recall e2e was last exercised on 2026-08-16.
|
|
27
|
+
|
|
28
|
+
## 7.27.0 — 2026-09-22
|
|
29
|
+
|
|
30
|
+
### Every JSONL read-back is form-judged, and every store that tolerates damage names the loss (#1013; behaviour NARROWING; +0 exports; @server @cli @client-core @test)
|
|
31
|
+
|
|
32
|
+
- **What was wrong.** `readJsonlRecords` judged only whether a line PARSED. A line parsing to `123`, `"str"`, `true`, `null` or `[1]` was handed to a replay as a record, and the replays then read members off it: the session repo pushed `undefined` into a session's entry list, the memory replay's `ev.op` and the workflow journal's `typeof rec.callKey` faulted on `null`. Eight of the twelve store-contract kits said `unbound` — nobody had measured their read face against the cross-store sentence at all.
|
|
33
|
+
- **NARROWING (`readJsonlRecords`, a package-root export).** A line that parses to anything but a JSON OBJECT is now CORRUPT: disclosed once through `onCorrupt` and skipped, the same road an unparseable line already took. A downstream store that deliberately wrote scalars or arrays as JSONL records will read them back as absent, with a disclosure. Disclosure `reason` strings now lead with a word from `CORRUPT_BODY_FORMS` (`torn-json: unparseable jsonl record at line N …`, `not-an-object: …`); the historical `unparseable jsonl record` substring is unchanged.
|
|
34
|
+
- **One disclosure seat, seven stores.** The swallow-guard around a host's `onCorruptRead` was hand-written four times and had diverged — three copies contained an async sink's rejection, the mailbox's caught only a synchronous throw, so an `async` sink there became an unhandled rejection. `guardCorruptSink` is now the only implementation. `FileCheckpointStoreOptions`, `FileBackgroundAgentStoreOptions`, `FileWorkflowRunStoreOptions`, `FileWorkflowJournalStore`'s new third argument and `FileToolResultStore`'s new second argument all gained the seat (additive; absent = the old silent tolerance). `FileStorageBackend` forwarded its seat to three stores out of seven — it now forwards to all of them, so a host wiring ONE sink hears every store's losses.
|
|
35
|
+
- **A record is judged before anything applies it.** `LedgerModel` gained REQUIRED `judgeRow` / `judgeEvent` (the three bundled ledger models stopped compiling until they answered), and each model reuses ONE row judge for a snapshot row and for the row an event carries. The mailbox's `applyEvent` answers whether it applied, and each arm admits its own payload: `{"t":"ack"}` with no sequence number used to compare every message against `undefined` and empty the whole box in silence. An UNKNOWN checkpoint event kind is still admitted first, so the `checkpoint.unsupported_version` refusal keeps its full reach.
|
|
36
|
+
- **NARROWING (loud refusals).** A `LocalArtifactStub` ledger whose `attempts` map carries a body the store never wrote now refuses with `artifact stub ledger is corrupt (bad attempt record)`, in the words a bad artifact row already used — `null` used to fault the `in` operator and `{}` used to become a publish outcome with an undefined code. A permission-rule adoption marker is now judged as a WHOLE arm: a half-written one (`{"adopted":{}}`, `{"from":{}}`) used to read `rec.from.kind` off `undefined` or fall through to a WRITABLE partition over the retired bucket; it now refuses fail-closed, as that module's doc already promised.
|
|
37
|
+
- **The roster is bound.** All eight formerly `unbound` kits run `storeReadBackShapeContract` against their real bundled backend and really damaged files (`test/backlog1013-kit-roster-bound.test.ts`); `peer-directory` declares its in-memory exemption with a reason. `unbound` stays a legal census word so a kit added tomorrow can still say it.
|
|
38
|
+
- **Known limits.** `docs/KNOWN-LIMITS.md` KL-1013a (a skipped ledger row is erased at the next compaction) and KL-1013b (a row judge covers the members the replay and the queries read, not a store's whole persisted schema).
|
|
39
|
+
### The classifier denial-limit's last resort has a DURABLE station too (#1019; behaviour NARROWING; +0 exports; @server @cli @test)
|
|
40
|
+
|
|
41
|
+
- **What was wrong.** The auto-mode classifier's denial limit falls back to a person. When nobody judges that fallback the run STOPS (`classifier.denial_limit`) rather than going back to a model the classifier denies without end. Only the synchronous leg had that station. A deployment that PARKS the fallback on a durable queue and then answers the park with its own policy (`hostDecision.decidedBy: "policy"`) got the deny delivered to the model, which tried again, was blocked again, parked again — forever, with zero `classifier.denial_limit` and zero notices. Measured on 7.26.3: the run re-parked on the next call and the terminal came back `paused`.
|
|
42
|
+
- **One rule, a second station.** The resume leg's deny arm asks the same two questions the synchronous settlement site asks: is the ask that just settled the classifier's FALLBACK (the member is minted only at the bound, so its presence IS "the limit was reached"), and did it end with NOBODY having judged it — read off the ONE table (`SETTLEMENT_NOBODY_JUDGED`), not a second word list. Rule count +0: one rule, two stations. The stop closure itself is now reached through `Prepared.stopForDenialLimit`, so both stations fill the same seat, compose the same sentence and mint the same notice.
|
|
43
|
+
- **NARROWING.** A durable deployment that answers a parked denial-limit fallback with `decidedBy:"policy"` now ends that leg: `failed`, `classifier.denial_limit`, one `EngineNotice`, the deny still delivered as the call's result. `"person"` is unchanged and stops nothing — the table's `human_refused` row says someone looked — and so is `"sla_timeout"`. An ordinary parked ask (no fallback member) is unchanged whatever the word.
|
|
44
|
+
- **The sentence says which lane, and states the ROW's counts.** The two lanes differ in one clause: the synchronous one says no approver is wired to fall back to, the durable one names the settlement word that ended the park. The counts come from the PARKED row, so a resume in a fresh process reports the streak that actually tripped the bound rather than the new leg's empty tracker.
|
|
45
|
+
- **Who is hurt.** A host whose durable approval channel refuses by policy and whose test asserts the run keeps going must re-pin: it now stops, which is the point — that loop was spending model turns on a question nothing in the deployment could answer. Nothing changes for a deployment that answers parks as a person, for one with no durable store, or for the synchronous leg, whose sentence is byte-identical (pinned).
|
|
46
|
+
|
|
47
|
+
### The approval-posture directive is EXCLUSIVE, bypass first (#1002; prompt-face NARROWING; +0 exports; @cli @test)
|
|
48
|
+
|
|
49
|
+
- **What changed.** A run declaring BOTH `autoModeRequested` and `approverPosture:"bypass"` was told both paragraphs in its first-turn engine reminder. It is now told the bypass one and nothing else. The two facts stay independent SEATS — that is how they ARRIVE — but the statement they compose is the reference implementation's: its own conditional takes the bypass arm first and never reaches the auto-mode body, and showing both put a pair in front of the model that the reference implementation never shows together. Ruled 2026-09-21 (the registered divergence is withdrawn).
|
|
50
|
+
- **One rule, minus one.** The composition was two independent pushes and a join; it is a choice now. The DECOMPOSER keeps both segments in its vocabulary on purpose: a transcript minted before this version carries a joined body in one engine region, and a leg reading it must still suppress both halves rather than restate one beside it (pinned).
|
|
51
|
+
- **Who is hurt.** Single-seat runs are byte-unchanged — auto alone renders the auto body, bypass alone the bypass body, neither renders nothing. A shell that renders or counts the engine's first-message head for a run holding both facts sees one segment where it saw two. A leg that GAINS the bypass seat mid-session states the bypass segment alone and does not restate the auto paragraph standing in its transcript.
|
|
52
|
+
|
|
53
|
+
### `createApprovalPolicy.approve` takes the outcome object, read by the one screen (#1020; additive; +1 export (type); @cli @server @client-core)
|
|
54
|
+
|
|
55
|
+
- **What was wrong.** The convenience approval door had one spelling for "no" — `false` — and `false` files a PERSON's record. A deployment whose approval channel is a MACHINE (a headless lane with no interactive prompt, a standing posture) could not say so on this door, while the same deployment on the `onAsk` seat says `{allow:false, settledBy:"policy"}` and gets the policy sentence and `policy_refused`. The object shape reached this door as an out-of-contract DEFECT that told the caller to return a boolean.
|
|
56
|
+
- **One reading, two doors.** `approve` now returns `boolean | { allow, settledBy?, reason?, approver?, updatedInput? }` — the same `AskOutcome` object the `onAsk` seat takes — and BOTH doors read it through one screen: the one-read member intake, the strict allow fold, the `settledBy` domain, the attribution screen, the "only a person approves" contradiction, the reason domain and the edit's alias isolation, together with the SETTLEMENT each outcome earns. A second screen here is exactly how one value came to be a policy refusal at one door and a defect at the other. Rule count: +1 arm, 0 special cases.
|
|
57
|
+
- **What the doors still say differently, and why.** A clean verdict's model-facing SENTENCE is each door's own: this seat holds a bare `ToolCallRequest`, so it has no ask message to quote and no delegated-child face, and its human refusal is the long-standing terse line. Contract DEFECTS are the other way round — their sentences come from the shared screen, so a malformed outcome is refused in byte-identical words whichever door it arrived at (pinned, seven shapes).
|
|
58
|
+
- **Two members of the object have no lane on this seat, and both are refused loudly.** `"unavailable"` is a ROUTING answer whose only consumer is the ask resolver's durable re-route. `updatedInput` is an OPERATOR EDIT, and this seat's answer is the deployment POLICY's verdict — an edit riding one leaves the gate as a policy REWRITE, which the restriction chain does not re-judge: only layers composed AFTER the rewriting one ever see the new args. Measured on this branch with a real Runner and `combinePolicies(guard, …)` where `guard` denies `/protected`: an edit from `/safe` to `/protected` EXECUTES through the policy-rewrite channel and is REFUSED through the ask resolution's edit re-adjudication. Carrying the factory's edit into the first channel would have given one value two enforcement rules depending on its door, so the seat declares it has no edit lane and the shared screen refuses; both members are outside the parameter's type as well (`updatedInput?: never`, the spelling a predeclared const or an annotated async return cannot dodge). The pre-existing asymmetry between the two channels is a separate question and is not touched here.
|
|
59
|
+
- **One direction is NOT source-compatible, and it is a TYPE change with no export moved.** Supplying a callback is unaffected: `approve: () => false` and `approve: async () => true` compile exactly as before (measured against v7.26.3 with the same compiler options). What breaks is FORWARDING the library's own callback type — a consumer that derives `type Approve = Parameters<typeof createApprovalPolicy>[0]['approve']` and then INVOKES it expecting a boolean (`async function invoke(a: Approve): Promise<boolean> { return await a(req); }`) now gets `Type 'ApprovalSeatOutcome' is not assignable to type 'boolean'` where v7.26.3 compiled. Measured, both sides, with a control on the supply direction. The repair is one annotation on the forwarder's own return; it is stated here rather than papered over with an overload, because an overload would tighten the SUPPLY side, which is the direction nearly every consumer is on. `npm run handoff:diff` reports +1 / −0 — the outcome type now has an exported NAME (`ApprovalSeatOutcome`, advanced tier), so a host can annotate with it instead of deriving it; that is the repair for the shape above, and an export count is still not what decides whether a release is additive.
|
|
60
|
+
- **Who is hurt at RUNTIME.** Nobody: `true` and `false` are byte-unchanged on both the verdict and the record. A deployment that was RELYING on the object arm being refused (it cannot be — the refusal named it out-of-contract) sees a verdict instead.
|
|
61
|
+
|
|
3
62
|
## 7.26.3 — 2026-09-21
|
|
4
63
|
|
|
5
64
|
### Monitor probes the tracked working directory before it spawns (#1018; behaviour NARROWING; +0 exports; @server @cli @test)
|
|
@@ -3,21 +3,12 @@ import type { AgentDefinition } from "../core/types.js";
|
|
|
3
3
|
* F1 (design/135 批次3 / PARITY-SPOT-AGENT-TYPES-2026-07-10) — the CC 2.1.198 BUILT-IN `Explore` and
|
|
4
4
|
* `Plan` agent types, ported to sema's AgentDefinition roster shape.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - Plan definition `Xor` — pretty.js:486943-486953 (`agentType:"Plan"`, single whenToUse, same six
|
|
13
|
-
* disallowedTools, `tools: qme.tools` = undefined, `model:"inherit"`, `omitClaudeMd:!0`).
|
|
14
|
-
* - Plan system prompt `kBm()` — pretty.js:486874-486930 (verbatim below, unix arm).
|
|
15
|
-
* - Registry `HCe` — pretty.js:487221-487240: Explore+Plan ship behind `iAt()` (487127-487131:
|
|
16
|
-
* env kill-switch `CLAUDE_CODE_DISABLE_EXPLORE_PLAN_AGENTS` + `tengu_slate_ibis` gate DEFAULT ON)
|
|
17
|
-
* ⇒ GA default-on in 198 (and byte-stable through 204/206 — CC-DRIFT-198-206-AGENT-TYPES 锚 1-7).
|
|
18
|
-
* SDK entrypoints ship them too (only `claude-code-guide` is non-SDK); the SDK-form opt-out is
|
|
19
|
-
* `CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS` (487221) → sema's deployment-config analog is
|
|
20
|
-
* `SubagentToolOptions.builtinAgents: false` (config-driven, not env-driven).
|
|
6
|
+
* Both definitions, their `whenToUse` lines and their system prompts are ported VERBATIM from the
|
|
7
|
+
* reference implementation's own (unix + Glob/Grep arm), including the six denied tools
|
|
8
|
+
* (Agent/Artifact/ExitPlanMode/Edit/Write/NotebookEdit) and `model:"inherit"`. Upstream they ship
|
|
9
|
+
* default-on behind an env kill-switch; sema's analog is the deployment-config
|
|
10
|
+
* `SubagentToolOptions.builtinAgents: false` rather than an env variable. Per-symbol source
|
|
11
|
+
* coordinates and the drift re-checks live in the parity register, not here.
|
|
21
12
|
*
|
|
22
13
|
* sema adaptations (deliberate, each recorded):
|
|
23
14
|
* - De-branding (prompt-sovereignty / SUBAGENT_PROMPT precedent): CC's persona first sentences name the
|
package/dist/agents/cascade.d.ts
CHANGED
|
@@ -161,48 +161,44 @@ export declare function runCascade(runner: RunnerSelfSeat, spec: TaskSpec, confi
|
|
|
161
161
|
*
|
|
162
162
|
* WHY THE SEAT EXISTS: `runCascade` returns a `TaskResult` and DRAINS each rung's event stream
|
|
163
163
|
* internally, so the host never holds a `TaskStream` for any rung. Without this parameter a
|
|
164
|
-
* deployment
|
|
165
|
-
* workspace — only the per-rung {@link CascadeAttempt} audit rows and the final result.
|
|
164
|
+
* deployment observes nothing a rung did — tool beats, forwarded child progress, the resolved
|
|
165
|
+
* workspace — only the per-rung {@link CascadeAttempt} audit rows and the final result. An
|
|
166
166
|
* observation sink here (`onActivity`, `onForwardEvent`, `onWorkspaceResolved`) is the only way in.
|
|
167
167
|
*
|
|
168
|
-
* ATTRIBUTION: rungs run SEQUENTIALLY
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* arrive after that rung's `onRung`, i.e. inside the next rung's segment.
|
|
168
|
+
* ATTRIBUTION: rungs run SEQUENTIALLY with the SAME object, so one sink receives the rungs' beats
|
|
169
|
+
* CONCATENATED in ladder order. `ToolActivity` carries no rung identity — two rungs running the same
|
|
170
|
+
* tool are indistinguishable — so the boundary a host keys on is {@link CascadeConfig.onRung}, which
|
|
171
|
+
* fires only after its own rung's beats have been delivered (the sole rung ending without one is a
|
|
172
|
+
* durable pause, which returns immediately). Two honesty notes: SUBAGENT events forwarded through
|
|
173
|
+
* `onForwardEvent` carry their own `sourceTaskId`, and a BACKGROUND child outlives the rung that
|
|
174
|
+
* spawned it — its frames can arrive inside the next rung's segment.
|
|
176
175
|
*
|
|
177
|
-
* FAILURE-ISOLATED
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
176
|
+
* FAILURE-ISOLATED, by the same primitive as {@link CascadeConfig.onRung}: engine-invoked through a
|
|
177
|
+
* run-scoped safe notifier — a throwing `onActivity` is contained (first failure per site disclosed
|
|
178
|
+
* via `console.warn`, later ones counted) and never faults the rung. Uncontained, the fault
|
|
179
|
+
* AMPLIFIES: the faulted rung is rejected by the gate, the cascade escalates, and a non-idempotent
|
|
180
|
+
* tool the rung had ALREADY executed runs AGAIN on the next rung — the MEASURED HAZARD pin holds
|
|
181
|
+
* containment.
|
|
183
182
|
*
|
|
184
183
|
* FORWARDED WHOLE, NOT FILTERED: every field rides every rung, including the ref-valued ones
|
|
185
184
|
* (`peerSelfRef`, `ownOrgAdmissionRef`) whose own contract is one ref per RUN — wiring those here
|
|
186
|
-
* makes the ladder's rungs share one carrier. The seat is meant for observation sinks; anything
|
|
187
|
-
* is a choice the caller is making explicitly, not a default this layer applies.
|
|
185
|
+
* makes the ladder's rungs share one carrier. The seat is meant for observation sinks; anything
|
|
186
|
+
* else is a choice the caller is making explicitly, not a default this layer applies.
|
|
188
187
|
*
|
|
189
188
|
* `isolation: "worktree"` in particular rides through here, where the verification gate REFUSES it —
|
|
190
|
-
* an asymmetry with a reason. That field mints a NEW workspace per RUN
|
|
189
|
+
* an asymmetry with a reason. That field mints a NEW workspace per RUN: the gate's runs are one
|
|
191
190
|
* artifact judged and repaired by several legs, so separate workspaces would have the verifier
|
|
192
|
-
* grading files the implementation never wrote to
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* this function's: each rung's factory-minted env is torn down at the end of that rung's run, and
|
|
199
|
-
* what teardown does to the workspace is the factory's business.
|
|
191
|
+
* grading files the implementation never wrote to, while a ladder's rungs are INDEPENDENT COLD
|
|
192
|
+
* RE-RUNS, so one workspace per rung is the coherent reading. What a host must then know: the
|
|
193
|
+
* returned result is ONE rung's, its work lives in THAT rung's workspace, and no field of the result
|
|
194
|
+
* names it — `onWorkspaceResolved` is the only place the path appears, once per rung in ladder
|
|
195
|
+
* order. Whether it is still readable afterwards is the FACTORY's contract: each rung's
|
|
196
|
+
* factory-minted env is torn down at the end of that rung's run.
|
|
200
197
|
*
|
|
201
|
-
* Absent ⇒ every rung is dispatched
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* reference, and an internals object that already carries a fixed point is forwarded verbatim.
|
|
198
|
+
* Absent ⇒ every rung is dispatched with `undefined`, with ONE design/380 O1② placement delta either
|
|
199
|
+
* way: when neither `placementRoot` nor `rootSessionId` is present, rungs after the first get a
|
|
200
|
+
* fresh shallow copy carrying `placementRoot` = the first rung's resolved sessionId (the ladder's
|
|
201
|
+
* placement fixed point); every other field still rides by the same reference, and an internals
|
|
202
|
+
* object that already carries a fixed point is forwarded verbatim.
|
|
207
203
|
*/
|
|
208
204
|
internals?: RunInternals): Promise<CascadeRunResult>;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Observer agents — CC 2.1.206 parity (逐字锚: docs/CC206-OBSERVER-ANCHORS-2026-07-11.md).
|
|
3
3
|
*
|
|
4
|
-
* A definition may declare `observer: "<agent name>"`: whenever that agent runs, the named agent
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* A definition may declare `observer: "<agent name>"`: whenever that agent runs, the named agent type
|
|
5
|
+
* is auto-spawned in the background, PAIRED with it. The observer receives read-only activity digests
|
|
6
|
+
* of the observed run (assistant text / tool calls / tool results / turn boundaries) and may push a
|
|
7
|
+
* report back through the {@link createObserverReportTool | ObserverReport} tool — the expected steady
|
|
8
|
+
* state is silence. One-way by construction: the observed agent never replies, and SendMessage is
|
|
9
|
+
* refused in BOTH directions.
|
|
10
10
|
*
|
|
11
|
-
* This module is the anchor-faithful half (digest model / renderers / envelope / postamble /
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* This module is the anchor-faithful half (digest model / renderers / envelope / postamble / framing
|
|
12
|
+
* prompt / declaration resolver / pairing state machine / ObserverReport tool); the spawn wiring
|
|
13
|
+
* (event tap on the delegation lanes + auto-spawn + delivery) lives in `subagent.ts`.
|
|
14
14
|
*
|
|
15
15
|
* CC anchor coordinates cited per symbol below refer to `claude-cli-2.1.206.pretty.js`.
|
|
16
16
|
*/
|
|
@@ -86,18 +86,16 @@ export declare function renderObserverDigestBatch(pairing: {
|
|
|
86
86
|
* CC `OIu` (220 `vrd` @374408) — the observer's opening system framing (delivered as the first
|
|
87
87
|
* prompt message).
|
|
88
88
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* tell the observer WHERE its report lands, and in sema the name is the addressable form. Note the
|
|
96
|
-
* ``?? "main"`` fallback is byte-identical to CC on the no-label leg.
|
|
89
|
+
* DIVERGENCE (declared, text unchanged): CC's report-target slot is an IDENTITY —
|
|
90
|
+
* `reportTargetTaskId ?? "main"` (@374409). sema passes the observed agent's NAME (`childAgentName`,
|
|
91
|
+
* see subagent.ts's arm): a delegated child's task id is minted inside the delegation and means
|
|
92
|
+
* nothing to the observer, while the name is exactly what the digests are tagged with
|
|
93
|
+
* (`<{name}-activity>`) and the addressable form here. The ``?? "main"`` fallback is byte-identical
|
|
94
|
+
* on the no-label leg.
|
|
97
95
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
96
|
+
* Declared gap: CC's SECOND framing shape — the coordinator/worker chain (`viaWorkerName`,
|
|
97
|
+
* @374419-374426), where an observer watches a WORKER but reports to the COORDINATOR. sema has one
|
|
98
|
+
* pairing shape (observer ⇄ observed).
|
|
101
99
|
*/
|
|
102
100
|
export declare function observerFramingPrompt(args: {
|
|
103
101
|
observedEnvelopeName: string;
|
|
@@ -5,27 +5,25 @@ import { type ExecutionEnv } from "../internal/harness.js";
|
|
|
5
5
|
import type { TaskNotificationPayload } from "../core/task-notification.js";
|
|
6
6
|
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* refused on the other.
|
|
8
|
+
* The two GRANULARITY facts a delegation's terminal `usage` carries beside tokens/turns/cost. The
|
|
9
|
+
* workflow lane's completion notification publishes `tool_uses` (summed `stats.toolCalls`) and
|
|
10
|
+
* `duration_ms` (wall clock), and the Agent tool's own SYNC report publishes both in its `<usage>`
|
|
11
|
+
* footer, so a delegation TERMINAL face (the background/fork/revive notification and its observer
|
|
12
|
+
* twin) that reported neither would have the same child answering "how much work, how long" on one
|
|
13
|
+
* lane and refusing on the other.
|
|
15
14
|
*
|
|
16
15
|
* PROVENANCE is the workflow lane's — `stats.toolCalls`, the RUN's own count — because that is the
|
|
17
16
|
* quantity the other side of the comparison publishes, and because it is available on every terminal
|
|
18
17
|
* lane, including the revive lane, which has no parent-side forwarding observer at all.
|
|
19
18
|
*
|
|
20
|
-
* WHAT THIS DOES NOT CLOSE,
|
|
21
|
-
*
|
|
19
|
+
* WHAT THIS DOES NOT CLOSE, since the honest scope is narrower than "one name, one meaning": the Agent
|
|
20
|
+
* tool's SYNC `<usage>` footer keeps its own `tool_uses`, counted by the parent-side
|
|
22
21
|
* `toolStatsCounter`, and the two numbers differ in BOTH directions for the same child — that counter
|
|
23
|
-
* EXCLUDES the delegation tool's own calls and INCLUDES descendants' (
|
|
24
|
-
* `stats.toolCalls` counts the direct child's calls and counts its delegations
|
|
25
|
-
* itself delegates therefore reports two different `tool_uses` depending on
|
|
26
|
-
* synchronously or in the background. That footer is a byte-pinned
|
|
27
|
-
* two is a separate ruling, and
|
|
28
|
-
* down rather than a claim that it is gone.
|
|
22
|
+
* EXCLUDES the delegation tool's own calls and INCLUDES descendants' (the reference implementation's
|
|
23
|
+
* nested fold), while `stats.toolCalls` counts the direct child's calls and counts its delegations
|
|
24
|
+
* among them. A child that itself delegates therefore reports two different `tool_uses` depending on
|
|
25
|
+
* whether it ran synchronously or in the background. That footer is a byte-pinned anchored face;
|
|
26
|
+
* reconciling the two is a separate ruling, and this is where the divergence is written down.
|
|
29
27
|
*
|
|
30
28
|
* Knownness follows `delegatedCostField`'s rule exactly: `toolCalls` is optional on the stats face
|
|
31
29
|
* ("undefined means the gateway reported no usage", and a checkpointed resume folds it per-leg), so the
|
|
@@ -39,11 +37,11 @@ import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js
|
|
|
39
37
|
* word is minted on either side.
|
|
40
38
|
*
|
|
41
39
|
* Exported at MODULE level (not from the package index — the same posture as `completedAgentCard` and
|
|
42
|
-
* `classifySubagentError
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
40
|
+
* `classifySubagentError`): the ABSENT arm is the point of the knownness rule and is not reachable
|
|
41
|
+
* through a real child. This runner initializes `stats.toolCalls` to a number on every path, so only a
|
|
42
|
+
* `TaskResult` from elsewhere — a foreign runner, an older checkpoint — carries the
|
|
43
|
+
* optional-and-missing shape the stats face declares, and a pin that only sees a live child cannot
|
|
44
|
+
* tell "absent" apart from "zero".
|
|
47
45
|
*/
|
|
48
46
|
export declare function delegatedGranularityFields(stats: {
|
|
49
47
|
toolCalls?: number;
|
|
@@ -241,17 +239,14 @@ export declare function classifySubagentError(terminal: TerminalCause): {
|
|
|
241
239
|
retryable: boolean;
|
|
242
240
|
} | undefined;
|
|
243
241
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
242
|
+
* The Agent family's **completed** `tool_end.structured` card, assembled from an EXPLICIT field
|
|
243
|
+
* whitelist over the child `TaskResult`.
|
|
246
244
|
*
|
|
247
|
-
* ### What was broken
|
|
248
245
|
* `TaskEvent.tool_end.structured`'s 双面契约 (core/types.ts) states that a host MUST render a
|
|
249
|
-
* delegation's landing report from `structured` and MUST NOT scrape the model-facing text
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* the
|
|
253
|
-
* one card that could never arrive, and the only way to get the child's report was the thing the
|
|
254
|
-
* contract forbids.
|
|
246
|
+
* delegation's landing report from `structured` and MUST NOT scrape the model-facing text — so this
|
|
247
|
+
* card has to carry `type:"agent"` and survive the card projector's allowlist. A bare `{...child}`
|
|
248
|
+
* spread does not: with no `type` key the projector drops it, and the one card the contract names is
|
|
249
|
+
* the one card that can never arrive.
|
|
255
250
|
*
|
|
256
251
|
* ### Reconciling with "never leak the whole child TaskResult"
|
|
257
252
|
* tool-output-projection.ts's own header calls the subagent `details` its example of what must NOT
|
|
@@ -263,37 +258,34 @@ export declare function classifySubagentError(terminal: TerminalCause): {
|
|
|
263
258
|
* - **`checkpointToken`** — the resume CAPABILITY ("never log it or put it in a URL", per its own
|
|
264
259
|
* JSDoc). A durable pause is short-circuited into `durablePauseDelegationReport` well before this
|
|
265
260
|
* point, but that is NOT what keeps the token off the wire: a child whose orchestrator mapped an
|
|
266
|
-
* unexpected `suspended` down to `failed` (`errorCode:"unexpected.suspended"`)
|
|
267
|
-
*
|
|
261
|
+
* unexpected `suspended` down to `failed` (`errorCode:"unexpected.suspended"`) arrives here carrying
|
|
262
|
+
* one, and a spread would publish it with the completed card.
|
|
268
263
|
* - **`checkpointGate`** — the token's other half (who must approve what); useless without the token
|
|
269
264
|
* and part of the same capability story.
|
|
270
265
|
*
|
|
271
266
|
* Everything admitted below is already on the MODEL face of the same tool result (status, result text,
|
|
272
|
-
* failure attribution, run totals, the transcript id) or
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
* whitelist: admitting one is a deliberate edit here, reviewed against this boundary.
|
|
267
|
+
* failure attribution, run totals, the transcript id) or an additive key on `details`
|
|
268
|
+
* (`resolvedModel` / `toolStats` / `worktreePath`). So the WIRE carries exactly the fields a host
|
|
269
|
+
* needs to render the report, and nothing new becomes readable. New `TaskResult` fields do NOT join
|
|
270
|
+
* automatically — that is the point: admitting one is a deliberate edit here.
|
|
277
271
|
*
|
|
278
272
|
* ### What this whitelist actually governs (wider than "the wire")
|
|
279
273
|
* `details` is the tool result's structured face, so this set is ALSO what the in-process consumers
|
|
280
274
|
* see: the parent's `afterToolCall` hook, the persisted tool-result message in the parent transcript,
|
|
281
275
|
* and anything reading `toolResult.details` — not only `tool_end.structured`. Omitting a field here
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* whitelist's reach is the reason their exclusion is worth anything.
|
|
276
|
+
* withholds it from all of them, which is why dropping a machine-actionable field (`retryAfterMs`,
|
|
277
|
+
* the wait hint paired with `errorCode:"usage.window_exhausted"`; `degraded`, the reduced-quality
|
|
278
|
+
* fact) is a real loss. The capability fields stay excluded on the same reading — the whitelist's
|
|
279
|
+
* reach is the reason their exclusion is worth anything.
|
|
287
280
|
*
|
|
288
281
|
* The card keeps `type:"agent"` (rather than minting a second word): CC models the Agent tool's output
|
|
289
282
|
* as one union discriminated by `status`, and both sema cards already carry `status` —
|
|
290
283
|
* `"async_launched"` for the receipt, the child's terminal status here. A consumer branches on it.
|
|
291
284
|
*
|
|
292
285
|
* Exported at MODULE level (not from the package index, same as `classifySubagentError`) so the leak
|
|
293
|
-
* boundary can be pinned
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* whitelist apart from a spread.
|
|
286
|
+
* boundary can be pinned against a child result carrying a token: the `unexpected.suspended` shape is
|
|
287
|
+
* reachable in production but expensive to stage, and a pin that only sees an ordinary completed
|
|
288
|
+
* child cannot tell this whitelist apart from a spread.
|
|
297
289
|
*/
|
|
298
290
|
export declare function completedAgentCard(child: {
|
|
299
291
|
taskId: string;
|
|
@@ -578,21 +570,19 @@ export interface SubagentToolOptions {
|
|
|
578
570
|
* exists at all after the fact.** A SYNC or BACKGROUND child mints its session through
|
|
579
571
|
* `runner.sessions`, never through the parent leg's store, so a deployment that mounts the Agent
|
|
580
572
|
* tool on a DIFFERENT runner than the one serving root tasks has split the transcript lane away
|
|
581
|
-
* from the session lane. (The FORK lane is the deliberate exception
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
586
|
-
* ordinary lanes, all reachable and all silent:
|
|
573
|
+
* from the session lane. (The FORK lane is the deliberate exception: a fork branches the CALLER's
|
|
574
|
+
* session through `ctx.hostSessionFork` — the host Runner's own store — precisely because `runner`
|
|
575
|
+
* may be a split child-execution runner whose store never held the source. A fork child follows the
|
|
576
|
+
* HOST store's fate, an ordinary child this runner's, and one deployment can land one on disk and
|
|
577
|
+
* lose the other.) Three shapes for the ordinary lanes, all reachable and all silent:
|
|
587
578
|
* - durable store on THIS runner whose `release` PRESERVES history (`evict:"forget"`, the
|
|
588
579
|
* `FileStorageBackend` default), no {@link SubagentToolOptions.background}.`agentStore` — the
|
|
589
580
|
* child transcript lands UNPLACED and survives the settle leg's eager release (a cache drop),
|
|
590
581
|
* so post-hoc forensics work even though nothing can ADDRESS it through an `a*` handle. The
|
|
591
|
-
* qualifier is load-bearing
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
* to make the deletion refuse. "Durable" is a statement about restarts, never about release;
|
|
582
|
+
* qualifier is load-bearing: durability and release semantics are INDEPENDENT declarations, and
|
|
583
|
+
* a store declaring itself durable while deleting on release destroys the transcript at settle —
|
|
584
|
+
* the settle leg calls `release`, and no placement makes the deletion refuse. "Durable" is a
|
|
585
|
+
* statement about restarts, never about release;
|
|
596
586
|
* - durable store + `agentStore` — the placed partition (see the mint gate in the background
|
|
597
587
|
* lane): addressable across a restart, list-excluded, joint-reaped with its row. This arm is
|
|
598
588
|
* also the one immune to the evict policy above (the settle leg keeps a confirmed row's
|
|
@@ -602,21 +592,19 @@ export interface SubagentToolOptions {
|
|
|
602
592
|
* reaches disk whatever the root leg's store is. Nothing in the engine compensates: the
|
|
603
593
|
* completion notification's recent steps and the in-memory registry are the only readers, and
|
|
604
594
|
* both die with the process.
|
|
605
|
-
* The asymmetry this produces
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
* There is deliberately NO manifest field
|
|
595
|
+
* The asymmetry this produces reads as a bug from the outside: the `run_workflow` tool is mounted by
|
|
596
|
+
* the ENGINE on the run's own runner (prepare-task's `runnerSelf`), so a workflow node's child
|
|
597
|
+
* session always follows the ROOT store, while this tool is mounted by the DEPLOYMENT and follows
|
|
598
|
+
* whatever runner it was handed. One data root can hold every workflow node's transcript and none
|
|
599
|
+
* of the Agent tool's.
|
|
600
|
+
* There is deliberately NO manifest field answering "does a child transcript reach disk here":
|
|
611
601
|
* `WiringManifest.fleet.subagentTranscripts` is the ADDRESSABILITY tier and folds to `"none"` on
|
|
612
|
-
* the absent-row-store arm before it
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
* .jsonl` + a `.meta.json` sidecar), so "the child conversation is readable afterwards" is a
|
|
619
|
-
* property CC never makes conditional on a store tier.
|
|
602
|
+
* the absent-row-store arm before it looks at a session store, so it cannot separate the first
|
|
603
|
+
* shape above from the third. What DOES answer it is this runner's store's own pair of
|
|
604
|
+
* declarations — `sessions.durability` and its release/evict policy — read together. Upstream
|
|
605
|
+
* (CC 2.1.250) writes a sub-agent transcript unconditionally to its own file beside the parent
|
|
606
|
+
* session, so "the child conversation is readable afterwards" is never conditional on a store
|
|
607
|
+
* tier there.
|
|
620
608
|
*/
|
|
621
609
|
runner: RunnerSelfSeat;
|
|
622
610
|
/**
|
|
@@ -13,14 +13,12 @@ import type { ToolSpec } from "../core/types.js";
|
|
|
13
13
|
* newly-added sensitive tool to every agent. Names not present in the pool are ignored (no error) — an
|
|
14
14
|
* allow/deny list is a filter over what's available, not an assertion that those tools exist.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* extension (no upstream deny-list exists): an allow-typo silently narrows (safe direction), a
|
|
23
|
-
* deny-typo silently widens (unacceptable) — each face follows its own failure direction.
|
|
16
|
+
* Anchored (dual-leg verified): this spawn-time item-level filter IS the upstream shape for allow
|
|
17
|
+
* entries — CC's resolveAgentTools partitions unknown names into an `invalidTools` bucket nobody
|
|
18
|
+
* consumes at runtime, and a live 2.1.207 probe shows the agent stays listed/delegable with the
|
|
19
|
+
* unknown item silently dropped, zero warnings. The asymmetric prepare-time fail-loud for
|
|
20
|
+
* `TaskSpec.agents` DENY entries is a deliberate sema extension (no upstream deny-list exists): an
|
|
21
|
+
* allow-typo silently narrows (safe direction), a deny-typo silently widens (unacceptable).
|
|
24
22
|
*
|
|
25
23
|
* Note: `"*"` in `allowTools` is the "allow all" sentinel, so a tool literally NAMED `"*"` cannot be
|
|
26
24
|
* the sole allowed tool via this path (tool names are conventionally identifiers, so this is moot).
|
|
@@ -28,10 +26,10 @@ import type { ToolSpec } from "../core/types.js";
|
|
|
28
26
|
/**
|
|
29
27
|
* Membership check for ONE tool name under the same allow/deny semantics as {@link resolveToolSubset}
|
|
30
28
|
* (canonicalized both sides; `"*"` allow sentinel; deny wins). Used by the delegation tool to decide
|
|
31
|
-
* whether a chosen agent DEFINITION may re-delegate: a per-agent allow/deny that excludes the
|
|
32
|
-
* tool's own name suppresses the nested delegation tool (
|
|
33
|
-
*
|
|
34
|
-
*
|
|
29
|
+
* whether a chosen agent DEFINITION may re-delegate: a per-agent allow/deny that excludes the
|
|
30
|
+
* delegation tool's own name suppresses the nested delegation tool (upstream parity — the built-in
|
|
31
|
+
* Explore/Plan denials include Agent, and the delegation tool is filtered like any other). The
|
|
32
|
+
* TOOL-level allow/deny keeps its own semantics (delegation governed by `maxDepth`).
|
|
35
33
|
*/
|
|
36
34
|
export declare function toolNameAllowed(name: string, allowTools?: ReadonlyArray<string>, denyTools?: ReadonlyArray<string>): boolean;
|
|
37
35
|
export declare function resolveToolSubset(pool: ReadonlyArray<ToolSpec>, allowTools?: ReadonlyArray<string>, denyTools?: ReadonlyArray<string>): ToolSpec[];
|