@sema-agent/core 7.49.5 → 7.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/dist/agents/observer.js +3 -10
  3. package/dist/agents/subagent-steps.d.ts +6 -14
  4. package/dist/agents/subagent-steps.js +3 -17
  5. package/dist/brain/stream-shared.d.ts +1 -1
  6. package/dist/core/allow-entry.d.ts +1 -1
  7. package/dist/core/allow-entry.js +1 -1
  8. package/dist/core/ask-origin.d.ts +15 -11
  9. package/dist/core/ask-origin.js +6 -14
  10. package/dist/core/auto-mode-prompt.js +6 -2
  11. package/dist/core/checkpoint-store.d.ts +6 -4
  12. package/dist/core/checkpoint-store.js +1 -1
  13. package/dist/core/context-edit.d.ts +2 -2
  14. package/dist/core/decision-receipt.d.ts +2 -2
  15. package/dist/core/decision-receipt.js +30 -11
  16. package/dist/core/delegation-frames.d.ts +66 -18
  17. package/dist/core/delegation-frames.js +22 -0
  18. package/dist/core/engine-notice.d.ts +7 -5
  19. package/dist/core/frame-settlement.d.ts +6 -1
  20. package/dist/core/gate-fold.js +19 -13
  21. package/dist/core/gate-lanes.js +12 -11
  22. package/dist/core/gate-outcome.d.ts +2 -2
  23. package/dist/core/governance-codes.js +2 -0
  24. package/dist/core/hooks.d.ts +28 -20
  25. package/dist/core/human-input-projection.js +2 -0
  26. package/dist/core/lsp-diagnostics.d.ts +3 -0
  27. package/dist/core/lsp-diagnostics.js +6 -0
  28. package/dist/core/lsp-session.d.ts +39 -17
  29. package/dist/core/lsp-session.js +118 -58
  30. package/dist/core/lsp.d.ts +83 -3
  31. package/dist/core/lsp.js +146 -16
  32. package/dist/core/mcp.js +2 -1
  33. package/dist/core/media-byte-cap.d.ts +14 -0
  34. package/dist/core/media-byte-cap.js +15 -3
  35. package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
  36. package/dist/core/memory-engine/consolidation-driver.js +54 -6
  37. package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
  38. package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
  39. package/dist/core/memory-engine/consolidation.d.ts +17 -0
  40. package/dist/core/memory-engine/consolidation.js +1 -0
  41. package/dist/core/memory-engine/distiller.d.ts +8 -2
  42. package/dist/core/memory-engine/distiller.js +8 -6
  43. package/dist/core/memory-engine/engine.d.ts +37 -4
  44. package/dist/core/memory-engine/engine.js +127 -38
  45. package/dist/core/memory-engine/file-backend.d.ts +8 -3
  46. package/dist/core/memory-engine/file-backend.js +44 -16
  47. package/dist/core/memory-engine/index.d.ts +2 -2
  48. package/dist/core/memory-engine/index.js +1 -1
  49. package/dist/core/memory-engine/landing-admission.d.ts +65 -0
  50. package/dist/core/memory-engine/landing-admission.js +68 -0
  51. package/dist/core/memory-engine/types.d.ts +23 -0
  52. package/dist/core/mode-policy.d.ts +3 -2
  53. package/dist/core/mode-policy.js +2 -0
  54. package/dist/core/permission-rule-model.d.ts +19 -13
  55. package/dist/core/permission-rule-model.js +7 -4
  56. package/dist/core/permission-rule-provider.d.ts +11 -0
  57. package/dist/core/permission-rules.d.ts +1 -1
  58. package/dist/core/permission-rules.js +5 -2
  59. package/dist/core/persisted-rule-arms.d.ts +15 -7
  60. package/dist/core/persisted-rule-arms.js +13 -6
  61. package/dist/core/reminder-mint.d.ts +5 -0
  62. package/dist/core/reminder-mint.js +1 -0
  63. package/dist/core/retired-keys.d.ts +8 -0
  64. package/dist/core/retired-keys.js +4 -0
  65. package/dist/core/rule-lane-reads.d.ts +4 -4
  66. package/dist/core/rule-lane-reads.js +10 -1
  67. package/dist/core/runner/construction-doors.d.ts +10 -4
  68. package/dist/core/runner/construction-doors.js +25 -2
  69. package/dist/core/runner/contracts.d.ts +20 -3
  70. package/dist/core/runner/env-capability-census.js +1 -1
  71. package/dist/core/runner/gate-exit.js +2 -1
  72. package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
  73. package/dist/core/runner/memory-consolidation-driver.js +5 -2
  74. package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
  75. package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
  76. package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
  77. package/dist/core/runner/permission-rule-lanes.js +13 -43
  78. package/dist/core/runner/person-path-rows.d.ts +244 -0
  79. package/dist/core/runner/person-path-rows.js +387 -0
  80. package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
  81. package/dist/core/runner/prepare-artifact.d.ts +5 -3
  82. package/dist/core/runner/prepare-artifact.js +2 -8
  83. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  84. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  85. package/dist/core/runner/prepare-context-lane.js +2 -2
  86. package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
  87. package/dist/core/runner/prepare-gate-stations.js +1 -1
  88. package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
  89. package/dist/core/runner/prepare-hands-readface.js +57 -43
  90. package/dist/core/runner/prepare-lsp.d.ts +15 -13
  91. package/dist/core/runner/prepare-lsp.js +11 -11
  92. package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
  93. package/dist/core/runner/prepare-policy-chain.js +6 -7
  94. package/dist/core/runner/prepare-run-refs.js +1 -1
  95. package/dist/core/runner/prepare-task.js +24 -16
  96. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
  97. package/dist/core/runner/prepare-turn-wiring.js +19 -8
  98. package/dist/core/runner/read-root-grant.d.ts +1 -25
  99. package/dist/core/runner/read-root-grant.js +1 -17
  100. package/dist/core/runner/retired-runner-deps.d.ts +2 -2
  101. package/dist/core/runner/run-brain-sinks.d.ts +1 -5
  102. package/dist/core/runner/run-brain-sinks.js +2 -4
  103. package/dist/core/runner/run-harness-handlers.js +5 -4
  104. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  105. package/dist/core/runner/run-identity-wiring.js +10 -1
  106. package/dist/core/runner/run-leg.d.ts +1 -1
  107. package/dist/core/runner/run-leg.js +22 -9
  108. package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
  109. package/dist/core/runner/run-settle-and-teardown.js +7 -4
  110. package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
  111. package/dist/core/runner/run-terminal-adoption.js +1 -1
  112. package/dist/core/runner/run-turn-boundary.js +2 -2
  113. package/dist/core/runner/runtask.js +8 -8
  114. package/dist/core/runner/session-file-state-replay.d.ts +14 -4
  115. package/dist/core/runner/session-file-state-replay.js +8 -0
  116. package/dist/core/runner/stream-steer-verb.js +3 -0
  117. package/dist/core/runner/tool-end-body.d.ts +5 -4
  118. package/dist/core/runner/tool-end-body.js +4 -4
  119. package/dist/core/runner/tool-output-projection.d.ts +6 -4
  120. package/dist/core/runner/tool-output-projection.js +66 -47
  121. package/dist/core/runner-deps.d.ts +6 -3
  122. package/dist/core/shared-memory/types.d.ts +2 -2
  123. package/dist/core/shell-read-silence.d.ts +23 -0
  124. package/dist/core/shell-read-silence.js +18 -0
  125. package/dist/core/synthetic-read-verdict.d.ts +80 -21
  126. package/dist/core/synthetic-read-verdict.js +89 -18
  127. package/dist/core/task-event.d.ts +113 -25
  128. package/dist/core/task-spec.d.ts +44 -0
  129. package/dist/core/task-tool-shape.d.ts +1 -1
  130. package/dist/core/tool-catalog-entries.js +16 -2
  131. package/dist/core/tool-policy.d.ts +25 -15
  132. package/dist/core/tool-policy.js +9 -13
  133. package/dist/core/trace.d.ts +3 -2
  134. package/dist/core/types.d.ts +3 -3
  135. package/dist/core/types.js +1 -1
  136. package/dist/core/untrusted-text.d.ts +2 -2
  137. package/dist/core/user-attachment.d.ts +164 -0
  138. package/dist/core/user-attachment.js +437 -0
  139. package/dist/engine/compaction/compaction.js +10 -3
  140. package/dist/engine/compaction/utils.js +6 -0
  141. package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
  142. package/dist/engine/execution-env/node-execution-env.js +125 -0
  143. package/dist/engine/harness/agent-harness.d.ts +12 -2
  144. package/dist/engine/harness/agent-harness.js +15 -3
  145. package/dist/engine/harness/types.d.ts +21 -0
  146. package/dist/engine/llm/content-block-render.d.ts +15 -0
  147. package/dist/engine/llm/content-block-render.js +10 -0
  148. package/dist/engine/llm/index.d.ts +1 -0
  149. package/dist/engine/llm/index.js +1 -0
  150. package/dist/engine/llm/types.d.ts +80 -4
  151. package/dist/engine/llm/types.js +43 -1
  152. package/dist/engine/session/import-validate.js +2 -0
  153. package/dist/index.d.ts +6 -4
  154. package/dist/index.js +2 -1
  155. package/dist/internal/harness-types.d.ts +1 -1
  156. package/dist/internal/llm.d.ts +2 -2
  157. package/dist/internal/llm.js +1 -1
  158. package/dist/orchestration/workflow.d.ts +4 -5
  159. package/dist/orchestration/workflow.js +2 -2
  160. package/dist/prompts/default.d.ts +1 -1
  161. package/dist/tools/artifact/artifact-tool.js +1 -1
  162. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  163. package/dist/tools/fs/dir-entries.d.ts +4 -10
  164. package/dist/tools/fs/dir-entries.js +4 -11
  165. package/dist/tools/fs/file-read-check.js +3 -3
  166. package/dist/tools/fs/fs-pdf.js +2 -4
  167. package/dist/tools/fs/fs-read.d.ts +47 -4
  168. package/dist/tools/fs/fs-read.js +63 -19
  169. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  170. package/dist/tools/fs/fs-search-tools.js +7 -2
  171. package/dist/tools/fs/fs-write.d.ts +1 -1
  172. package/dist/tools/fs/fs-write.js +7 -6
  173. package/dist/tools/fs/index.d.ts +7 -1
  174. package/dist/tools/fs/index.js +56 -34
  175. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  176. package/dist/tools/fs/read-deny-home.js +32 -22
  177. package/dist/tools/fs/read-deny.d.ts +14 -0
  178. package/dist/tools/fs/read-deny.js +8 -0
  179. package/dist/tools/fs/repo-map.d.ts +3 -2
  180. package/dist/tools/fs/repo-map.js +11 -3
  181. package/dist/tools/fs/safety.d.ts +41 -2
  182. package/dist/tools/fs/safety.js +12 -3
  183. package/dist/tools/fs/shell-read-check.d.ts +12 -3
  184. package/dist/tools/fs/shell-read-check.js +28 -15
  185. package/dist/tools/fs/shell-read-device.d.ts +1 -1
  186. package/dist/tools/fs/shell-read-judge.d.ts +9 -4
  187. package/dist/tools/fs/shell-read-judge.js +1 -1
  188. package/dist/tools/loop-tick.d.ts +2 -2
  189. package/package.json +3 -1
  190. package/test/export-surface.snapshot.json +39 -1
  191. package/wire-changes/core@7.50.0.json +1699 -0
  192. package/dist/core/runner/persisted-path-rows.d.ts +0 -88
  193. package/dist/core/runner/persisted-path-rows.js +0 -108
