@sema-agent/core 7.27.0 → 7.28.1
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 +27 -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/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/shell/lex.d.ts +279 -0
- package/dist/shell/lex.js +1253 -0
- 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/wire-changes/core@7.28.1.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,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.28.1 — 2026-09-22
|
|
4
|
+
|
|
5
|
+
> **7.28.0 was never published.** Its first `npm publish` was cut off mid-upload by a pipe on the publisher's side; the registry keeps a *staged* half-upload under that number and refuses every later publish of it (`E409 Cannot publish over previously staged version`), and `npm unpublish` does not clear a staged version. This release is the same tree under the next patch number — tag `v7.28.1`; no `v7.28.0` tag or package exists.
|
|
6
|
+
|
|
7
|
+
### 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)
|
|
8
|
+
|
|
9
|
+
- **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.
|
|
10
|
+
- **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.
|
|
11
|
+
- **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.
|
|
12
|
+
- **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).
|
|
13
|
+
- **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).
|
|
14
|
+
- **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).
|
|
15
|
+
- **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).
|
|
16
|
+
- **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).
|
|
17
|
+
- **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).
|
|
18
|
+
|
|
19
|
+
### 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)
|
|
20
|
+
|
|
21
|
+
- **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).
|
|
22
|
+
- **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.
|
|
23
|
+
- **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`).
|
|
24
|
+
- **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`.
|
|
25
|
+
- **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`.
|
|
26
|
+
- **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).
|
|
27
|
+
- **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.
|
|
28
|
+
- **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.
|
|
29
|
+
|
|
3
30
|
## 7.27.0 — 2026-09-22
|
|
4
31
|
|
|
5
32
|
### 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)
|
|
@@ -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[];
|