@sema-agent/core 5.13.0 → 5.14.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 +296 -0
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +4 -0
- package/dist/agents/subagent.js +133 -41
- package/dist/brain/anthropic.js +33 -10
- package/dist/brain/context-overflow.d.ts +20 -0
- package/dist/brain/context-overflow.js +58 -0
- package/dist/brain/open-responses.js +24 -10
- package/dist/brain/openai.js +29 -11
- package/dist/brain/request-params.d.ts +2 -0
- package/dist/brain/request-params.js +16 -0
- package/dist/brain/stream-engine.d.ts +9 -1
- package/dist/brain/stream-engine.js +256 -27
- package/dist/brain/timeout.d.ts +1 -0
- package/dist/brain/timeout.js +1 -0
- package/dist/core/a2a.d.ts +2 -2
- package/dist/core/a2a.js +3 -3
- package/dist/core/ask-question.d.ts +47 -2
- package/dist/core/ask-question.js +209 -28
- package/dist/core/background-agent-store.d.ts +2 -0
- package/dist/core/checkpoint-store.d.ts +41 -17
- package/dist/core/checkpoint-store.js +114 -3
- package/dist/core/hooks.d.ts +24 -2
- package/dist/core/hooks.js +97 -10
- package/dist/core/human-input-projection.d.ts +12 -0
- package/dist/core/human-input-projection.js +27 -0
- package/dist/core/mcp.d.ts +7 -2
- package/dist/core/mcp.js +7 -7
- package/dist/core/memory-admission.d.ts +4 -0
- package/dist/core/memory-admission.js +3 -0
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +16 -6
- package/dist/core/runner/prepare-task.js +301 -24
- package/dist/core/runner/runtask.d.ts +3 -6
- package/dist/core/runner/runtask.js +186 -36
- package/dist/core/runner/tool-output-projection.js +1 -0
- package/dist/core/session-store.d.ts +3 -0
- package/dist/core/session-store.js +4 -0
- package/dist/core/session.d.ts +1 -0
- package/dist/core/store-contracts/background-agent-store-contract.js +19 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +62 -3
- package/dist/core/task-notification.d.ts +2 -0
- package/dist/core/task-notification.js +5 -3
- package/dist/core/task-registry-agent.d.ts +1 -0
- package/dist/core/task-registry-agent.js +6 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/tool-policy.d.ts +5 -0
- package/dist/core/tool-policy.js +2 -1
- package/dist/core/types.d.ts +32 -1
- package/dist/core/wiring-manifest.d.ts +97 -0
- package/dist/core/wiring-manifest.js +186 -0
- package/dist/engine/compaction/compaction.js +2 -2
- package/dist/engine/harness/agent-harness.d.ts +2 -1
- package/dist/engine/harness/agent-harness.js +8 -1
- package/dist/engine/harness/types.d.ts +3 -1
- package/dist/engine/llm/types.d.ts +7 -0
- package/dist/engine/llm/types.js +8 -1
- package/dist/engine/session/import-validate.d.ts +6 -1
- package/dist/engine/session/import-validate.js +29 -6
- package/dist/engine/session/memory-repo.d.ts +3 -1
- package/dist/engine/session/memory-repo.js +2 -2
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +4 -0
- package/dist/orchestration/run-workflow-tool.js +3 -0
- package/dist/orchestration/workflow-types.d.ts +8 -0
- package/dist/orchestration/workflow-types.js +14 -0
- package/dist/orchestration/workflow.d.ts +4 -0
- package/dist/orchestration/workflow.js +134 -5
- package/dist/prompts/default.js +1 -1
- package/dist/stores/file/checkpoint-store.d.ts +3 -5
- package/dist/stores/file/checkpoint-store.js +31 -2
- package/dist/stores/file/index.js +1 -1
- package/dist/stores/file/session-store.d.ts +3 -1
- package/dist/stores/file/session-store.js +2 -2
- package/dist/stores/file/shared-ledger.js +8 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -0
- package/dist/tools/fs/bash-readonly-classifier.js +94 -0
- package/dist/tools/fs/fs-bash.js +31 -12
- package/dist/tools/fs/safety.js +34 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,301 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.14.0 — 2026-08-06
|
|
4
|
+
|
|
5
|
+
### BREAKING
|
|
6
|
+
|
|
7
|
+
- **`CheckpointStore.setPendingSteer` APPENDS to a bounded ordered queue instead of overwriting a single seat**
|
|
8
|
+
(design/171 §5.3/§6.1/§6.3). Two people steering one durably-suspended run used to mean the second steer
|
|
9
|
+
silently destroyed the first — an undelivered operator instruction, gone with no record that anything was
|
|
10
|
+
lost. `CheckpointState` gains `pendingSteerQueue: PendingSteerEntry[]`
|
|
11
|
+
(`{text, trusted, actor?, seq, inputId, priority?}`); resume drains the WHOLE queue in `seq` order, each
|
|
12
|
+
entry framed under its own trust semantics. New exports: `readPendingSteerQueue` (the single read point —
|
|
13
|
+
it folds a pre-queue row's legacy `pendingSteer` seat in as member 0, so old checkpoints deliver unchanged
|
|
14
|
+
with no migration step), `appendPendingSteer` (the shared append every backend must build its next queue
|
|
15
|
+
with: bounds + `seq` mint live there, not once per store), `PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES` (48,000 —
|
|
16
|
+
under a MySQL `TEXT` column with UTF-8 headroom), `MAX_PENDING_STEER_ENTRIES` (derived from it and
|
|
17
|
+
`MAX_PENDING_STEER_CHARS`), `PENDING_STEER_FROZEN_FIELDS` / `ACTOR_ASSERTION_FROZEN_FIELDS`, and the
|
|
18
|
+
`ActorAssertion` type (`{id (namespaced), hostAsserted, issuer?}` — attribution ONLY, never authority;
|
|
19
|
+
`hostAsserted` must be derived by the host from ingress credentials and never taken from a request body).
|
|
20
|
+
Bounds are fail-loud: crossing either throws the **new error code `steering.queue_full`** rather than
|
|
21
|
+
evicting an accepted instruction. `validatePendingSteer` now REFUSES an unknown field
|
|
22
|
+
(`steering.invalid_content`) instead of dropping it, so a producer one release ahead learns its field would
|
|
23
|
+
not survive the parked leg. Idempotency moved from last-writer-wins to the `inputId` key: re-appending the
|
|
24
|
+
same `inputId` with an IDENTICAL payload is a no-op returning `true`, while the same `inputId` carrying
|
|
25
|
+
DIFFERENT content is refused with the **new error code `steering.duplicate_input_id`** (swallowing it would
|
|
26
|
+
reintroduce exactly the silent instruction loss the queue replaces); an absent `inputId` is minted (uuidv7),
|
|
27
|
+
and the synthetic id a pre-queue seat reads back as is reserved against caller supply. The file backend's
|
|
28
|
+
ledger replay also gained a fail-loud arm: an event kind the reader does not recognise now refuses the whole
|
|
29
|
+
replay (`checkpoint.unsupported_version`) instead of folding in as a silent no-op — **note that a binary
|
|
30
|
+
released BEFORE this one still ignores unknown kinds, so rolling back across the queue event loses steers
|
|
31
|
+
parked in the un-compacted ledger tail.**
|
|
32
|
+
**Consumer flips**: ① any code reading `state.pendingSteer` directly sees `undefined` for every new write —
|
|
33
|
+
read through `readPendingSteerQueue`; ② probes pinning "a second setPendingSteer overwrites the first" red;
|
|
34
|
+
③ a custom `CheckpointStore` must round-trip BOTH state fields and build its next queue with
|
|
35
|
+
`appendPendingSteer` (the cross-backend contract kit gained the queue arms and will fail it otherwise);
|
|
36
|
+
④ `steering.queue_full` and `steering.duplicate_input_id` are new members of the `CheckpointError` code
|
|
37
|
+
union — exhaustive consumers add both arms.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **Shared-session attribution (design/171, core half).** Four pieces, one contract: ① `UserMessage.actor`
|
|
42
|
+
carries an `ActorAssertion` (attribution ONLY, never authority; snapshot-minted at the API boundary, wire
|
|
43
|
+
metadata stripped). ② ONE projection point (`human-input-projection.ts`, exported `projectHumanInput` /
|
|
44
|
+
`buildHumanInputEvent`) renders the speaker envelope for every human-input carrier — objective, live steer,
|
|
45
|
+
nextTurn, parked-steer resume frames, wake — as `[from "<id>"]` (+ ` (unverified)` when the host did not
|
|
46
|
+
derive the identity from ingress credentials), through the same neutralizers as the external-notification
|
|
47
|
+
header, always INSIDE the trust frame it attributes. No actor, or `source:"system"` ⇒ byte-identical
|
|
48
|
+
passthrough, so every pre-171 caller and single-user host is pinned unchanged. ③ **New TaskEvent arm
|
|
49
|
+
`human_input`** — the lifecycle ledger of who fed the run what: `{inputId, sessionSeq (leg-scoped),
|
|
50
|
+
carrier, source, delivery, issuer?, actor?, principal?, entryId?}`; live emissions precede the commit and
|
|
51
|
+
omit `entryId` (join via `message_committed` + stream order). Exhaustive TaskEvent switches need an arm.
|
|
52
|
+
④ The compaction attribution invariant: speaker labels survive summarization across all three summary-path
|
|
53
|
+
variants (the summarizer is instructed to preserve `[from …]` attributions).
|
|
54
|
+
|
|
55
|
+
- **Assembly self-evidence (design/173, all eight items).** The engine can now PROVE what it wired instead of
|
|
56
|
+
the operator inferring it from behavior:
|
|
57
|
+
- `describeStaticWiring(spec, deps)` (package-root export) — the pure static half of the wiring manifest;
|
|
58
|
+
per-leg EFFECTIVE manifests are built at prepare and emitted as the **new TaskEvent arm `wiring_manifest`**
|
|
59
|
+
(root/child/resume legs each emit their own). Together with `human_input` above this release takes
|
|
60
|
+
`TaskEvent` **16→18** — an exhaustive switch adds TWO arms, not one. A child leg's manifest rides the
|
|
61
|
+
CHILD's own stream and is deliberately not in the parent-forwarding whitelist: "every leg self-evidences"
|
|
62
|
+
means on its own stream; a host that wants a delegated child's manifest subscribes to the child. The manifest covers ask (form/provenance/effective),
|
|
63
|
+
question (three-valued channel state incl. the engine-stripped bg lane, plus the composition-lie flag
|
|
64
|
+
`interactiveToolsWithoutDeliveryFace`), elicit, the park lane (capability vs effective policy, reason
|
|
65
|
+
codes, checkpoint durability), session-store durability, fleet seats, and a governance presence section
|
|
66
|
+
stamped `audience:"operator"` — **a serving layer forwarding this event to a multi-tenant stream MUST
|
|
67
|
+
project the governance section for operators only; no projection ⇒ do not disclose**. `configFingerprint`
|
|
68
|
+
(sha256 prefix over the manifest's own resolved facts) correlates legs that ran under the same assembly.
|
|
69
|
+
- **Store durability declarations**: `SessionStore`/`CheckpointStore` gain `readonly durability?`
|
|
70
|
+
("durable" | "process-local"); absent reads fail-closed as process-local, unparseable is refused loud
|
|
71
|
+
(`config.store_durability_invalid`). Built-ins declare themselves; an armed park lane over a
|
|
72
|
+
process-local store is now a REPORTABLE degrade shape instead of a silent one.
|
|
73
|
+
- **`TaskSpec.interactionPosture` / `RunnerDeps.interactionPosture`** ("interactive" | "headless") with a
|
|
74
|
+
fail-loud prepare door: posture `"interactive"` refuses a leg whose resolved wiring cannot reach a human
|
|
75
|
+
(`config.interaction_posture` / `config.interaction_wiring`) instead of silently auto-answering. Child
|
|
76
|
+
legs resolve `spec ?? parent posture (trusted internals) ?? deps`. A deployment that declares interactive
|
|
77
|
+
at deps level must supply a persistent question face for session-scoped bg legs — the door refusing that
|
|
78
|
+
leg is the honest reading of an unfulfillable declaration.
|
|
79
|
+
- **AskUserQuestion's three exits mint an in-band card** (`details.type: "ask-question"`, a new
|
|
80
|
+
`CC_DETAIL_TYPES` member): `continuationSource: "human_response" | "synthetic_self_answer_instruction"`
|
|
81
|
+
plus reason codes (a THREE-member closed set: `seam_absent` / `callback_failed` /
|
|
82
|
+
`declined_unavailable`), so a consumer can tell a human answer from the engine's self-answer fallback
|
|
83
|
+
without prose-matching. The card is a two-arm union — the coded-failure arm (`question.human_channel_failed` /
|
|
84
|
+
`question.human_unavailable`) carries neither `continuationSource` nor `runContinues`, so consumers
|
|
85
|
+
must branch on `code` FIRST and only then read `continuationSource`. Under posture `"interactive"`, the callback-failed
|
|
86
|
+
arm becomes a coded failure (`question.human_channel_failed`) unless the explicit
|
|
87
|
+
`interactiveQuestionFallback` knob opts back into degrade; the degrade path emits onError
|
|
88
|
+
(phase `"degraded"`, classification `"no-human-autoanswered"`).
|
|
89
|
+
- **The four question-face strip sites pair-produce a disclosure flag** through one helper: when the engine
|
|
90
|
+
deliberately strips a spawn turn's per-request question face from a bg/fork child, the manifest reports
|
|
91
|
+
the channel as `"stripped_bg_lane"` (flag minted only when a face was actually stripped).
|
|
92
|
+
- **`effectiveShellGate === "off"` with a real writable shell now leaves a per-prepare forensic note**
|
|
93
|
+
(onError phase `"config"`, classification `"shell-gate-off"`) — the assembly state that previously had
|
|
94
|
+
zero observable trace. onError `classification` is filled on the config phase for the first time.
|
|
95
|
+
- `isDelegatedAgentTerminal` is re-exported at the package root so a serving layer's escalation code shares
|
|
96
|
+
core's own terminal classification.
|
|
97
|
+
|
|
98
|
+
- **The brain honors a provider's explicit retry verdict, and recovers from the context-overflow 400.**
|
|
99
|
+
① The `x-should-retry` response header is now read double-sided: `true` retries a normally-terminal status,
|
|
100
|
+
`false` stops retrying a normally-retryable one; header absent ⇒ prior behavior byte-identical.
|
|
101
|
+
② The `input length and max_tokens exceed context limit` 400 (previously terminal `invalid_request`) now
|
|
102
|
+
adaptively lowers `max_tokens` and retries, with two loud guards: a floor (3000 output tokens — at the
|
|
103
|
+
floor and still overflowing ⇒ give up) and a no-progress check (same error after a cut ⇒ give up); every
|
|
104
|
+
cut is disclosed as a status frame. The narrow error-shape parser refuses anything it does not recognize
|
|
105
|
+
(prior terminal behavior preserved).
|
|
106
|
+
|
|
107
|
+
- **Question sync-first (design/174).** With a live question face, an adjudicated `AskUserQuestion` is
|
|
108
|
+
answered in-stream — same turn, no checkpoint; the durable park is what happens when nobody is there
|
|
109
|
+
(no live face / the face affirms nobody is reachable / `forceDurableGate` — and the third park-lane arming,
|
|
110
|
+
the safety vocabulary, behaves the same: **any park-lane arming keeps the park**, sync-first only applies
|
|
111
|
+
to the durable-approval posture with a live face). Assemblies without a live face are pinned byte-identical
|
|
112
|
+
to the previous release. `OnQuestion` may now return `{kind: "unavailable"}` — implementers additive,
|
|
113
|
+
**callers BREAKING** (`.answers` direct access goes red; `isQuestionUnavailable` is exported for relaying
|
|
114
|
+
wrappers). `AskQuestionRequest` gains `boundInputHash` and an engine-minted per-delivery `deliveryId`
|
|
115
|
+
(the identity; `(taskId, toolCallId, boundInputHash)` repeats for two identical sequential questions and is
|
|
116
|
+
reconciliation data only). The question tool executes in its own batch (`executionMode: "sequential"`).
|
|
117
|
+
New closed-set members: `SyntheticContinuationReason` += `declined_unavailable`; card `code` +=
|
|
118
|
+
`question.human_unavailable`; onError classification += `"unconsumed-human-answer"` and
|
|
119
|
+
`"interaction-posture-refused"`.
|
|
120
|
+
|
|
121
|
+
- **Pre-release verification hardening (test AI's A/B groups + the convergence review).**
|
|
122
|
+
- A question outcome claiming NEITHER arm (no `unavailable` discriminant, no `answers` array — a failed
|
|
123
|
+
decode) is now refused as a delivery failure instead of being read as "the user selected nothing";
|
|
124
|
+
the explicit `{answers: []}` stays a real answer. The resume lane applies the same rule pre-CAS and
|
|
125
|
+
captures the redeemed answer ONCE (`structuredClone`) — the persisted outcome and the delivered answer
|
|
126
|
+
are the same snapshot, so a stateful getter can no longer make audit and delivery diverge.
|
|
127
|
+
- **`TaskResult.strandedHumanAnswers`** (new field): `{deliveryId, toolCallId}` records of questions a
|
|
128
|
+
person answered that no call executed to collect — keyed by the engine-minted delivery identity, since
|
|
129
|
+
call ids can repeat (two lost answers on one call id are two records). This is the MANDATORY disclosure
|
|
130
|
+
face; the `onError` (classification `"unconsumed-human-answer"`) alert remains as an additional lane.
|
|
131
|
+
An answer arriving after the result is already terminal can only reach `onError` — the structural limit
|
|
132
|
+
of a result face; a value that does not READ as an answer is never recorded as one.
|
|
133
|
+
- **`AskRequest.riskAxes`** (additive): `{irreversible?, egress?}`, filled by the gate from the SAME
|
|
134
|
+
resolved axes the safety tightens read (declared tiers + `toolAxes` folds + shellGate marks; `"maybe"`
|
|
135
|
+
counts as irreversible). Either axis ABSENT = the engine did not judge it — consumers must render
|
|
136
|
+
"unjudged", never fold absence to `false`. An EXPLICIT caller negative (`egress:false` /
|
|
137
|
+
`irreversibility:"never"` via MCP/A2A `toolAxes`) now survives the override algebra and reports as a
|
|
138
|
+
judged `false` — enforcement stays tighten-only and ignores negatives.
|
|
139
|
+
- **`WiringManifest.interaction.posture`** (new section): the posture declaration ("interactive" /
|
|
140
|
+
"headless" / "absent") joins the manifest — previously the one interactive-face declaration it did not
|
|
141
|
+
report. A posture refusal now also emits an onError frame (phase `"config"`,
|
|
142
|
+
classification `"interaction-posture-refused"`): the coded `TaskResult.errorCode` remains the primary
|
|
143
|
+
carrier (neither entry point throws), the frame serves hosts wired only to `onError`.
|
|
144
|
+
- **`configFingerprint` now hashes the ASSEMBLY, excluding leg identity AND seat provenance** (the
|
|
145
|
+
delegation lane re-homes a parent's deps face onto the child's spec seat — same callback, different
|
|
146
|
+
seat) — root/child/resume legs of one assembly really do share a fingerprint (its advertised use).
|
|
147
|
+
Fingerprint VALUES change with this release; pin the equality relation, not literals.
|
|
148
|
+
Presence condition for the resume leg: a resume's assembly comes from the `ResumeTaskConfig` you
|
|
149
|
+
pass, not from the root's spec — omit a seam face there (e.g. `onQuestion`) and the resume leg
|
|
150
|
+
HONESTLY reports a different assembly (and a different fingerprint), because that leg really has no
|
|
151
|
+
in-stream answerer. Re-supply the same faces on resume to keep the fingerprint equal.
|
|
152
|
+
- The synthetic-continuation disclosure dedup is **per ask-call** (key = tool-call id; one call may carry
|
|
153
|
+
1–4 questions and discloses once) and scoped to ONE leg — a continuation leg re-discloses, and a host
|
|
154
|
+
aggregating across legs dedups on `(sessionId, toolCallId)` itself.
|
|
155
|
+
|
|
156
|
+
- **Capture-domain narrowing (design/174 convergence review, rounds 9–14).** Deployment-supplied resume
|
|
157
|
+
values are read ONCE and captured as canonical plain data; the canonicalizer refuses what the JSON
|
|
158
|
+
checkpoint store cannot faithfully round-trip. Concretely:
|
|
159
|
+
- resume `updatedInput` and a content-ask `answer` now REFUSE (pre-CAS, `checkpoint.invalid_outcome`,
|
|
160
|
+
checkpoint stays pending) values carrying Map/Set/Date/buffers/class instances, shared references or
|
|
161
|
+
cycles, sparse or expando arrays, non-finite numbers, bigint/symbol/function anywhere in the tree;
|
|
162
|
+
object keys with `undefined` values drop and `-0` normalizes to `0` (JSON semantics, applied at capture
|
|
163
|
+
so audit, replay comparison and execution all read the same value);
|
|
164
|
+
- `decision` is domain-checked at capture: anything other than `"allow"`/`"deny"` refuses pre-CAS
|
|
165
|
+
(previously an unknown word could fall past the deny check into the execute arm);
|
|
166
|
+
- `updatedInput: null` flows into the tool's own validation and fails THERE, instead of silently
|
|
167
|
+
executing the ORIGINAL arguments while the record said the input was rewritten;
|
|
168
|
+
- the gate discriminant and every `policy_ask` field are read once into a plain twin consumed by all
|
|
169
|
+
guards, the env-failed replay equality, persistence and the answer face; array equality visits every
|
|
170
|
+
index and compares named non-index keys;
|
|
171
|
+
- upgrade note: a winner persisted RAW by an older release still replays (equality compares canonical
|
|
172
|
+
forms — no checkpoint migration), but an OLD row whose winner is outside the new domain fails closed
|
|
173
|
+
on replay; the row stays recoverable by re-deciding.
|
|
174
|
+
- The direct tool lane applies the same one reader: an answer payload with a throwing accessor refuses
|
|
175
|
+
the WHOLE outcome (no partial salvage of well-formed siblings), and `{}` is a failed decode, not
|
|
176
|
+
"selected nothing" — `{answers: []}` remains the legitimate empty selection.
|
|
177
|
+
|
|
178
|
+
- **`AskRequest.boundInputHash`** (additive): the canonical digest of the presented args, minted once at
|
|
179
|
+
the `resolveAsk` chokepoint over the retained snapshot — the SAME `boundInputHashOf` digest a durable
|
|
180
|
+
park binds its checkpoint to, so an aggregating approver can reconcile a synchronous ask row against a
|
|
181
|
+
parked checkpoint row for the same call on `(toolCallId, hash)` equality. Reconciliation metadata only
|
|
182
|
+
(repeats for identical args; re-minted per `updatedInput` edit round); a caller-supplied value is not
|
|
183
|
+
trusted over the chokepoint's own.
|
|
184
|
+
|
|
185
|
+
- **Cross-process revival inherits the row's org-admission verdict (issue #22, tier-3 half).**
|
|
186
|
+
`BackgroundAgentRecord` gains `admittedOrgScopes?: string[]` and `admittedOrgWriteScope?: string | null`
|
|
187
|
+
(both additive): each leg writes its own adjudicated org verdict at the injector-ready barrier, and a
|
|
188
|
+
tier-3 claim seeds the revived child's admission fold from the row (seed ∩ live — narrow-only, the same
|
|
189
|
+
fold the in-process half uses). A row with the fields ABSENT reads as ZERO admission (`{scopes: [],
|
|
190
|
+
writeScope: null}`) — "no record" and "adjudicated to nothing" deliberately converge fail-closed.
|
|
191
|
+
Store contract additions (the cross-backend kit enforces both): the two fields round-trip verbatim
|
|
192
|
+
(`null` write-scope stays `null`), and ABSENT must read back absent, never materialize as `[]`.
|
|
193
|
+
Behavioral note: a revival leg that carries a seeded verdict now counts as governed, so an `org:`-shaped
|
|
194
|
+
scope in a non-v2 spelling is refused on that leg even with no resolver configured. Fork rows are not
|
|
195
|
+
written (they refuse tier-3 claims).
|
|
196
|
+
|
|
197
|
+
- **`task_progress` gains `taskType`** (additive; new exported type `DelegationTaskType` =
|
|
198
|
+
`"background_agent" | "workflow"`): the discriminator a consumer needs to route a progress frame
|
|
199
|
+
without inferring the task's kind from its id shape. Stamped at both mint sites for background/fork
|
|
200
|
+
legs and both workflow spawn legs (retained-resume included); absent on a synchronous child's frames —
|
|
201
|
+
absence means "no fleet row", tolerate it.
|
|
202
|
+
|
|
203
|
+
- **The scratchpad prompt section states volatility**: one added sentence ("Treat it as ephemeral — it may
|
|
204
|
+
not survive a long suspension or a resume on a different worker; keep durable outputs in the working
|
|
205
|
+
directory"); the CC-verbatim remainder is byte-unchanged. sema runs under durable suspend / multi-replica
|
|
206
|
+
serving where the scratchpad is a local-disk copy; without the sentence a model parks critical
|
|
207
|
+
intermediates in a directory that can vanish mid-task.
|
|
208
|
+
|
|
209
|
+
- **`shellGate:"classify"` now auto-allows a bounded read-only polling loop** (`classifyBoundedReadonlyPollLoop`,
|
|
210
|
+
consulted by `bashReversibilityProbe` only after the plain compound face rejects). A monitoring consumer's
|
|
211
|
+
core idiom is `for i in $(seq 1 8); do tail -n 5 x.log; sleep 2; done`; the compound classifier rejects any
|
|
212
|
+
control structure (`for` is not an allowlisted name), so `classify` behaved like `always` for the Monitor
|
|
213
|
+
tool — an approval prompt on every poll. The new arm accepts EXACTLY that grammar and nothing else: a loop
|
|
214
|
+
head with a LITERAL finite bound (`$(seq <int> <int>)`, ascending `{<int>..<int>}`, or a literal word list,
|
|
215
|
+
capped at 120 iterations), a body of `;`-separated allowlisted readers plus a literal `sleep` (≤600s), the
|
|
216
|
+
SAME read-boundary scan the plain face runs (RB-412/413, unchanged; an unexpanded glob rejects — stricter
|
|
217
|
+
than the plain face), and everything else banned wholesale (redirection, pipes, backgrounding, substitution
|
|
218
|
+
other than the one `$(seq)` exemption, subshells, braces, escapes, nested control structures, and any
|
|
219
|
+
non-ASCII/control whitespace). The loop variable must be a single lowercase letter, so it cannot shadow
|
|
220
|
+
`PATH`/`IFS`/`LD_PRELOAD` and alter how the body's commands resolve. Additive and fail-closed: it only ever
|
|
221
|
+
converts a rejection into an allow for this exact shape, never the reverse, and any unparseable input falls
|
|
222
|
+
through to a rejection — so a deployment on `off`/`always`, or one not polling, is byte-identical. New
|
|
223
|
+
exports from the fs tools barrel: `classifyBoundedReadonlyPollLoop`, `POLL_LOOP_MAX_BEATS` (120),
|
|
224
|
+
`POLL_LOOP_MAX_SLEEP_SECONDS` (600).
|
|
225
|
+
|
|
226
|
+
- **`HookToolContext.env`** (additive): a read-only path-resolution capability face over the env the task's
|
|
227
|
+
hands run against, on the PreToolUse / PostToolUse / PostToolUseFailure contexts alike. A hook that judges
|
|
228
|
+
paths (containment, symlink destinations) must resolve them in the filesystem the tools actually write to —
|
|
229
|
+
for a sandboxed or remote deployment that env is minted by `executionEnvFactory` DURING prepare, after the
|
|
230
|
+
caller built its hooks, so a hook closed over a spec-time env answers about the wrong machine; reading it
|
|
231
|
+
off the CALL context removes the ordering problem and picks up a resumed leg's rebuilt env with no
|
|
232
|
+
re-instantiation protocol. The face (new exported type `HookEnvCapabilities`, builder
|
|
233
|
+
`createHookEnvCapabilities` — public because `runToolGate` is) carries FOUR wrapped read primitives under
|
|
234
|
+
the env's own names: `canonicalPath`, `exists`, `readLink`, `cwd()` (the seam sketch's `canonicalize`/`root`
|
|
235
|
+
— `ExecutionEnv` declares no "root", so the honest anchor is the env's own working directory, read live).
|
|
236
|
+
**`cwd()` is the ENVIRONMENT's directory, not the task's tracked cwd** — the engine tracks a separate
|
|
237
|
+
per-task cwd (starting at the resolved task root, moving with `cd`/worktree entry) that the TOOLS' relative
|
|
238
|
+
paths resolve against and the env is never told about, so a hook judging a RELATIVE target must judge
|
|
239
|
+
absolute paths or get the tracked cwd through the deployment's own channel.
|
|
240
|
+
Deliberately capabilities and not the env object: no back-reference, null prototype, frozen, each primitive
|
|
241
|
+
captured once at build time. **Every member is optional and ABSENCE is the capability signal** — a member is
|
|
242
|
+
present iff the env exposes that primitive, never a stub that throws or an emulation that guesses, so a hook
|
|
243
|
+
branches on the shape; `ctx.env` itself is absent when the deployment wired no execution env at all
|
|
244
|
+
(`null` included). Existing hooks are unaffected.
|
|
245
|
+
|
|
246
|
+
### Fixed
|
|
247
|
+
|
|
248
|
+
- **The auto-mode handback review now runs on EVERY sub-agent completion path, not only the synchronous
|
|
249
|
+
spawn.** A child finishing on the sync-fork path, on either background resolve leg (plain and fork),
|
|
250
|
+
through the parked-resume drive, or on a woken retained-resume cycle handed its output to the parent
|
|
251
|
+
without the completed-work review the sync path has performed since it landed — so exactly the
|
|
252
|
+
delegation shapes that outlive the spawning call were the ones that skipped it. The review, its
|
|
253
|
+
evidence set (the child's final text, the step recorder's observed tool activity, and — on the one path
|
|
254
|
+
that delivers it — the streamed partial-findings tail of a child that stopped without completing), its
|
|
255
|
+
framing and its fail-open posture are now one shared mint point that every call site uses. Carriers per path: the
|
|
256
|
+
fork report card leads with the warning line exactly as the sync card does; a background or resumed
|
|
257
|
+
child's warning is prefixed to the one value the durable row and the completion notification BOTH
|
|
258
|
+
derive from, so those two faces can never disagree about whether a child was flagged. Two deliberate
|
|
259
|
+
skips: a lane whose abort already fired (a stopped child settles on its stop story instead of waiting
|
|
260
|
+
on a classifier — the residual is stated at `reviewHandback`), and a durably-paused child (it has
|
|
261
|
+
handed nothing back yet — its resumed cycle gets the review). Deployments without auto-mode armed are
|
|
262
|
+
byte-identical on every path.
|
|
263
|
+
**Consumer notes**: ① the completed-agent structured card (`details`) gained an optional
|
|
264
|
+
**`handbackWarning`** key — the content face has always led with the warning line, but a host reading
|
|
265
|
+
`details` (afterToolCall hooks, persisted tool results) saw nothing; it rides as its own key because
|
|
266
|
+
`result` is contracted to be the child's text verbatim. Present only when a child was flagged, so
|
|
267
|
+
presence must not be read as a signal of anything else. ② the classifier sees one new `toolName` value,
|
|
268
|
+
`Agent(fork handback)`, beside the existing `Agent(handback)`. ③ `createSubagentResume` gained a
|
|
269
|
+
`currentAutoModeReview` seat in the same `current*` family as `currentOnQuestion`/`currentClamps` (the
|
|
270
|
+
waking run's decider, not the frozen spawn snapshot's); a host wiring SendMessage itself supplies it
|
|
271
|
+
from its own tool ctx, and absent it a woken cycle carries through unreviewed.
|
|
272
|
+
|
|
273
|
+
- **A retained background child's revival leg now inherits the session's own org-admission verdict.** The
|
|
274
|
+
in-process resume leg (a settled retained child woken onto its existing session) is a same-session
|
|
275
|
+
continuation that never goes through a checkpoint, so it re-adjudicated org memory admission from scratch
|
|
276
|
+
with no session freeze: a resolver whose answer WIDENED between the two legs remounted a tenant layer the
|
|
277
|
+
session had already lost, and a read-only first leg could gain an org WRITE grant on the revival — the
|
|
278
|
+
exact "a continuation can never widen" invariant the checkpoint plane enforces with seed ∩ live. The
|
|
279
|
+
verdict now rides `RunInternals.ownOrgAdmissionRef` (trusted internal, one ref per spawned child, threaded
|
|
280
|
+
through the frozen internals snapshot the revival replays); prepare INTERSECTS it with any checkpoint seed
|
|
281
|
+
rather than picking one, so the fold narrows along both the scope and the write axis. Deployments with no
|
|
282
|
+
governance surface are byte-identical (the ref is never read or written). The CROSS-PROCESS (tier-3) revival
|
|
283
|
+
half stays open and is now recorded in `memory-admission.ts`'s header instead of the old blanket residual
|
|
284
|
+
note: that leg rebuilds the spec from the reviving caller's mount, and the durable agent row records lookup
|
|
285
|
+
keys only, so it has no carrier for the original verdict.
|
|
286
|
+
|
|
287
|
+
- **A background delegation no longer needs a NAME to park at an approval gate.** The §7.3 park predicate
|
|
288
|
+
carried an `agentName !== undefined` term, so an ANONYMOUS background child that hit an approval gate was
|
|
289
|
+
sentenced to `no_park_lane`: the row settled `failed`, and the approval never surfaced anywhere an operator
|
|
290
|
+
could decide it (the checkpoint stayed committed but undiscoverable through the agent faces). A name was
|
|
291
|
+
never a structural precondition — the park is keyed on the taskId (`parkBackgroundAgent(taskId, …)`), the
|
|
292
|
+
claim/resume chain addresses the row by handle, and every revival face already treats `row.name` as optional.
|
|
293
|
+
The term is dropped from BOTH sites of the family in one commit: the settle-time park predicate and the
|
|
294
|
+
`durableApproval` forwarding predicate (a divergence would auto-deny an anonymous child's plain ask while its
|
|
295
|
+
safety-tier ask parks). The fork lane is unaffected (r4 F-07 — it has no park block at all), and genuinely
|
|
296
|
+
un-wired deployments still take `no_park_lane`, with the reason text no longer naming the retired
|
|
297
|
+
precondition. Consumer note: probes pinning "an anonymous background child never parks" red.
|
|
298
|
+
|
|
3
299
|
## 5.13.0 — 2026-08-05
|
|
4
300
|
|
|
5
301
|
### BREAKING
|
|
@@ -534,6 +534,7 @@ export function createSendMessageTool(opts) {
|
|
|
534
534
|
...(ctx.interactiveTools === false ? { interactiveTools: false } : {}),
|
|
535
535
|
...(ctx.oneShot === true ? { oneShot: true } : {}),
|
|
536
536
|
},
|
|
537
|
+
...(ctx.autoModeReview !== undefined ? { currentAutoModeReview: ctx.autoModeReview } : {}),
|
|
537
538
|
});
|
|
538
539
|
const fromPrefix = senderIsChild ? `(message from teammate "${senderLabel}")\n` : "";
|
|
539
540
|
try {
|
|
@@ -66,6 +66,7 @@ export declare function completedAgentCard(child: {
|
|
|
66
66
|
worktreePath?: string;
|
|
67
67
|
toolStats?: SubagentToolStats;
|
|
68
68
|
modelFallback?: "inherit_no_tier_binding";
|
|
69
|
+
handbackWarning?: string;
|
|
69
70
|
}): Record<string, unknown>;
|
|
70
71
|
export declare const SUBAGENT_SUSPENDED_AWAITING_APPROVAL = "suspended.awaiting_approval";
|
|
71
72
|
export declare const SUBAGENT_SUSPENDED_NEEDS_REVIEW = "suspended.needs_review";
|
|
@@ -96,6 +97,9 @@ export declare function createSubagentResume(deps: {
|
|
|
96
97
|
interactiveTools?: false;
|
|
97
98
|
oneShot?: true;
|
|
98
99
|
};
|
|
100
|
+
currentAutoModeReview?: {
|
|
101
|
+
decider: import("../core/auto-mode.js").AutoModeDecider;
|
|
102
|
+
};
|
|
99
103
|
taskId?: string;
|
|
100
104
|
taskAccess?: import("../core/task-registry.js").TaskAccess;
|
|
101
105
|
bgSink?: (event: import("../core/types.js").BackgroundChildEvent) => void;
|