@@ -0,0 +1,23 @@
1
+ import type { ShellReadVerdict } from "../tools/fs/shell-read-check.js";
2
+ import type { GatePass } from "./runner/gate-exit.js";
3
+ import type { AskGuard } from "./tool-policy.js";
4
+ /** Whose silence a station's answer carries, each its own class on the question it keeps or mints: unread rows are a rule that could
5
+ * not be read (the rule lane's class); the finding `unanswered` is, unless rows are unread, the environment's silence — shell text the
6
+ * engine cannot read (a guard) — and the marks only pick whose. Either is a question only a person answers; `probe` follows the finding. */
7
+ export declare function silencedBy(verdict: ShellReadVerdict, d: {
8
+ readonly guards?: readonly AskGuard[];
9
+ readonly ruleUnreadable?: unknown;
10
+ }): {
11
+ ruleUnreadable?: "persisted_rows";
12
+ guards?: readonly AskGuard[];
13
+ requiresRealApproval?: true;
14
+ };
15
+ /** The finding `unanswered` IS the silence, applied once, before any disposition arm: a station — a host-mounted one included — may
16
+ * spell `vouch` or `mandated` beside it, and neither word vouches for what nobody judged. A deny stands; an ask keeps its words and
17
+ * gains the silence's class; an allow becomes the silence's question (`ask.message`), one only a person answers. Nothing is admitted,
18
+ * no `probe` is stamped and no grant is offered — the station made no finding. */
19
+ export declare function applyUnansweredSilence(pass: GatePass, verdict: ShellReadVerdict, ask: {
20
+ readonly message: string;
21
+ readonly account: string | undefined;
22
+ readonly tightenedBy: "shell_gate" | "safety";
23
+ }): void;
@@ -0,0 +1,18 @@
1
+ export function silencedBy(verdict, d) {
2
+ const environment = verdict.environmentSilent === true || (verdict.finding === "unanswered" && verdict.rowsUnread !== true);
3
+ const rows = verdict.rowsUnread === true && d.ruleUnreadable === undefined ? { ruleUnreadable: "persisted_rows" } : {};
4
+ const guards = environment && d.guards?.includes("unverifiable_shell_text") !== true ? { guards: [...(d.guards ?? []), "unverifiable_shell_text"] } : {};
5
+ return environment || verdict.rowsUnread === true ? { ...rows, ...guards, requiresRealApproval: true } : {};
6
+ }
7
+ export function applyUnansweredSilence(pass, verdict, ask) {
8
+ if (verdict.details !== undefined)
9
+ pass.shellReadDetails = verdict.details;
10
+ if (pass.decision.action === "ask") {
11
+ pass.decision = { ...pass.decision, ...silencedBy(verdict, pass.decision) };
12
+ return;
13
+ }
14
+ if (pass.decision.action !== "allow")
15
+ return;
16
+ pass.decision = { action: "ask", message: ask.message, decisionReason: "safety", ...silencedBy(verdict, {}), ...(ask.account !== undefined ? { probeReason: ask.account } : {}) };
17
+ pass.tightenedBy = ask.tightenedBy;
18
+ }
@@ -1,24 +1,40 @@
1
1
  /**
2
- * The synthetic-Read DENY judge — "would a `Read` of this file be refused outright?", asked without a call,
3
- * without an approver and without a hook. One judge, one set of layers, for every consumer that needs the
4
- * answer for a file the model did not itself ask to read (the changed-file notice today; a user's
5
- * attachment next). It answers in THREE states and never picks a direction:
2
+ * The synthetic-call judge — "would this call be refused outright?", asked without a model call, without an
3
+ * approver and without a hook. One judge, one set of layers, for every consumer that needs the answer for a file the
4
+ * model did not itself ask to touch. It has TWO FACES over one body (the layer walk below is written once):
5
+ *
6
+ * READ — a `Read` of a file the model did not ask for (the changed-file notice; a user's attachment). An ordinary
7
+ * `ask` — a person's own ask rule — is not a refusal: the person who would answer it is the one asking.
8
+ * WRITE — a `Write` of a path the engine itself writes in the background (the memory consolidation run). Every `ask`
9
+ * is an objection here: nobody is present to answer it, so a layer that would ask a person refuses the write.
10
+ *
11
+ * It never picks a direction. The read face answers in FOUR states:
6
12
  *
7
13
  * deny — some layer refuses it;
8
- * no_objection — every layer it consulted answered and none refused (an `ask` is not a refusal);
9
- * unanswerable — no layer refused, and at least one could not answer (see the table below).
14
+ * mandated_ask — no layer refuses it, and some layer would put the read to a person no rule may answer for
15
+ * (the ask's own mandate reading, {@link ruleClearanceOf}: a real-approval demand, the deployment's
16
+ * own question, an ancestor's, a hook's; or an organization rule's ask) — the person who must
17
+ * answer is not necessarily the one asking;
18
+ * no_objection — every layer it consulted answered and none objected;
19
+ * unanswerable — no layer objected, and at least one could not answer (see the table below).
20
+ *
21
+ * The write face answers the same four, with `ask` in place of `mandated_ask`: any layer's ask, mandated or not.
10
22
  *
11
- * The CONSUMER decides where `unanswerable` goes: a notice that merely tells the model a file changed
12
- * reads it as no objection (the worst case is a notice about a file it then cannot re-read); a face that
13
- * would put a file's contents in front of the model reads it as a refusal.
23
+ * ONE PRECEDENCE on both faces: a deny anywhere wins over a held ask, and a held ask over an unanswerable layer; the walk
24
+ * goes on past an ask (a later layer's deny is the stronger answer). The faces differ only in which asks are held. The
25
+ * CONSUMER decides where `mandated_ask` and `unanswerable` go: a notice that merely tells the model a file changed reads
26
+ * both as no objection (the worst case is a notice about a file it then cannot re-read); a face that would put a file's
27
+ * contents in front of the model reads both as a refusal; a background write reads everything but `no_objection` as a
28
+ * refusal. Every answer but `unanswerable` names the location the layers judged (`judged`, the canonical view of the
29
+ * stamped target, when a stamp was taken), so a consumer that resolved the file itself can tell the judged file from the
30
+ * one it opens.
14
31
  *
15
- * THE LAYERS (every one that can refuse a model's own `Read`): the run's skill deny rows; the
32
+ * THE LAYERS (every one that can refuse a model's own call): the run's skill deny rows; the
16
33
  * deny-narrowing fold (session rules, inherited ancestor rules, frozen constraint projections); the
17
34
  * deployment's caller policy; each live inherited parent policy; the person's persisted-rule lane; the org
18
- * lane. Not consulted, by design: PreToolUse hooks (the deployment's screening of a model's CALL), the
19
- * permission mode's write gate and the integrity guards (neither refuses a read), and the read-deny table
20
- * and containment roots (the path's own resolution judges those before this judge is asked). A deny
21
- * anywhere wins over an unanswerable layer elsewhere.
35
+ * lane. Not consulted, by design: PreToolUse hooks (the deployment's screening of a model's CALL), the permission mode's write
36
+ * gate, the integrity guards and the write-protection table (a live `Write` meets them; the read face never needs them and the
37
+ * write face's background run is not asked), and the read-deny table and containment roots (the path's own resolution judges those first).
22
38
  *
23
39
  * FAULT → STATE (the live gate's own readings, through the same functions — never a second copy):
24
40
  * | layer | fault | state |
@@ -28,11 +44,13 @@
28
44
  * | caller / inherited policy | its deny carries an engine settlement (an approval | unanswerable `approval_seat` |
29
45
  * | | seat that ended unanswered; in a combinePolicies | |
30
46
  * | | composition a rule deny anywhere is returned instead)| |
31
- * | persisted lane | store unreadable, throws, times out (readPersistedRuleLane ⇒ `{unreadable}`) | its rows take no part |
47
+ * | persisted lane | a store partition (or all of it) is unreadable, the lane throws, times out (readPersistedRuleLane ⇒ `{unreadable}`) | unanswerable `store_unreadable` (both faces: a row nobody could read may forbid the call) |
32
48
  * | persisted lane | the call cannot be read against the rows (`reason`) — applyPersistedTightening ⇒ `unreadable` | unanswerable `call_unreadable` |
33
49
  * | org lane | unavailable: throws, garbage (readOrgRuleLane) | unanswerable `org_unavailable` |
50
+ * | any policy layer | answers with a rewrite of the arguments (`updatedInput`) | unanswerable `rewritten` |
51
+ * | inherited parent policy | answers `ask` (the ancestor's approver's question) | mandated_ask (write: ask) |
34
52
  * | any layer | the judgment's budget (one dependency timeout, or a | unanswerable `timed_out` |
35
- * | | batch's shared deadline) is spent | (persisted: rows take no part)|
53
+ * | | batch's shared deadline) is spent | |
36
54
  *
37
55
  * NEVER AN APPROVER. A deployment policy (the caller's, an inherited parent's) is consulted with an
38
56
  * ALREADY-ABORTED signal and no approver seat: the `ToolPolicy.check` contract is that an aborted signal
@@ -45,32 +63,50 @@
45
63
  * the notice face stays silent. The engine's own layers (narrowing, skill rows, the two lanes) get the
46
64
  * run's real signal: they are pure matchers and store reads.
47
65
  *
48
- * THE PROBE'S IDENTITY. Every layer sees `toolName: "Read"`, the probed arguments, and the stable synthetic
49
- * call id `<face>-visibility-probe` ({@link syntheticReadProbeId}), so a deployment's audit can tell a
50
- * visibility probe from a model's call.
66
+ * THE PROBE'S IDENTITY. Every layer sees the probed tool's name (`Read` / `Write`), the probed arguments, and a stable
67
+ * synthetic call id (`<face>-visibility-probe` for a read, `<face>-write-probe` for a write; {@link syntheticReadProbeId},
68
+ * {@link syntheticWriteProbeId}), so a deployment's audit can tell a probe from a model's call. The write face stamps the
69
+ * facts the live gate stamps on a write (the roster's `Write` face, the tracked cwd and root, the advertised writable
70
+ * directories, the data root's exemption): a containment fence and a data-root exemption judge the probe as they judge the call.
51
71
  */
