@sema-agent/core 7.5.2 → 7.6.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
@@ -0,0 +1,189 @@
1
+ /**
2
+ * The settlement record and the gate outcome — the L0 vocabulary of what ONE tool-gate pass decided,
3
+ * what ask it settled and who asked. One structure, minted once (the gate's exit; the decide lane for a
4
+ * durable park) and projected three times without re-derivation: the `permissionDenied` observer's
5
+ * payload, the call's `tool_end` frame, and the durable row's resolved outcome. Lives beside
6
+ * `tool-policy.ts` rather than in it because it is a different concern from the permission verdict a
7
+ * policy composes: a policy says allow / ask / deny; this file says how the pass that consumed that
8
+ * verdict ended.
9
+ */
10
+ import { type AskOrigin, type AssertAllKeysHandled } from "./ask-origin.js";
11
+ /**
12
+ * How the wait an ask was in ENDED — the closed set. One word per way a wait can end; the `who` on the
13
+ * record says which party ended it and is fixed BY the word (see {@link Settlement}).
14
+ * - `human_allowed` / `human_refused` — a person (or the approver acting for one) decided; the refusal's
15
+ * own note, when the decider attached one, rides `note`.
16
+ * - `approval_window_expired` — an approval window elapsed with no answer: the engine's own approval
17
+ * factory window (`createApprovalPolicy.approvalTimeoutMs`), or a synchronous host approver that
18
+ * reported its own window elapsed.
19
+ * - `denial_limit_window_expired` — the classifier denial-limit fallback's auto-deny window (core's own
20
+ * timer over the synchronous leg) elapsed with no answer.
21
+ * - `park_sla_expired` — a DURABLE park's SLA deadline passed and the host's sweep resolved it as a deny
22
+ * (FACET A). A store `expire`/`reap` produces no `tool_end` and therefore no settlement.
23
+ * - `no_approver` — headless: no approver (or no question face) is wired, or the deny posture string.
24
+ * - `approver_unavailable` — the approver (or an ancestor's frozen approver) answered the ROUTING question
25
+ * "nobody reachable", and no park took the ask.
26
+ * - `approver_error` — the approver threw.
27
+ * - `approver_contract` — the approver answered outside its contract (a non-boolean verdict, an
28
+ * unreadable member, a timeout-settled allow, a refused attribution…).
29
+ * - `presentation_failed` — the args or the edit could not be safely presented/adopted (unclonable).
30
+ * - `blanket_allow_refused` — a blanket `onAsk:"allow"` met a `requiresRealApproval` ask.
31
+ * - `task_aborted` — the wait's abort signal ended it (the run's own end, or a turn-level interrupt).
32
+ */
33
+ export declare const SETTLEMENT_KINDS: readonly ["human_allowed", "human_refused", "approval_window_expired", "denial_limit_window_expired", "park_sla_expired", "no_approver", "approver_unavailable", "approver_error", "approver_contract", "presentation_failed", "blanket_allow_refused", "task_aborted"];
34
+ export type SettlementKind = (typeof SETTLEMENT_KINDS)[number];
35
+ /** Membership test for {@link SettlementKind} — the word crosses process boundaries on `tool_end`, so a
36
+ * consumer validating one it received must not hand-roll the set. */
37
+ export declare function isSettlementKind(v: unknown): v is SettlementKind;
38
+ /**
39
+ * The settlement record: WHAT ended the wait (`kind`), WHO ended it (`who` — its shape is decided by the
40
+ * word: the two human words carry a person, the window words say whose window, the park word the host,
41
+ * the fail-closed family nobody), WHEN (epoch ms, written at the settlement site), and for a person's
42
+ * refusal the decider's own NOTE when one was attached (a refusal WITHOUT a note is a bare "no", the arm
43
+ * the runner treats as a control-flow boundary for the issuing batch).
44
+ *
45
+ * It is a discriminated union and not a free product of `kind` × `who` on purpose: a free product can
46
+ * express "human_allowed, ended by the engine's window" — a structurally complete record that contradicts
47
+ * itself — and a consumer would then need the pairing rules this shape makes unwritable.
48
+ *
49
+ * @contract settlement.single_mint — every `Settlement` kind is composed by CORE at the site whose wait
50
+ * ended (the ask resolver's arms, the approval factory's arms, the decide lane over a host's decision
51
+ * FACTS). A host never mints a kind: a synchronous approver reports `"human"`/`"timeout"` on its outcome
52
+ * and a durable decide reports `hostDecision.decidedBy` (`"person"`/`"sla_timeout"`) — facts core turns
53
+ * into the word. Nothing on the caller-composable policy face (`PermissionResult`) carries a settlement,
54
+ * so a policy cannot self-declare one; the engine's own settlement sites that hand a settled decision
55
+ * back through a policy fold attest it through {@link engineSettlementOf}.
56
+ */
57
+ export type Settlement = {
58
+ kind: "human_allowed" | "human_refused";
59
+ who: {
60
+ party: "person";
61
+ approver?: string;
62
+ };
63
+ when: number;
64
+ note?: string;
65
+ } | {
66
+ kind: "approval_window_expired";
67
+ who: {
68
+ party: "engine";
69
+ window: "approval_factory";
70
+ } | {
71
+ party: "host";
72
+ };
73
+ when: number;
74
+ } | {
75
+ kind: "denial_limit_window_expired";
76
+ who: {
77
+ party: "engine";
78
+ window: "denial_limit";
79
+ };
80
+ when: number;
81
+ } | {
82
+ kind: "park_sla_expired";
83
+ who: {
84
+ party: "host";
85
+ approver?: string;
86
+ };
87
+ when: number;
88
+ } | {
89
+ kind: "no_approver" | "approver_unavailable" | "approver_error" | "approver_contract" | "presentation_failed" | "blanket_allow_refused" | "task_aborted";
90
+ who: {
91
+ party: "none";
92
+ };
93
+ when: number;
94
+ };
95
+ /** Which settlement kinds are REFUSALS (the ask ended as a deny). Total over the closed set — the fence
96
+ * below refuses a member without a row — so invariant I2 is a table lookup, not a list in a condition. */
97
+ export declare const SETTLEMENT_IS_REFUSAL: {
98
+ readonly human_allowed: false;
99
+ readonly human_refused: true;
100
+ readonly approval_window_expired: true;
101
+ readonly denial_limit_window_expired: true;
102
+ readonly park_sla_expired: true;
103
+ readonly no_approver: true;
104
+ readonly approver_unavailable: true;
105
+ readonly approver_error: true;
106
+ readonly approver_contract: true;
107
+ readonly presentation_failed: true;
108
+ readonly blanket_allow_refused: true;
109
+ readonly task_aborted: true;
110
+ };
111
+ /** Compile-time fence: `never` while every settlement kind has a refusal row. */
112
+ export type SettlementRefusalTableCoversEveryKind = AssertAllKeysHandled<Exclude<SettlementKind, keyof typeof SETTLEMENT_IS_REFUSAL>>;
113
+ /**
114
+ * WHO REFUSED a call — the layer whose verdict is the deny. The other question a deny raises, "who ASKED",
115
+ * is answered by {@link GateOutcome.origin} (an {@link import("./ask-origin.js").AskOrigin}); the two used
116
+ * to share one seven-word list (`PermissionDeniedSource`) in which `classifier` meant both "the classifier
117
+ * asked" and "the classifier denied".
118
+ * - `policy` — the deployment `ToolPolicy` denied (directly, or re-checking an approved edit), or the
119
+ * approval-edit chain hit its round cap.
120
+ * - `hook` — a PreToolUse hook denied, threw, or never answered (the engine's fail-closed block on a
121
+ * hook that made no decision counts as the hook layer's refusal for attribution).
122
+ * - `org` — an organization policy rule denied.
123
+ * - `classifier` — the auto-mode classifier denied directly (its ASK-side role is `origin:
124
+ * "denial_limit_fallback"`).
125
+ * - `plan_mode` — plan mode's read-only block on a write tool.
126
+ * - `compliance` — the compliance call-time lock.
127
+ * - `write_protection` — an approved edit was rewritten by the restriction chain onto a write-protected
128
+ * path that no approval covers.
129
+ * - `ask_resolution` — the ask's own settlement is the refusal (a person said no, a window elapsed,
130
+ * nobody could be asked…); the detail is on {@link GateOutcome.settlement}.
131
+ */
132
+ export declare const DENIED_BY_VALUES: readonly ["policy", "hook", "org", "classifier", "plan_mode", "compliance", "write_protection", "ask_resolution"];
133
+ export type DeniedBy = (typeof DENIED_BY_VALUES)[number];
134
+ /** Membership test for {@link DeniedBy}. */
135
+ export declare function isDeniedBy(v: unknown): v is DeniedBy;
136
+ /** Which layers may VETO an approval a person already gave (invariant I3: a `human_allowed` settlement
137
+ * beside a `denied` disposition names one of these). The approval-edit re-screen (hook), the policy and
138
+ * org re-checks, and the write-protection re-judge run AFTER the person's yes; the classifier, plan mode
139
+ * and compliance run before any ask exists, and `ask_resolution` IS the settlement, not a veto of it. */
140
+ export declare const DENIED_BY_MAY_VETO: {
141
+ readonly policy: true;
142
+ readonly hook: true;
143
+ readonly org: true;
144
+ readonly classifier: false;
145
+ readonly plan_mode: false;
146
+ readonly compliance: false;
147
+ readonly write_protection: true;
148
+ readonly ask_resolution: false;
149
+ };
150
+ /** Compile-time fence: `never` while every deny layer has a veto row. */
151
+ export type VetoTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedBy, keyof typeof DENIED_BY_MAY_VETO>>;
152
+ /** The final disposition of a gate pass. */
153
+ export type GateDisposition = {
154
+ kind: "allowed";
155
+ } | {
156
+ kind: "denied";
157
+ deniedBy: DeniedBy;
158
+ };
159
+ /**
160
+ * The WHOLE record of one tool-gate pass: the final disposition, plus — when this pass settled an ask —
161
+ * the settlement it consumed and the ask's origin. Minted ONCE (the gate's exit; the decide lane for a
162
+ * durable park) and projected three times without re-derivation: the `permissionDenied` observer's
163
+ * payload, the call's `tool_end` frame, and the durable row's resolved outcome. The three faces are the
164
+ * same object graph, so they cannot tell different stories.
165
+ *
166
+ * The three members are orthogonal but bound by four invariants ({@link screenGateOutcome}):
167
+ * I1 `settlement` present ⇔ `origin` present (they describe the same ask);
168
+ * I2 `disposition.deniedBy === "ask_resolution"` ⇒ `settlement` present and a refusal kind;
169
+ * I3 `settlement.kind === "human_allowed"` ∧ `disposition.kind === "denied"` ⇒ `deniedBy` is a veto
170
+ * layer ({@link DENIED_BY_MAY_VETO}) — a person approved, a later re-check refused; the approver stays
171
+ * on the settlement (it is not the approver's refusal);
172
+ * I4 `disposition.kind === "allowed"` ⇒ `settlement` absent or `human_allowed`.
173
+ * Nothing here is read out of an ABSENCE: an ordinary allow with no ask is `{ disposition: { kind: "allowed" } }`,
174
+ * a direct policy deny is `{ disposition: { kind: "denied", deniedBy: "policy" } }`, and neither carries a
175
+ * settlement because neither settled one.
176
+ */
177
+ export interface GateOutcome {
178
+ disposition: GateDisposition;
179
+ settlement?: Settlement;
180
+ origin?: AskOrigin;
181
+ }
182
+ /**
183
+ * The one invariant screen for a {@link GateOutcome} (I1–I4 above, plus the shape of each member). Runs
184
+ * where an outcome enters a wire face — the engine's settlement sideband before the `tool_end` frame, and
185
+ * the decide lane before a durable row is resolved. Returns the defects as sentences (an empty list is a
186
+ * clean record); the caller decides the fail-loud form (report + withhold, or refuse), never silently
187
+ * repairs the record.
188
+ */
189
+ export declare function screenGateOutcome(g: GateOutcome): string[];
@@ -0,0 +1,70 @@
1
+ import { isAskOrigin } from "./ask-origin.js";
2
+ export const SETTLEMENT_KINDS = [
3
+ "human_allowed",
4
+ "human_refused",
5
+ "approval_window_expired",
6
+ "denial_limit_window_expired",
7
+ "park_sla_expired",
8
+ "no_approver",
9
+ "approver_unavailable",
10
+ "approver_error",
11
+ "approver_contract",
12
+ "presentation_failed",
13
+ "blanket_allow_refused",
14
+ "task_aborted",
15
+ ];
16
+ const SETTLEMENT_KIND_SET = new Set(SETTLEMENT_KINDS);
17
+ export function isSettlementKind(v) {
18
+ return SETTLEMENT_KIND_SET.has(v);
19
+ }
20
+ export const SETTLEMENT_IS_REFUSAL = {
21
+ human_allowed: false,
22
+ human_refused: true,
23
+ approval_window_expired: true,
24
+ denial_limit_window_expired: true,
25
+ park_sla_expired: true,
26
+ no_approver: true,
27
+ approver_unavailable: true,
28
+ approver_error: true,
29
+ approver_contract: true,
30
+ presentation_failed: true,
31
+ blanket_allow_refused: true,
32
+ task_aborted: true,
33
+ };
34
+ export const DENIED_BY_VALUES = ["policy", "hook", "org", "classifier", "plan_mode", "compliance", "write_protection", "ask_resolution"];
35
+ const DENIED_BY_SET = new Set(DENIED_BY_VALUES);
36
+ export function isDeniedBy(v) {
37
+ return DENIED_BY_SET.has(v);
38
+ }
39
+ export const DENIED_BY_MAY_VETO = {
40
+ policy: true,
41
+ hook: true,
42
+ org: true,
43
+ classifier: false,
44
+ plan_mode: false,
45
+ compliance: false,
46
+ write_protection: true,
47
+ ask_resolution: false,
48
+ };
49
+ export function screenGateOutcome(g) {
50
+ const defects = [];
51
+ const d = g.disposition;
52
+ if (d.kind === "denied" && !isDeniedBy(d.deniedBy))
53
+ defects.push(`disposition.deniedBy "${String(d.deniedBy)}" is outside the closed set`);
54
+ const s = g.settlement;
55
+ if (s !== undefined && !isSettlementKind(s.kind))
56
+ defects.push(`settlement.kind "${String(s.kind)}" is outside the closed set`);
57
+ if (g.origin !== undefined && !isAskOrigin(g.origin))
58
+ defects.push(`origin "${String(g.origin)}" is outside the closed set`);
59
+ if ((s !== undefined) !== (g.origin !== undefined))
60
+ defects.push("I1: settlement and origin must be present together — they describe the same ask");
61
+ if (d.kind === "denied" && d.deniedBy === "ask_resolution" && (s === undefined || !isSettlementKind(s.kind) || !SETTLEMENT_IS_REFUSAL[s.kind])) {
62
+ defects.push("I2: deniedBy ask_resolution requires a settlement whose kind is a refusal");
63
+ }
64
+ if (s !== undefined && s.kind === "human_allowed" && d.kind === "denied" && isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_VETO[d.deniedBy]) {
65
+ defects.push(`I3: a human_allowed settlement beside a deny must name a veto layer, not "${d.deniedBy}"`);
66
+ }
67
+ if (d.kind === "allowed" && s !== undefined && s.kind !== "human_allowed")
68
+ defects.push(`I4: an allowed disposition cannot carry a "${s.kind}" settlement`);
69
+ return defects;
70
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * design/138 S2-C — the hands band's write-time CONTENT hook (single source; `src/tools/fs` re-exports
3
+ * these so its import surface is unchanged — moved here by design/141 件2 because `RunnerDeps.hands`
4
+ * carries the deployment-composable half of the hook). Called before EVERY `env.writeFile` in
5
+ * Write/Edit/NotebookEdit with the resolved containment key and the exact final text; `{ ok:false }`
6
+ * fails the tool with a structured error and NOTHING is written. A THROWING hook fails CLOSED.
7
+ */
8
+ export interface BeforeWriteRequest {
9
+ tool: "Write" | "Edit" | "NotebookEdit";
10
+ /** The model-supplied path argument (for the error-message coordinate the model knows). */
11
+ path: string;
12
+ /** The resolved canonical containment key — what the hook should judge. */
13
+ key: string;
14
+ /** The FINAL full text about to be written (Edit/NotebookEdit: after application). */
15
+ content: string;
16
+ }
17
+ export type BeforeWriteResult = {
18
+ ok: true;
19
+ } | {
20
+ ok: false;
21
+ code: string;
22
+ reason: string;
23
+ };
24
+ export type BeforeWriteHook = (req: BeforeWriteRequest) => BeforeWriteResult | undefined | Promise<BeforeWriteResult | undefined>;
25
+ /**
26
+ * design/381 — the hands band's FIRST-TOUCH HISTORY hook, called before EVERY file mutation in
27
+ * Write/Edit/NotebookEdit (after the `beforeWrite` content gate passed, immediately before the
28
+ * final env write) with the resolved canonical containment key. The Runner wires it to
29
+ * `FileHistoryStore.trackEdit` (backup-before-first-edit, the CC FileHistory trigger-point set
30
+ * minus the bash simulation arm sema does not have — DV-7); later calls for an already-tracked
31
+ * path are cheap store no-ops. `{ok:false}` carries the REFUSAL TEXT the tool must answer instead
32
+ * of writing (DV-14 default: no durable first-touch state ⇒ the edit is refused, typed and loud —
33
+ * any proceed-unprotected policy is resolved INSIDE the hook by the Runner, never by the band).
34
+ * A THROWING hook fails CLOSED (same posture as the write gate). Absent hook ⇒ byte-identical
35
+ * behavior (no history store wired).
36
+ */
37
+ export interface TrackEditRequest {
38
+ tool: "Write" | "Edit" | "NotebookEdit";
39
+ /** The model-supplied path argument (for the error-message coordinate the model knows). */
40
+ path: string;
41
+ /** The resolved canonical containment key — the identity the history record is keyed on. */
42
+ key: string;
43
+ /** The tool call's abort signal, threaded into the store's env read. */
44
+ signal?: AbortSignal;
45
+ }
46
+ export type TrackEditResult = {
47
+ ok: true;
48
+ /**
49
+ * #491 — the compensating handle for the ordering the topology cannot avoid: the pre-image
50
+ * capture runs BEFORE the write, so a write that then FAILS (lost create race, ENOSPC,
51
+ * EACCES, read-only mount) would leave a first-touch record for an edit that never happened —
52
+ * and an `existed-not` record makes an untouched path a rewind DELETE target, removing bytes
53
+ * whoever writes them next (the user, a peer process) owns. Present ONLY when THIS call minted
54
+ * the record (an already-tracked path belongs to the edit that first touched it and must never
55
+ * be discarded); the band calls it on the write-failure arm of every mutation lane. `proof`
56
+ * says which retraction rule applies: `"proven"` = the write's own error code guarantees
57
+ * nothing was written, `"verify"` = ambiguous, so the history seat must re-read the path and
58
+ * retract only if it still matches what the first touch recorded. It never throws and never
59
+ * blocks the failure it accompanies — the original write error is the answer.
60
+ */
61
+ annul?: (proof: "proven" | "verify") => Promise<void>;
62
+ } | {
63
+ ok: false;
64
+ refusal: string;
65
+ };
66
+ export type TrackFileEditHook = (req: TrackEditRequest) => Promise<TrackEditResult>;
67
+ /**
68
+ * The hands band's MUTATION-EDITED observation seat — the counterpart of {@link TrackEditRequest},
69
+ * fired at the other end of the same lane. Called once per Write/Edit/NotebookEdit call whose FINAL
70
+ * env write did not PROVABLY write nothing, at the single point every mutation lane funnels through,
71
+ * under the same three-valued law as the first-touch record's own retraction:
72
+ * - a call the argument checks, the read-before-edit gate, the write gate or the first-touch
73
+ * history seat refused never fires it (nothing was written);
74
+ * - a call whose write FAILED with a code whose contract says nothing was written never fires it;
75
+ * - a call whose write failed AMBIGUOUSLY (a non-atomic env that truncated and then errored) or
76
+ * THREW fires it — exactly the arm where the retraction KEEPS the first-touch record because the
77
+ * file may really have changed. The two seats agree on purpose: a file whose baseline was kept
78
+ * for a possible modification must also be listed as possibly modified;
79
+ * - a `Bash` command that changed a file never fires it: bash does not run through this band's
80
+ * write lanes at all, which is the whole reason this seat is not a tool-name table.
81
+ * PURE OBSERVATION: it returns nothing and it cannot refuse. A FAULT in it is contained in both
82
+ * shapes an observer can fail in — a synchronous throw, and (the return type is `void`, but an
83
+ * `async` function still type-checks there) a rejected promise, which is sunk rather than left
84
+ * unhandled. Neither is awaited: an observer must never turn a landed write into a failed tool
85
+ * answer, nor delay one.
86
+ */
87
+ export interface FileEditedNotice {
88
+ tool: "Write" | "Edit" | "NotebookEdit";
89
+ /** The model-supplied path argument — the SAME coordinate the delegated-child projection's
90
+ * `SubagentEditedFile.path` carries, so the two seats can be read side by side. */
91
+ path: string;
92
+ /** The resolved canonical containment key the bytes actually landed on. */
93
+ key: string;
94
+ }
95
+ export type FileEditedHook = (notice: FileEditedNotice) => void;
96
+ /**
97
+ * design/141 件2 — the SAFE deployment-configurable subset of the hands toolkit ({@link RunnerDeps.hands}).
98
+ * Only fields whose injection is purely additive for a deployment are here; Runner-internal orchestration
99
+ * state (taskRegistry/detachHub/execClamp/cwdRef/mount coordination) is deliberately NOT configurable.
100
+ */
101
+ export interface HandsBandOptions {
102
+ /** Override the `bash_readonly` command allowlist (read-only band). Default: the built-in read-only set. */
103
+ bashReadonlyAllow?: readonly string[];
104
+ /** Co-Authored-By trailer for the bash git protocol. Default NONE ([c209] BREAKING: attribution is a
105
+ * deployment identity asset — the branded scenario sets `"Name <email>"`); `false` ≡ unset. */
106
+ commitCoAuthor?: string | false;
107
+ /** Read-tool image downsampler: inject your own, or `false` to force-disable (deterministic no-sharp).
108
+ * Omitted ⇒ auto-detect sharp. Same contract as the MCP-side {@link import("./mcp.js").ImageDownsampler}. */
109
+ readImageDownsampler?: ((input: Buffer, mimeType: string) => Promise<import("./mcp.js").DownsampledImage | undefined>) | false;
110
+ /** Deployment write gate — COMPOSED with (never replacing) the engine's MemoryEngine write-scan gate:
111
+ * this hook judges first (a rejection short-circuits), the engine gate then still runs in full. */
112
+ beforeWrite?: BeforeWriteHook;
113
+ /** RB-198 F1 (CC 220 `Zry`/`WZi.#m` parity): on a foreground Bash command's OWN timeout, an eligible
114
+ * command (see `canAutoBackground` — no `git` anywhere in the command, not a `sleep`-first-word wait) is
115
+ * adopted as a background task instead of being killed — CC's own posture ("received no response for
116
+ * Nms" would otherwise force a wasteful blind re-run of a non-idempotent long command). Default `false`
117
+ * (byte-compat): this is a deployment-level opt-in, deliberately NOT inferred from background-task
118
+ * plumbing (`detachHub`/`taskRegistry`) merely being present, since the Runner wires those unconditionally
119
+ * for every task — tying eligibility to their mere presence would silently flip the timeout outcome for
120
+ * every existing caller with no explicit signal at all. */
121
+ autoBackgroundOnTimeout?: boolean;
122
+ /** Append the per-read content-safety reminder after a successful text read (the ~50-token
123
+ * `<system-reminder>` the Read tool adds to a text body — the plain-text and notebook read paths;
124
+ * PDF text extraction has its own result builder and has never carried the reminder, so this switch
125
+ * does not govern it). Default TRUE, and an omitted field keeps
126
+ * that default: a BYOM engine cannot assume its serving model carries that mitigation internally, and
127
+ * the reminder only works because it sits next to the bytes it is about — not in a system prompt
128
+ * hundreds of turns back.
129
+ *
130
+ * Pass `false` to state that THIS deployment's serving model does carry it, and stop paying the
131
+ * reminder's tokens on every text read for advice the model already applies. Absent and `false` are
132
+ * deliberately different decisions: absent means "unknown, so assume not". */
133
+ readCyberReminder?: boolean;
134
+ }
@@ -0,0 +1 @@
1
+ export {};