52
72
  import type { ToolCallRequest, ToolCallFace, ToolPolicy } from "./tool-policy.js";
53
- import type { CallTargetStamp } from "./effective-path-target.js";
73
+ import { type CallTargetStamp } from "./effective-path-target.js";
54
74
  import { type SkillGrantLane } from "./skill-grant.js";
55
75
  import type { OrgGateVerdict, PersistedRuleAnswer } from "./hooks.js";
56
76
  /** The layers the judge consults, as named in its verdicts. */
57
77
  export declare const SYNTHETIC_READ_LAYERS: readonly ["skill_rows", "narrowing", "caller_policy", "inherited_constraint", "persisted_rules", "org_rules"];
58
78
  export type SyntheticReadLayer = (typeof SYNTHETIC_READ_LAYERS)[number];
59
79
  /** Why a layer could not answer — the consumer's disclosure word. */
60
- export declare const SYNTHETIC_READ_UNANSWERABLE_CAUSES: readonly ["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable"];
80
+ export declare const SYNTHETIC_READ_UNANSWERABLE_CAUSES: readonly ["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable", "rewritten", "store_unreadable"];
61
81
  export type SyntheticReadUnanswerableCause = (typeof SYNTHETIC_READ_UNANSWERABLE_CAUSES)[number];
62
82
  export type SyntheticReadVerdict = {
63
83
  verdict: "deny";
64
84
  layer: SyntheticReadLayer;
65
85
  message?: string;
86
+ judged?: string;
87
+ } | {
88
+ verdict: "mandated_ask";
89
+ layer: SyntheticReadLayer;
90
+ message?: string;
91
+ judged?: string;
66
92
  } | {
67
93
  verdict: "no_objection";
94
+ judged?: string;
68
95
  } | {
69
96
  verdict: "unanswerable";
70
97
  layer: SyntheticReadLayer;
71
98
  cause: SyntheticReadUnanswerableCause;
72
99
  reason: string;
73
100
  };
101
+ /** The write face's answer: the read face's states with `ask` — any layer's ask, mandated or not — in place of `mandated_ask`. */
102
+ export type SyntheticWriteVerdict = Exclude<SyntheticReadVerdict, {
103
+ verdict: "mandated_ask";
104
+ }> | {
105
+ verdict: "ask";
106
+ layer: SyntheticReadLayer;
107
+ message?: string;
108
+ judged?: string;
109
+ };
74
110
  export interface SyntheticReadLayers {
75
111
  skillGrants?: Pick<SkillGrantLane, "denying">;
76
112
  denyNarrowingPolicy?: ToolPolicy;
@@ -97,9 +133,27 @@ export interface SyntheticReadCall {
97
133
  /** The live roster's face for `Read`, when the consumer has one. */
98
134
  face?: ToolCallFace;
99
135
  }
136
+ /** The path a synthetic `Write` probes. The request's other facts come from the judge's {@link SyntheticWriteFacts}. */
137
+ export interface SyntheticWriteCall {
138
+ file_path: string;
139
+ }
140
+ /** The facts the live gate stamps on a write, read at the moment of each judgment (the leg's cells move: the tracked cwd,
141
+ * the advertised-directory ledger). Every member is optional — a leg without hands has no roster face, cwd or root. */
142
+ export interface SyntheticWriteFacts {
143
+ /** The live roster's face for `Write` (a containment fence admits a write only by the tool's own declared path target). */
144
+ face?: ToolCallFace | undefined;
145
+ cwd?: string | undefined;
146
+ root?: string | undefined;
147
+ advertisedWritableDirs?: {
148
+ readonly roots: readonly string[];
149
+ } | undefined;
150
+ exemptRoots?: readonly string[] | undefined;
151
+ }
100
152
  /** The stable synthetic call id a probe carries: `<face>-visibility-probe`, `face` a lowercase word
101
153
  * (letters and hyphens) naming the consumer. */
102
154
  export declare function syntheticReadProbeId(face: string): string;
155
+ /** The write face's stable synthetic call id: `<face>-write-probe`, `face` the same lowercase word. */
156
+ export declare function syntheticWriteProbeId(face: string): string;
103
157
  /** One judgment's wait budget. `deadlineAt` (epoch ms) bounds the WHOLE judgment — every layer waits at most
104
158
  * what is left of it, and a layer reached after it has passed is not consulted (`timed_out`). Absent ⇒ the
105
159
  * dependency timeout from the start of this judgment. A consumer judging a batch passes one shared deadline,
@@ -108,4 +162,9 @@ export interface SyntheticReadBudget {
108
162
  deadlineAt?: number;
109
163
  }
110
164
  export type SyntheticReadJudge = (call: SyntheticReadCall, face: string, budget?: SyntheticReadBudget) => Promise<SyntheticReadVerdict>;
165
+ export type SyntheticWriteJudge = (call: SyntheticWriteCall, face: string, budget?: SyntheticReadBudget) => Promise<SyntheticWriteVerdict>;
166
+ /** The READ face: only a mandated ask is held (a person's own ask is that person's question). */
111
167
  export declare function createSyntheticReadJudge(layers: SyntheticReadLayers): SyntheticReadJudge;
168
+ /** The WRITE face: every ask is held as an objection (no approver is present), and a persisted store that cannot be read is
169
+ * unanswerable rather than silent. */
170
+ export declare function createSyntheticWriteJudge(layers: SyntheticReadLayers, facts: () => SyntheticWriteFacts): SyntheticWriteJudge;
@@ -1,20 +1,36 @@
1
- import { attestationOf, describeThrown, refuseOutOfContractDecision } from "./tool-policy.js";
1
+ import { callTargetReadingOf } from "./effective-path-target.js";
2
+ import { advertisedWritableRootsSeat, attestationOf, describeThrown, exemptRootsSeat, refuseOutOfContractDecision } from "./tool-policy.js";
3
+ import { ownedClearanceFacts, ruleClearanceOf } from "./ask-origin.js";
2
4
  import { skillRowRefusal } from "./skill-grant.js";
3
5
  import { applyPersistedTightening } from "./persisted-rule-arms.js";
4
6
  import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
5
7
  import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "./permission-rule-org.js";
8
+ import { deepJsonEqual } from "./runner/decide-continuation.js";
6
9
  export const SYNTHETIC_READ_LAYERS = Object.freeze(["skill_rows", "narrowing", "caller_policy", "inherited_constraint", "persisted_rules", "org_rules"]);
7
- export const SYNTHETIC_READ_UNANSWERABLE_CAUSES = Object.freeze(["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable"]);
10
+ export const SYNTHETIC_READ_UNANSWERABLE_CAUSES = Object.freeze(["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable", "rewritten", "store_unreadable"]);
8
11
  export function syntheticReadProbeId(face) {
12
+ return `${probeFaceWord(face)}-visibility-probe`;
13
+ }
14
+ export function syntheticWriteProbeId(face) {
15
+ return `${probeFaceWord(face)}-write-probe`;
16
+ }
17
+ function probeFaceWord(face) {
9
18
  if (!/^[a-z][a-z-]*$/.test(face))
10
- throw new Error(`syntheticReadProbeId: the probe face must be a lowercase word (letters and hyphens), got ${JSON.stringify(face)}`);
11
- return `${face}-visibility-probe`;
19
+ throw new Error(`synthetic probe id: the probe face must be a lowercase word (letters and hyphens), got ${JSON.stringify(face)}`);
20
+ return face;
12
21
  }
13
22
  const ABORTED = AbortSignal.abort();
23
+ function sameArguments(value, probed) {
24
+ try {
25
+ return deepJsonEqual(value, probed);
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
14
31
  export function createSyntheticReadJudge(layers) {
32
+ const judge = createProbeJudge(layers, "read");
15
33
  return async (call, face, budget) => {
16
- const deadlineAt = budget?.deadlineAt ?? Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
17
- const left = () => Math.max(0, Math.min(ORG_ADJUDICATION_TIMEOUT_MS, deadlineAt - Date.now()));
18
34
  const probe = {
19
35
  toolName: "Read",
20
36
  args: { file_path: call.file_path, ...(call.offset !== undefined ? { offset: call.offset } : {}), ...(call.limit !== undefined ? { limit: call.limit } : {}) },
@@ -22,32 +38,76 @@ export function createSyntheticReadJudge(layers) {
22
38
  ...(call.cwd !== undefined ? { cwd: call.cwd } : {}),
23
39
  ...(call.face !== undefined ? { face: call.face } : {}),
24
40
  };
41
+ const v = await judge(probe, budget);
42
+ return v.verdict === "ask" ? { ...v, verdict: "mandated_ask" } : v;
43
+ };
44
+ }
45
+ export function createSyntheticWriteJudge(layers, facts) {
46
+ const judge = createProbeJudge(layers, "write");
47
+ return async (call, face, budget) => {
48
+ const f = facts();
49
+ const probe = {
50
+ toolName: "Write",
51
+ args: { file_path: call.file_path, content: "" },
52
+ toolCallId: syntheticWriteProbeId(face),
53
+ ...(f.cwd !== undefined ? { cwd: f.cwd } : {}),
54
+ ...(f.root !== undefined ? { root: f.root } : {}),
55
+ ...(f.face !== undefined ? { face: f.face } : {}),
56
+ ...advertisedWritableRootsSeat(f.advertisedWritableDirs),
57
+ ...(f.exemptRoots !== undefined ? exemptRootsSeat(f.exemptRoots) : {}),
58
+ };
59
+ return judge(probe, budget);
60
+ };
61
+ }
62
+ function createProbeJudge(layers, kind) {
63
+ const everyAskHeld = kind === "write";
64
+ return async (probe, budget) => {
65
+ const deadlineAt = budget?.deadlineAt ?? Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
66
+ const left = () => Math.max(0, Math.min(ORG_ADJUDICATION_TIMEOUT_MS, deadlineAt - Date.now()));
67
+ const probedArgs = structuredClone(probe.args);
25
68
  const stamped = layers.stampCallTarget?.(probe, layers.signal);
26
69
  const req = stamped !== undefined ? await stamped : probe;
70
+ const view = callTargetReadingOf(req)?.protective;
71
+ const judged = view?.kind === "path" && view.canonical !== undefined ? { judged: view.canonical } : {};
27
72
  let unanswered;
73
+ let held;
28
74
  const cannot = (layer, cause, reason) => {
29
75
  unanswered ??= { verdict: "unanswerable", layer, cause, reason };
30
76
  };
77
+ const ask = (layer, message, mandated) => {
78
+ if (everyAskHeld || mandated)
79
+ held ??= { verdict: "ask", layer, ...(message !== undefined ? { message } : {}), ...judged };
80
+ };
31
81
  const spent = (layer) => {
32
82
  if (left() > 0)
33
83
  return false;
34
84
  cannot(layer, "timed_out", "the judgment's wait budget was spent before this layer was asked");
35
85
  return true;
36
86
  };
37
- const policyLayer = async (layer, policy, signal) => {
87
+ const policyLayer = async (layer, policy, signal, ancestorOwned = false) => {
38
88
  if (spent(layer))
39
89
  return undefined;
40
90
  const waitMs = left();
41
91
  let timedOut = false;
92
+ let thrown = undefined;
42
93
  const TIMEOUT = Symbol("timeout");
43
94
  let d;
44
95
  try {
45
- d = await settleOrgVerdictWithin(Promise.resolve().then(() => policy.check(req, signal)), TIMEOUT, { ...(layers.signal !== undefined ? { signal: layers.signal } : {}), timeoutMs: waitMs, onFallback: () => void (timedOut = true) });
96
+ d = await settleOrgVerdictWithin(Promise.resolve()
97
+ .then(() => policy.check(req, signal))
98
+ .catch((err) => {
99
+ thrown = { err };
100
+ return TIMEOUT;
101
+ }), TIMEOUT, { ...(layers.signal !== undefined ? { signal: layers.signal } : {}), timeoutMs: waitMs, onFallback: () => void (timedOut = true) });
46
102
  }
47
103
  catch (err) {
48
104
  cannot(layer, "threw", describeThrown(err));
49
105
  return undefined;
50
106
  }
107
+ if (thrown !== undefined) {
108
+ cannot(layer, "threw", describeThrown(thrown.err));
109
+ return undefined;
110
+ }
51
111
  if (d === TIMEOUT || timedOut) {
52
112
  cannot(layer, "timed_out", `no answer within ${waitMs}ms (or the run ended first)`);
53
113
  return undefined;
@@ -60,18 +120,25 @@ export function createSyntheticReadJudge(layers) {
60
120
  cannot(layer, "threw", describeThrown(err));
61
121
  return undefined;
62
122
  }
123
+ if (read.action !== "deny" && ((read.updatedInput !== undefined && !sameArguments(read.updatedInput, probedArgs)) || !sameArguments(req.args, probedArgs))) {
124
+ cannot(layer, "rewritten", "the layer would rewrite the call's arguments, so its answer is not about this call");
125
+ }
126
+ if (read.action === "ask") {
127
+ ask(layer, read.message, ancestorOwned || ruleClearanceOf(ownedClearanceFacts(read), {}).mandated);
128
+ return undefined;
129
+ }
63
130
  if (read.action !== "deny")
64
131
  return undefined;
65
132
  if (attestationOf(read, req)?.settlement !== undefined) {
66
- cannot(layer, "approval_seat", "the policy would ask an approver, who is never consulted for a visibility probe");
133
+ cannot(layer, "approval_seat", "the policy would ask an approver, who is never consulted for a synthetic probe");
67
134
  return undefined;
68
135
  }
69
- return { verdict: "deny", layer, ...(read.message !== undefined ? { message: read.message } : {}) };
136
+ return { verdict: "deny", layer, ...(read.message !== undefined ? { message: read.message } : {}), ...judged };
70
137
  };
71
138
  try {
72
139
  const refusal = skillRowRefusal(layers.skillGrants, req);
73
140
  if (refusal !== undefined)
74
- return { verdict: "deny", layer: "skill_rows", ...(refusal.message !== undefined ? { message: refusal.message } : {}) };
141
+ return { verdict: "deny", layer: "skill_rows", ...(refusal.message !== undefined ? { message: refusal.message } : {}), ...judged };
75
142
  }
76
143
  catch (err) {
77
144
  cannot("skill_rows", "threw", describeThrown(err));
@@ -82,14 +149,14 @@ export function createSyntheticReadJudge(layers) {
82
149
  return v;
83
150
  }
84
151
  if (layers.callerPolicy !== undefined) {
85
- const v = await policyLayer("caller_policy", layers.callerPolicy, ABORTED);
152
+ const v = await policyLayer("caller_policy", layers.callerPolicy, ABORTED, (layers.inheritedPolicies ?? []).includes(layers.callerPolicy));
86
153
  if (v !== undefined)
87
154
  return v;
88
155
  }
89
156
  for (const parent of layers.inheritedPolicies ?? []) {
90
157
  if (parent === layers.callerPolicy)
91
158
  continue;
92
- const v = await policyLayer("inherited_constraint", parent, ABORTED);
159
+ const v = await policyLayer("inherited_constraint", parent, ABORTED, true);
93
160
  if (v !== undefined)
94
161
  return v;
95
162
  }
@@ -97,17 +164,21 @@ export function createSyntheticReadJudge(layers) {
97
164
  const read = await readPersistedRuleLane(layers.persistedRuleLane, req, layers.signal, left());
98
165
  const tightened = applyPersistedTightening({ action: "allow" }, read).tightened;
99
166
  if (tightened === "deny")
100
- return { verdict: "deny", layer: "persisted_rules" };
101
- if (tightened === "unreadable")
102
- cannot("persisted_rules", "call_unreadable", read.reason ?? "the call could not be read against the rules");
167
+ return { verdict: "deny", layer: "persisted_rules", ...judged };
168
+ if (tightened === "ask")
169
+ ask("persisted_rules", undefined, false);
170
+ if (read.unreadable === true)
171
+ cannot("persisted_rules", read.reason !== undefined ? "call_unreadable" : "store_unreadable", read.reason ?? "a partition of the person's rule store could not be read, so whether a standing rule forbids this call is unknown");
103
172
  }
104
173
  if (layers.orgRuleLane !== undefined && !spent("org_rules")) {
105
174
  const org = await readOrgRuleLane(layers.orgRuleLane, req, layers.signal, left());
106
175
  if (org.status === "unavailable")
107
176
  cannot("org_rules", left() > 0 ? "org_unavailable" : "timed_out", org.disclosures.join("; ") || "organization governance could not be read");
108
177
  else if (org.verdict?.behavior === "deny")
109
- return { verdict: "deny", layer: "org_rules", message: `an organization policy rule (${org.verdict.rule}) denies it` };
178
+ return { verdict: "deny", layer: "org_rules", message: `an organization policy rule (${org.verdict.rule}) denies it`, ...judged };
179
+ else if (org.verdict?.behavior === "ask")
180
+ ask("org_rules", `an organization policy rule (${org.verdict.rule}) requires approval for it`, true);
110
181
  }
111
- return unanswered ?? { verdict: "no_objection" };
182
+ return held ?? unanswered ?? { verdict: "no_objection", ...judged };
112
183
  };
113
184
  }
@@ -6,7 +6,7 @@
6
6
  * nothing else. Layer 0 vocabulary; `types.ts` re-exports every name below, so no consumer's import
7
7
  * changes.
8
8
  */
9
- import type { ActorAssertion } from "../internal/llm.js";
9
+ import type { ActorAssertion, UserAttachmentKind, UserAttachmentOutcome } from "../internal/llm.js";
10
10
  import type { TaskResult } from "./task-result.js";
11
11
  import type { TaskNotificationPayload } from "./task-notification.js";
12
12
  import type { ProviderCondition, ProviderConditionSource } from "../brain/provider-dialect.js";
@@ -383,17 +383,28 @@ export type TaskEvent = ({
383
383
  * (previously dropped here); the internal `details` payload is NOT exposed (e.g. a sub-agent tool's
384
384
  * `details` is the entire child `TaskResult`).
385
385
  *
386
- * **Type is `unknown` and intentionally non-uniform** — within size it passes through AS-IS to keep
387
- * structure a UI wants (a `string`, or `(TextContent|ImageContent)[]` blocks); when the serialized body
388
- * exceeds the core size cap it degrades to a single TRUNCATED STRING (see {@link truncated}). A consumer
389
- * must handle both shapes.
386
+ * **Type is `unknown` and intentionally non-uniform** — a `string` result stays a string (over the core text cap it is
387
+ * cut and ends with the `…[+N chars truncated]` marker); a `(TextContent|ImageContent|DocumentContent)[]` result stays
388
+ * an array whose blocks keep their slots. One rule for the array (see {@link truncated}): its text blocks (each at its
389
+ * text plus whatever else it carries) — with any block that is neither text nor an admitted image/document, at its
390
+ * serialization — are one character stream under the text cap, cut IN
391
+ * PLACE where the cap runs out (that block ends with the marker, the stream's later blocks leave); MEDIA blocks are not
392
+ * measured against the text cap and ride AS-IS while the leg's media budget lasts (`RunnerDeps.mediaByteCapBytes`,
393
+ * default `AGGREGATE_MEDIA_BUDGET_BYTES`: decoded bytes, spent as base64 on the wire — x 4/3 — after the text's
394
+ * serialized size), past which a block becomes `{ type, mimeType?, bytes, truncated: true }` in its slot (never a
395
+ * string; a media type over 255 characters is not echoed), and when not even a descriptor fits, it and every later
396
+ * media block fold into ONE `{ type: "omitted", blocks, bytes, truncated: true }` in its slot, so the media in an event
397
+ * is bounded at any block count. A consumer must handle both shapes.
390
398
  *
391
399
  * **UNTRUSTED RAW + for OBSERVABILITY ONLY**: it is external tool output (file reads, shell stderr, API
392
400
  * bodies) and may carry secrets/paths/PII — core bounds only its SIZE; a consumer MUST redact and MAY
393
401
  * further bound before it persists or displays it (same contract as `tool_start.args`). It is for a live
394
402
  * UI / trace, NOT for re-feeding to a model: a truncated body would mislead an LLM, and the model already
395
403
  * has the full, untruncated result in its own context — build model prompts from the session, never by
396
- * replaying this field. Absent when the tool produced no content body.
404
+ * replaying this field. The engine's two model-facing readers of it, the observer digest and the sub-agent step
405
+ * outcome, render a block through the one block rendering (text as text, any other block as its `[type: mimeType]`
406
+ * marker, never its payload).
407
+ * Absent when the tool produced no content body.
397
408
  */
398
409
  output?: unknown;
399
410
  /**
@@ -543,29 +554,31 @@ export type TaskEvent = ({
543
554
  */
544
555
  notExecuted?: true;
545
556
  /**
546
- * design/99 §E1 — `true` when {@link output} was SIZE-bounded by core (the full body exceeded the cap and
547
- * was degraded to a truncated string). Lets a consumer detect truncation programmatically instead of
548
- * parsing the `…[+N chars truncated]` marker out of the string. Absent/false = `output` is the complete
549
- * content body. (The full untruncated result still lives in the model's context / `ToolResultStore`.)
557
+ * design/99 §E1 — `true` exactly when {@link output} is not the content the tool returned: text was cut at the cap
558
+ * (the string, or the block at the cut, carries the marker; the stream's later blocks left), a media block was
559
+ * replaced by its descriptor, media folded into the summary, or content that could not be read was replaced by the
560
+ * one `[unserializable tool output]` string. Lets a consumer detect truncation programmatically
561
+ * instead of parsing the marker out of the text; a media block carries `truncated: true` on its own descriptor.
562
+ * Absent/false = `output` IS the returned content, every block in its slot. (The full untruncated result still
563
+ * lives in the model's context / `ToolResultStore`.)
550
564
  */
551
565
  truncated?: boolean;
552
566
  /**
553
- * Present iff {@link truncated}: the TRUE original size (characters) of
554
- * the content this event's {@link output} was degraded from — for a string result, the string's own
567
+ * Present iff {@link truncated} and the content could be read: the TRUE original size (characters) of
568
+ * the value this event's {@link output} was bounded from — for a string result, the string's own
555
569
  * length; for a non-string result (a blocks array), the SUM of each block's own true size (a text
556
- * block's `.text.length`; an image/document block's `.data.length`, i.e. its base64 payload) — never
557
- * `JSON.stringify(content).length`, which is polluted by two different kinds of noise: structural
558
- * overhead (field-name/punctuation bytes that aren't content at all) AND, for text containing quotes/
559
- * backslashes/control characters, escape inflation (the same class of bug this field was created to
560
- * fix in the first place — a real result: 17298 reported vs 13696 true content characters — reappearing
561
- * in a subtler form if the true count were ever taken from the serialized form instead of the blocks).
570
+ * block's `.text.length` plus whatever else it carries at its serialized length; an image/document block's
571
+ * `.data.length`, i.e. its base64 payload, whether the
572
+ * block rode as-is or was replaced by its descriptor; any other block, outside the tool contract, its serialized
573
+ * length) — never `JSON.stringify(content).length`, which is
574
+ * polluted by two different kinds of noise: structural overhead (field-name/punctuation bytes that aren't
575
+ * content at all) AND, for text containing quotes/backslashes/control characters, escape inflation (a
576
+ * real result: 17298 reported vs 13696 true content characters).
562
577
  *
563
- * **Not always arithmetically tied to the marker's own `N`**: the marker counts how much MORE of the
564
- * shown raw-text rendering exists beyond the cut point; `totalChars` counts the true size of the
565
- * ORIGINAL value, which for mixed content includes non-text blocks (e.g. an inlined image) that
566
- * `output` never renders as text at all. The two can legitimately diverge — `totalChars` may run far
567
- * ahead of the cap plus the marker's `N` — and that gap is itself the honest signal that non-text bulk
568
- * was dropped, not an inconsistency to paper over.
578
+ * **Not arithmetically tied to the marker's own `N`**: the marker counts how much MORE of the shown text
579
+ * exists beyond the cut point; `totalChars` counts the whole ORIGINAL value, media blocks included, so it
580
+ * can run far ahead of the cap plus `N`. Read what was cut from the marker (text) and from the
581
+ * descriptors' own `bytes` (media), not from the gap.
569
582
  */
570
583
  totalChars?: number;
571
584
  } & TaskEventIdentity) | ({
@@ -869,6 +882,10 @@ export type TaskEvent = ({
869
882
  * serving layer replaying the ledger); the core's live emissions precede the commit and
870
883
  * omit it — consumers there join via `message_committed` + stream order. */
871
884
  entryId?: string;
885
+ /** Present only on the `"applied"` frame of an objective that declared attachments: per attachment, in order, what
886
+ * the engine read for it and will send (`outcome`) — an admission result, not a delivery. Each attachment is
887
+ * delivered as its own message after the objective; its `message_committed` is the delivery. */
888
+ attachments?: readonly HumanInputAttachment[];
872
889
  } & TaskEventIdentity) | ({
873
890
  /**
874
891
  * design/99 §E3/§E10 — a live BRAIN-LAYER status the brain would otherwise absorb
@@ -897,11 +914,74 @@ export type TaskEvent = ({
897
914
  * fact, not a default: a pre-373 producer event, or a parked record whose tier was not
898
915
  * carried (the terminal sweep's re-pend) — never read absence as `"later"`. */
899
916
  priority?: import("./task-notification.js").SystemInjectionPriority;
917
+ } & TaskEventIdentity) | ({
918
+ /**
919
+ * A DELEGATED CHILD STARTED: the one frame that says who the child is, at the beat it starts —
920
+ * before its first model request, so a consumer opens the child's row (a foreground child's panel
921
+ * line, a background child's fleet entry) without waiting a model turn for the first
922
+ * `task_progress`. Mirrors CC's `system/task_started`.
923
+ *
924
+ * ONE MINT PER CYCLE, at the child leg's spawn station, for every delegation lane alike
925
+ * (foreground, background, fork, workflow agent, named teammate): a first spawn mints it, and so
926
+ * does every revival of a completed child (a new cycle — `seq` moves on, the `taskId`/`sessionId`
927
+ * of the revived run say which). A durable RESUME of a parked leg is the same cycle continuing and
928
+ * mints NOTHING; a child whose prepare throws never reaches the station and mints nothing either
929
+ * (its terminal still arrives on the lane that spawned it). Ordered strictly after the leg's
930
+ * `wiring_manifest` and strictly before anything its first turn produces, so on the forward
931
+ * channel it is the child's first frame. Rides the same door as `task_progress` (the child's own
932
+ * stream, and the forward channel to the deployment); the process-level delegation-lifecycle
933
+ * `spawn` frame is a projection of this mint, delivered at the same instant under the same gate.
934
+ *
935
+ * Live and ephemeral like `task_progress`: never persisted, never replayed on resume, absent from
936
+ * checkpoints. Every member below is a value the run already speaks elsewhere — this frame
937
+ * introduces no new reading of any of them.
938
+ *
939
+ * CC mapping: `task_id` → {@link taskId} (run domain; a background child's registry handle rides
940
+ * {@link TaskEventIdentity.bgAgentId}), `tool_use_id` → `parentToolCallId`, `description` →
941
+ * {@link name}, `subagent_type` → {@link agentType}, `task_type`/`is_backgrounded` → {@link taskType}
942
+ * (absent = a foreground child), `run_id` → {@link seq} (the cycle), `parent_task_id` →
943
+ * {@link parentTaskId}, `session_id` → {@link sessionId}. Not carried: `prompt` (a delegated
944
+ * objective can hold a secret — the same reason {@link name} never falls back to it), `awaited`,
945
+ * `spawn_depth` (the nesting is the `parentTaskId` chain), and the shell/monitor-only members.
946
+ */
947
+ type: "task_started";
948
+ /** The child RUN's id (`spec.taskId ?? sessionId`) — the key its `task_progress` frames carry. */
949
+ taskId: string;
950
+ /** The child run's session id — the handle its transcript is read by. */
951
+ sessionId: string;
952
+ /** The fleet-task kind, exactly as the child's `task_progress.taskType`: ABSENT for a foreground
953
+ * (synchronous) child, which has no fleet row. */
954
+ taskType?: DelegationTaskType;
955
+ /** The registry row's cycle generation, exactly as `task_progress.seq` (absent where the child has no
956
+ * `a*` row). A revived cycle's frame carries the bumped value. */
957
+ seq?: number;
958
+ /** Workflow-lane self-identification, as on `task_progress` (stamped where the workflow forwards). */
959
+ workflowRunId?: string;
960
+ /** The workflow agent's label, riding with {@link workflowRunId}. */
961
+ workflowAgentLabel?: string;
962
+ /** The spawning run's task id, exactly as `task_progress.parentTaskId`. */
963
+ parentTaskId?: string;
964
+ /** The spawning run's session id (the always-on parent linkage). */
965
+ parentSessionId?: string;
966
+ /** The delegation tree's root host session. */
967
+ rootSessionId?: string;
968
+ /** The child's display label, the same bounded single-line value its `task_progress.name` and its
969
+ * hook identity's `agentName` carry. UNTRUSTED display text. */
970
+ name?: string;
971
+ /** The explicit `Agent({name})` spawn identity (the teammate key), when the spawn carried one. */
972
+ spawnName?: string;
973
+ /** The resolved subagent type the child was spawned as (absent on a workflow-lane agent). */
974
+ agentType?: string;
975
+ /** The model the child leg was PREPARED with — the value its `task_progress.model` and
976
+ * `TaskResult.model` carry. */
977
+ model: string;
978
+ /** Epoch ms at the mint. */
979
+ startedAt: number;
900
980
  } & TaskEventIdentity) | ({
901
981
  /**
902
982
  * design/99 MF-10 — a SUBAGENT PROGRESS TICK: the delegated sub-run's usage ACCRUING
903
983
  * over time, emitted at each turn boundary WHILE it runs, so a parent UI animates the Task card's rollup
904
- * (tokens/tool-uses climbing). Emitted ONLY for a subagent (a task with `parentToolCallId`); a top-level
984
+ * (tokens/tool-uses climbing). Emitted ONLY for a delegated leg (the same eligibility as `task_started`); a top-level
905
985
  * run's own usage is the consumer's own. `status:"running"` mid-run; the terminal usage is the `done` result.
906
986
  * Live/ephemeral — NOT persisted (a resumable trace re-derives usage from the result), like `status`.
907
987
  * Maps to CC `SDKTaskProgress` (camelCase here → snake_case `task_id`/`total_tokens`/`tool_uses`/`duration_ms`
@@ -1075,6 +1155,14 @@ export type TaskEvent = ({
1075
1155
  export type HumanInputEvent = Extract<TaskEvent, {
1076
1156
  type: "human_input";
1077
1157
  }>;
1158
+ /** One attachment on a `human_input` frame: its position, the kind the engine found (absent when the read was refused
1159
+ * before the kind was known), what reached the model of its contents, and the path or name it was declared with. */
1160
+ export interface HumanInputAttachment {
1161
+ readonly ordinal: number;
1162
+ readonly kind?: UserAttachmentKind;
1163
+ readonly outcome: UserAttachmentOutcome;
1164
+ readonly label: string;
1165
+ }
1078
1166
  /**
1079
1167
  * Outcome of a manual {@link TaskStream.compact} request (the enum names align with
1080
1168
  * the diagnosis decision tree's drain exits, so a consumer can tell "202 processed" states apart):