@sema-agent/core 5.32.0 → 5.34.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 (46) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist/agents/cascade.d.ts +49 -1
  3. package/dist/agents/cascade.js +2 -2
  4. package/dist/agents/verify.d.ts +70 -4
  5. package/dist/agents/verify.js +62 -16
  6. package/dist/core/checkpoint-store.d.ts +95 -0
  7. package/dist/core/checkpoint-store.js +40 -0
  8. package/dist/core/hooks.d.ts +14 -6
  9. package/dist/core/hooks.js +14 -3
  10. package/dist/core/memory-engine/file-backend.d.ts +172 -22
  11. package/dist/core/memory-engine/file-backend.js +877 -79
  12. package/dist/core/memory-engine/memory-backend-contract.js +33 -0
  13. package/dist/core/push-queue.d.ts +4 -1
  14. package/dist/core/push-queue.js +2 -1
  15. package/dist/core/runner/assemble-result.d.ts +7 -0
  16. package/dist/core/runner/assemble-result.js +1 -1
  17. package/dist/core/runner/prepare-acquire-reconcile.d.ts +72 -0
  18. package/dist/core/runner/prepare-acquire-reconcile.js +126 -0
  19. package/dist/core/runner/prepare-config-doors.d.ts +140 -0
  20. package/dist/core/runner/prepare-config-doors.js +250 -0
  21. package/dist/core/runner/prepare-safety-scan.d.ts +53 -0
  22. package/dist/core/runner/prepare-safety-scan.js +80 -0
  23. package/dist/core/runner/prepare-task.d.ts +40 -81
  24. package/dist/core/runner/prepare-task.js +83 -586
  25. package/dist/core/runner/prepare-workspace-restore.d.ts +102 -0
  26. package/dist/core/runner/prepare-workspace-restore.js +144 -0
  27. package/dist/core/runner/runtask.js +18 -4
  28. package/dist/core/safe-notify.d.ts +9 -0
  29. package/dist/core/safe-notify.js +9 -0
  30. package/dist/core/tool-policy.d.ts +25 -0
  31. package/dist/core/types.d.ts +140 -13
  32. package/dist/index.d.ts +4 -3
  33. package/dist/index.js +1 -1
  34. package/dist/orchestration/workflow-governance.d.ts +6 -4
  35. package/dist/tools/fs/bash-readonly-classifier.d.ts +9 -3
  36. package/dist/tools/fs/bash-readonly-classifier.js +4 -1
  37. package/dist/tools/fs/fs-bash.d.ts +19 -3
  38. package/dist/tools/fs/fs-bash.js +26 -1
  39. package/dist/tools/fs/index.d.ts +20 -4
  40. package/dist/tools/fs/index.js +4 -1
  41. package/dist/tools/fs/read-deny.d.ts +66 -8
  42. package/dist/tools/fs/read-deny.js +75 -39
  43. package/dist/tools/fs/read-face.d.ts +3 -2
  44. package/dist/tools/fs/search.js +2 -0
  45. package/package.json +2 -1
  46. package/test/export-surface.snapshot.json +1552 -0
@@ -0,0 +1,102 @@
1
+ import type { RemoteExecutionEnv, RemoteExecutionError, SnapshotId, VmLifecycleOptions } from "../remote-env.js";
2
+ import type { ExecutionEnv } from "../../internal/harness.js";
3
+ import type { RemoteEnvFailureNote, RunnerDeps, TaskSpec } from "../types.js";
4
+ import type { PrepareResume, RunInternals } from "./prepare-task.js";
5
+ /**
6
+ * codex 1360 r2-r4 — rebase one checkpointed absolute path from the OLD workspace root onto the RESTORED
7
+ * one (divergent `resumeVM`). POSIX-ONLY by contract: every remote lane's `mountPath` is a container
8
+ * path (e2b/k8s/ssh/adb/local-docker are all Linux targets), so a backslash ANYWHERE in the inputs marks
9
+ * the value outside this function's domain and it returns `p` UNCHANGED — an un-rebased path is honestly
10
+ * observable (the divergence observation already fired) while a WRONGLY-rebased one silently corrupts
11
+ * cwd/read-state (r4: drive-root and cross-family recomposition are not implementable without a Windows
12
+ * path model no lane needs). Trailing slashes are tolerated (from="/app/" must not weld the suffix);
13
+ * bare "/" keeps filesystem-root semantics; a path outside `from` (incl. the prefix-sibling
14
+ * "/application" vs "/app") returns unchanged. Exported for direct unit pinning.
15
+ */
16
+ export declare function rebaseWorkspacePath(p: string, fromRaw: string, toRaw: string): string;
17
+ /**
18
+ * RB-439-a — project one failed remote lifecycle call onto the caller-facing note. ONE builder for all
19
+ * three ops so the `retryable` classification can never drift between the lane that reports a `suspendVM`
20
+ * refusal and the lane that reports a failed restore.
21
+ */
22
+ export declare function remoteEnvFailureNote(op: RemoteEnvFailureNote["op"], error: RemoteExecutionError, attempts: number): RemoteEnvFailureNote;
23
+ /**
24
+ * RB-439-a — `resumeVM` with a bounded retry on the TRANSIENT codes, plus the attempt count for the
25
+ * caller-facing note.
26
+ *
27
+ * Why this op and no other: `resumeVM` is the one lifecycle call the seam documents as "idempotent +
28
+ * re-entrant" (a replica that crashed mid-suspend can be superseded by another resuming the SAME
29
+ * snapshot), which is exactly `withRetry`'s red line — retry idempotent establishment, never a
30
+ * side-effecting op. `suspendVM` TAKES a snapshot and `postResumeInit` re-injects secrets under an
31
+ * ordering red line; neither is retried here, they are disclosed with `retryable` instead so the caller
32
+ * decides. A code outside the retryable family (`unsupported`, `auth_failed`) returns on the first
33
+ * attempt — `withRetry` will not spend a second call on a permanent refusal.
34
+ */
35
+ export declare function restoreWorkspaceWithRetry(env: RemoteExecutionEnv, snapshotId: SnapshotId, options: VmLifecycleOptions): Promise<{
36
+ outcome: Awaited<ReturnType<RemoteExecutionEnv["resumeVM"]>>;
37
+ attempts: number;
38
+ }>;
39
+ /**
40
+ * RB-439-c — {@link rebaseWorkspacePath} over a SET of accepted spellings of the old root: the first
41
+ * prefix that actually matches wins, and a path under none of them is returned unchanged.
42
+ *
43
+ * The set exists because "the old root" has no single spelling. `WorkspaceHandle.mountPath` is whatever the
44
+ * adapter called the mount, while the persisted paths being migrated were spelled by whoever produced them
45
+ * (a `cd` the env canonicalized, a Read key resolved through realpath). On a target where the root has an
46
+ * equivalent alias (`/var` ↔ `/private/var`) those disagree while naming the same directory, and a
47
+ * single-prefix rebase then matched nothing and silently left the resumed shell in the pre-suspend tree —
48
+ * under a disclosure that said the task follows the restored root. Order is caller-chosen (the checkpointed
49
+ * spelling first, its canonical form second) and only matters if one prefix is a prefix of another, in
50
+ * which case the earlier — more specific — spelling is the intended one. Exported for direct unit pinning.
51
+ */
52
+ export declare function rebaseWorkspacePathAcross(p: string, froms: readonly string[], to: string): string;
53
+ export interface PrepareWorkspaceRestoreInput {
54
+ /** borrowed-readonly (the ownership-ref PROTOCOL stays with the driver — D-8 案①): the env this
55
+ * task owns, when the factory minted one. This phase reads and drives it (`resumeVM`,
56
+ * `postResumeInit`, `canonicalPath`) but NEVER rebinds or destroys it — the detach guard and the
57
+ * prepare-throw catch are the exception-path owners, both in the driver, and their reference
58
+ * must stay authoritative (a phase-local rebind could never reach them). */
59
+ ownedEnv: ExecutionEnv | undefined;
60
+ /** borrowed-readonly — the two deployment seats this phase reads, as a Pick over the SAME `deps`
61
+ * object (相 API 规则件 R-4: receiver preserved for `deps.onError?.()`; a new deps read must
62
+ * widen this Pick). `executionEnv` feeds only the observation sink's remote predicate. */
63
+ deps: Pick<RunnerDeps, "onError" | "executionEnv">;
64
+ /** borrowed-readonly — `signal` only: the caller's cancel signal, composed into the restore's
65
+ * abortSignal so a cancel DURING the restore short-circuits it (the live listener is attached
66
+ * only after the harness exists). */
67
+ spec: Pick<TaskSpec, "signal">;
68
+ /** borrowed-readonly — the trusted spawn-side seats the observation sink reads: the sink itself
69
+ * and the isolation REQUEST (by this point past the driver's fail-closed enforcement, so
70
+ * `isolated: true` is a receipt, not a restatement). */
71
+ internals: Pick<RunInternals, "onWorkspaceResolved" | "isolation"> | undefined;
72
+ /** borrowed-readonly — the durable-resume seats this phase reads: the checkpointed
73
+ * `workspaceHandle` (restore target) and `executesApprovedAction` (the divergence fail-closed
74
+ * key — codex 1360 r6). Absent for a fresh/non-durable run: the phase is then root-passthrough. */
75
+ resume: Pick<PrepareResume, "workspaceHandle" | "executesApprovedAction"> | undefined;
76
+ /** owned (value) — the pre-restore task root (isolation-aware initial derivation, computed in the
77
+ * driver). Dead after this call by design (R-5): later phases read the returned
78
+ * `taskRootFinal`, never this spelling. */
79
+ taskRootInitial: string;
80
+ /** borrowed-readonly — prepare's abort seat; only `.signal` is read (kept as the object so the
81
+ * moved body's `abortController.signal` spelling stays verbatim). */
82
+ abortController: {
83
+ readonly signal: AbortSignal;
84
+ };
85
+ /** borrowed-readonly — the acquired session's id, for the observation arms' error tags. */
86
+ sessionId: string;
87
+ }
88
+ /** The phase's outputs (相 API 规则件 four-class form). */
89
+ export interface PrepareWorkspaceRestoreResult {
90
+ /** owned — the SETTLED task root (T6 FINAL semantics): the initial derivation, else the park-only
91
+ * handle's mountPath, else resumeVM's restored mountPath — settled before the observation sink
92
+ * fired. Bound to a NEW name by design (R-5): `taskRootPath` must not be re-spelled by later
93
+ * phases, so a consumer moved ahead of this call is a lexical error. */
94
+ taskRootFinal: string;
95
+ /** owned (pure function; closes over the phase-armed rebase state) — migrates one checkpointed
96
+ * absolute path from the OLD workspace root onto the restored one (RB-439-c: any accepted
97
+ * spelling of the old root); the identity function when no divergence was armed. Consumers (T7):
98
+ * the readFileState checkpoint seed, the handsCwd seed, the activeWorktree restore. */
99
+ rebaseRestoredPath: (p: string) => string;
100
+ }
101
+ /** The B-4 phase body — the workspace-restore slice, verbatim (see the module header for the contract). */
102
+ export declare function prepareWorkspaceRestore(input: PrepareWorkspaceRestoreInput): Promise<PrepareWorkspaceRestoreResult>;
@@ -0,0 +1,144 @@
1
+ import { isRemoteExecutionEnv, isRetryableRemoteErrorCode, missingRestoreSurface, RETRYABLE_REMOTE_ERROR_CODES } from "../remote-env.js";
2
+ import { withRetry } from "../with-retry.js";
3
+ export function rebaseWorkspacePath(p, fromRaw, toRaw) {
4
+ if (p.includes("\\") || fromRaw.includes("\\") || toRaw.includes("\\"))
5
+ return p;
6
+ const stripTrail = (s) => (s.length > 1 && s.endsWith("/") ? stripTrail(s.slice(0, -1)) : s);
7
+ const from = stripTrail(fromRaw);
8
+ const to = stripTrail(toRaw);
9
+ if (p === from || stripTrail(p) === from)
10
+ return to;
11
+ const fromPrefix = from === "/" ? from : `${from}/`;
12
+ if (!p.startsWith(fromPrefix))
13
+ return p;
14
+ const suffix = p.slice(fromPrefix.length).replace(/^\/+/, "");
15
+ return to === "/" ? `${to}${suffix}` : `${to}/${suffix}`;
16
+ }
17
+ export function remoteEnvFailureNote(op, error, attempts) {
18
+ return { op, code: error.code, retryable: isRetryableRemoteErrorCode(error.code), attempts, message: error.message };
19
+ }
20
+ const REMOTE_RESTORE_MAX_ATTEMPTS = 2;
21
+ const REMOTE_RESTORE_BACKOFF_MS = 200;
22
+ export async function restoreWorkspaceWithRetry(env, snapshotId, options) {
23
+ let attempts = 0;
24
+ const outcome = await withRetry(async (attempt) => {
25
+ attempts = attempt;
26
+ return env.resumeVM(snapshotId, options);
27
+ }, { retryableCodes: RETRYABLE_REMOTE_ERROR_CODES, maxAttempts: REMOTE_RESTORE_MAX_ATTEMPTS, backoffMs: () => REMOTE_RESTORE_BACKOFF_MS }, { ...(options.abortSignal !== undefined ? { signal: options.abortSignal } : {}) });
28
+ return { outcome, attempts };
29
+ }
30
+ export function rebaseWorkspacePathAcross(p, froms, to) {
31
+ for (const from of froms) {
32
+ const out = rebaseWorkspacePath(p, from, to);
33
+ if (out !== p)
34
+ return out;
35
+ }
36
+ return p;
37
+ }
38
+ export async function prepareWorkspaceRestore(input) {
39
+ const { ownedEnv, deps, spec, internals, resume, abortController, sessionId } = input;
40
+ let taskRootPath = input.taskRootInitial;
41
+ let restoredRootRebase;
42
+ const rebaseRestoredPath = (p) => restoredRootRebase === undefined ? p : rebaseWorkspacePathAcross(p, restoredRootRebase.from, restoredRootRebase.to);
43
+ if (resume?.workspaceHandle !== undefined) {
44
+ const failResume = (message, cause, note) => {
45
+ const e = new Error(message, cause ? { cause } : undefined);
46
+ e.code = "resume.env_failed";
47
+ if (note !== undefined)
48
+ e.remoteEnvFailure = note;
49
+ throw e;
50
+ };
51
+ const handle = resume.workspaceHandle;
52
+ if (ownedEnv === undefined || !isRemoteExecutionEnv(ownedEnv)) {
53
+ failResume("resume needs a RemoteExecutionEnv from executionEnvFactory to restore the workspace snapshot");
54
+ }
55
+ else if (handle.snapshotId === undefined) {
56
+ if (handle.restoreMode !== "park_only" && ownedEnv.capabilities.suspendable) {
57
+ failResume("checkpoint workspaceHandle has no snapshotId and is not a park_only handle, but the resumed env is suspendable — refusing to resume on a possibly-unrestored workspace (corrupt checkpoint?)");
58
+ }
59
+ if (handle.mountPath && handle.mountPath !== taskRootPath) {
60
+ taskRootPath = handle.mountPath;
61
+ }
62
+ }
63
+ else {
64
+ const restoreSignal = spec.signal
65
+ ? AbortSignal.any([abortController.signal, spec.signal])
66
+ : abortController.signal;
67
+ const missingHere = missingRestoreSurface(ownedEnv);
68
+ if (missingHere.length > 0) {
69
+ failResume(`the resumed execution env cannot restore a workspace snapshot: its adapter does not implement ${missingHere.join(" or ")}. The checkpoint holds snapshot "${handle.snapshotId}" — wire an adapter that implements the full RemoteExecutionEnv restore surface and re-resume.`);
70
+ }
71
+ const { outcome: restored, attempts: restoreAttempts } = await restoreWorkspaceWithRetry(ownedEnv, handle.snapshotId, {
72
+ abortSignal: restoreSignal,
73
+ priorHandle: handle,
74
+ });
75
+ if (!restored.ok) {
76
+ failResume(`resumeVM failed after ${restoreAttempts} attempt(s) (${restored.error.code}): ${restored.error.message}`, restored.error, remoteEnvFailureNote("resumeVM", restored.error, restoreAttempts));
77
+ }
78
+ else {
79
+ const restoredEnv = ownedEnv;
80
+ const canonicalInEnv = async (p) => {
81
+ try {
82
+ const r = await restoredEnv.canonicalPath(p, restoreSignal);
83
+ return r.ok ? r.value : undefined;
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ };
89
+ let checkpointedCanonical;
90
+ let sameRootUnderAlias = false;
91
+ if (restored.value.mountPath !== handle.mountPath) {
92
+ checkpointedCanonical = await canonicalInEnv(handle.mountPath);
93
+ const restoredCanonical = await canonicalInEnv(restored.value.mountPath);
94
+ sameRootUnderAlias =
95
+ checkpointedCanonical !== undefined && restoredCanonical !== undefined && checkpointedCanonical === restoredCanonical;
96
+ }
97
+ if (restored.value.mountPath !== handle.mountPath && !sameRootUnderAlias) {
98
+ if (resume.executesApprovedAction === true) {
99
+ failResume(`resumeVM workspace-root divergence with a pending approved action: checkpointed mountPath "${handle.mountPath}" but the restored handle reports "${restored.value.mountPath}" — the approved args are bound to the checkpointed root; refusing to execute them against a moved workspace (adapter should honor priorHandle)`);
100
+ }
101
+ try {
102
+ deps.onError?.(new Error(`resumeVM workspace-root divergence: checkpointed mountPath "${handle.mountPath}" but the restored handle reports "${restored.value.mountPath}" — the resumed task follows the restored root`), { phase: "config", sessionId });
103
+ }
104
+ catch {
105
+ }
106
+ }
107
+ if (restored.value.mountPath && restored.value.mountPath !== taskRootPath) {
108
+ taskRootPath = restored.value.mountPath;
109
+ }
110
+ if (restored.value.mountPath && restored.value.mountPath !== handle.mountPath && !sameRootUnderAlias) {
111
+ const from = checkpointedCanonical !== undefined && checkpointedCanonical !== handle.mountPath
112
+ ? [handle.mountPath, checkpointedCanonical]
113
+ : [handle.mountPath];
114
+ restoredRootRebase = { from, to: restored.value.mountPath };
115
+ try {
116
+ deps.onError?.(new Error(from.length > 1
117
+ ? `resumeVM workspace-root rebase accepts both spellings of the checkpointed root (${from.map((f) => `"${f}"`).join(" and ")}) when migrating persisted paths to "${restored.value.mountPath}"`
118
+ : `resumeVM workspace-root rebase is spelling-exact: it matches the checkpointed root "${handle.mountPath}" only, so persisted paths recorded under an equivalent alias of it are NOT migrated to "${restored.value.mountPath}" and stay as written`), { phase: "config", sessionId });
119
+ }
120
+ catch {
121
+ }
122
+ }
123
+ }
124
+ const init = await ownedEnv.postResumeInit();
125
+ if (!init.ok) {
126
+ failResume(`postResumeInit failed (${init.error.code}): ${init.error.message}`, init.error, remoteEnvFailureNote("postResumeInit", init.error, 1));
127
+ }
128
+ }
129
+ }
130
+ if (internals?.onWorkspaceResolved !== undefined) {
131
+ try {
132
+ const workspaceEnv = ownedEnv ?? deps.executionEnv;
133
+ internals.onWorkspaceResolved({
134
+ cwd: taskRootPath,
135
+ isolated: internals.isolation === "worktree",
136
+ remote: workspaceEnv !== undefined &&
137
+ (isRemoteExecutionEnv(workspaceEnv) || workspaceEnv.hostLocalPaths === false),
138
+ });
139
+ }
140
+ catch {
141
+ }
142
+ }
143
+ return { taskRootFinal: taskRootPath, rebaseRestoredPath };
144
+ }
@@ -1,4 +1,5 @@
1
1
  import { persistedReadDenyEntryProblem } from "../../tools/fs/read-deny.js";
2
+ import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
2
3
  import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
3
4
  import { snapshotActorAssertion } from "../../internal/llm.js";
4
5
  import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
@@ -989,6 +990,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
989
990
  }
990
991
  function makeHarnessHandlers(prepared, stats, rs, deps) {
991
992
  const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
993
+ const internalsNotifier = createSafeNotifier({
994
+ onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals activity sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
995
+ });
992
996
  let lastWorkspaceCwd = prepared.cwdRef?.current;
993
997
  const announceWorkspaceMove = () => {
994
998
  const cwdNow = prepared.cwdRef?.current;
@@ -1150,7 +1154,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1150
1154
  rs.counters.groundingSignalPostR9 = true;
1151
1155
  }
1152
1156
  const activityArg = primaryActivityArg(event.args);
1153
- internals?.onActivity?.({ phase: "start", toolCallId: event.toolCallId, toolName: event.toolName, at: startAt, ...(activityArg !== undefined ? { arg: activityArg } : {}) });
1157
+ internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "start", toolCallId: event.toolCallId, toolName: event.toolName, at: startAt, ...(activityArg !== undefined ? { arg: activityArg } : {}) }), internalsNotifier, "runtask.onActivity.start"), "runtask.onActivity.start");
1154
1158
  pushContent({
1155
1159
  type: "tool_start",
1156
1160
  toolCallId: event.toolCallId,
@@ -1199,7 +1203,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1199
1203
  ok: !event.isError,
1200
1204
  ts: toolNow,
1201
1205
  }));
1202
- internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow });
1206
+ internalsNotifier.notify(() => observeThenableRejection(internals?.onActivity?.({ phase: "end", toolCallId: event.toolCallId, toolName: event.toolName, isError: event.isError, at: toolNow }), internalsNotifier, "runtask.onActivity.end"), "runtask.onActivity.end");
1203
1207
  if (prepared.lspDiagnostics && !event.isError) {
1204
1208
  const name = event.toolName;
1205
1209
  if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
@@ -2325,8 +2329,11 @@ export class Runner {
2325
2329
  queue.push({ type: "message_committed", entryId, role, ...(toolCallId !== undefined ? { toolCallId } : {}), ...ident() });
2326
2330
  };
2327
2331
  const subagentName = parentToolCallId !== undefined && internals?.agentName !== undefined ? inlineUntrusted(internals.agentName.slice(0, 320), 80) : undefined;
2332
+ const statusSinkNotifier = createSafeNotifier({
2333
+ onError: (f) => console.warn(`[sema-core] ${f.site}: run-internals status sink threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
2334
+ });
2328
2335
  const statusEmit = (s) => {
2329
- queue.push({
2336
+ const frame = {
2330
2337
  type: "status",
2331
2338
  phase: s.phase,
2332
2339
  ...(s.detail !== undefined ? { detail: s.detail } : {}),
@@ -2336,7 +2343,12 @@ export class Runner {
2336
2343
  ...(s.maxRetries !== undefined ? { maxRetries: s.maxRetries } : {}),
2337
2344
  ...(s.errClass !== undefined ? { errClass: s.errClass } : {}),
2338
2345
  ...ident(),
2339
- });
2346
+ };
2347
+ Object.freeze(frame);
2348
+ const accepted = queue.push(frame);
2349
+ if (accepted && internals?.onStatusEvent !== undefined) {
2350
+ statusSinkNotifier.notify(() => observeThenableRejection(internals.onStatusEvent?.(frame), statusSinkNotifier, "runtask.onStatusEvent"), "runtask.onStatusEvent");
2351
+ }
2340
2352
  };
2341
2353
  const telemetryEmit = (t) => {
2342
2354
  emitTrace(rs.telemetry.tracer, () => t.kind === "failover"
@@ -3114,6 +3126,8 @@ export class Runner {
3114
3126
  rewindNotes: prepared.rewindNotes,
3115
3127
  strandedHumanAnswers,
3116
3128
  remoteEnvFailures: prepared.remoteEnvFailures,
3129
+ effectiveReadFace: prepared.effectiveReadFace,
3130
+ effectiveReadDenyPatterns: prepared.effectiveReadDenyPatterns,
3117
3131
  retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
3118
3132
  abortedForTimeout: timeout.fired,
3119
3133
  abortedForTurns: rs.limits.turnsExceeded,
@@ -79,3 +79,12 @@ export interface SafeNotifier {
79
79
  * notifier whose callbacks never throw costs one object and nothing per call beyond the `try`.
80
80
  */
81
81
  export declare function createSafeNotifier(opts?: SafeNotifierOptions): SafeNotifier;
82
+ /**
83
+ * Observe a `=> void` host callback's RETURN value for the async dialect: TypeScript accepts an
84
+ * `async` function at a void seat, and a rejected one would escape {@link SafeNotifier.notify}
85
+ * (which deliberately ignores return values — see the scope note above) as an unhandled rejection.
86
+ * Call this with the callback's return value INSIDE the notify thunk: a thenable's rejection is
87
+ * routed back through the same notifier/site, so async and sync failures share one count and one
88
+ * bounded disclosure, and neither can fault the engine's control flow.
89
+ */
90
+ export declare function observeThenableRejection(r: unknown, notifier: SafeNotifier, site: string): void;
@@ -57,3 +57,12 @@ export function createSafeNotifier(opts) {
57
57
  },
58
58
  };
59
59
  }
60
+ export function observeThenableRejection(r, notifier, site) {
61
+ if (typeof r?.then === "function") {
62
+ r.then(undefined, (err) => {
63
+ notifier.notify(() => {
64
+ throw err instanceof Error ? err : new Error(String(err));
65
+ }, site);
66
+ });
67
+ }
68
+ }
@@ -175,6 +175,17 @@ export type PermissionResult = {
175
175
  * consumer (approval card, wire frame) can tell the person their rule is alive, just outranked.
176
176
  * Absent ⇒ no rule matched, or the ask was cleared normally. */
177
177
  persistedRuleShadowed?: string;
178
+ /** backlog #239 disclosure (additive): WHY the reversibility probe did not clear this call, in the
179
+ * probe's own words. ENGINE-STAMPED at the irreversibility tighten (which constructs this decision
180
+ * wholesale), already neutralized + capped there; a `"maybe"` tier whose probe supplied no cause,
181
+ * and the `"always"` tier (which runs no probe), leave it absent. Same DISPLAY-ONLY posture as
182
+ * {@link persistedRuleShadowed}: a policy that self-declares it can only put its own prose on its
183
+ * own card — nothing reads this member to decide anything, so it is not an authority channel. */
184
+ probeReason?: string;
185
+ /** backlog #239 disclosure (additive): the STRUCTURED cause, for a probe whose verdict this engine
186
+ * understands (see {@link import("./checkpoint-store.js").ProbeCause}). Engine-stamped and
187
+ * validated at the irreversibility tighten; same display-only posture as the prose sibling. */
188
+ probeCause?: import("./checkpoint-store.js").ProbeCause;
178
189
  /** An EXPLICIT `ask` permission rule matched this call (design/127 DSL — the CC `alwaysAskRules`
179
190
  * shape); carries the matched rule's text. Present ⇔ a rule someone WROTE says "ask about this",
180
191
  * never for a `defaultAction:"ask"` fallback (an unmatched call is default-closed posture, not a
@@ -657,6 +668,20 @@ export interface AskRequest {
657
668
  * {@link PermissionResult}'s ask arm). The matched rule text, so the approval card renders "your
658
669
  * rule is alive, just outranked" instead of leaving the person to regex the message prose. */
659
670
  persistedRuleShadowed?: string;
671
+ /** backlog #239: WHY the reversibility probe did not clear this call — the account of the tighten the
672
+ * card was showing WITHOUT until now (the gate knew the cause and dropped it at this seam, leaving the
673
+ * approver the action but not the reason it is being asked about). Present only for a `"maybe"`-tier
674
+ * tighten whose probe supplied a cause; the durable park route carries the same string as
675
+ * `RiskDescriptor.probeReason`. Neutralized + length-capped by the gate. UNTRUSTED-for-display,
676
+ * never adjudication input. */
677
+ readonly probeReason?: string;
678
+ /** backlog #239: the STRUCTURED cause behind the tighten — a machine-readable code plus the operand
679
+ * families as arrays with honest totals, so this surface renders its OWN sentence rather than one the
680
+ * engine had to write before the check's edge cases were known. See
681
+ * {@link import("./checkpoint-store.js").ProbeCause}, whose contract is that the ARRAY is the
682
+ * structure: render the entries as data, never re-derive structure by splitting or joining them.
683
+ * The durable park route carries the same value as `RiskDescriptor.probeCause`. */
684
+ readonly probeCause?: import("./checkpoint-store.js").ProbeCause;
660
685
  toolCallId: string;
661
686
  /** The (post-rewrite) args the tool would run with. */
662
687
  args: unknown;
@@ -87,6 +87,25 @@ export type ToolEffect = "read" | "write" | "idempotent";
87
87
  /** design/178 §3 — the content-origin classes (see {@link ToolSpec.contentOrigin}). */
88
88
  export type ToolContentOrigin = "external" | "execution" | "local";
89
89
  /** A native tool the model can call. `parameters` is a typebox schema; `execute` does the work. */
90
+ /**
91
+ * design/77 §4 — what a {@link ToolSpec.reversibilityProbe} returns for ONE call.
92
+ *
93
+ * `reversible` alone decides: the gate tightens a surviving `allow` to `ask` unless it is exactly `true`,
94
+ * and neither optional member can widen that. They exist so the resulting approval card can say WHY, a
95
+ * thing the probe knows at the moment it decides and nothing downstream can re-derive.
96
+ *
97
+ * Supply `cause` when the reason has parts a consumer should render itself; `reason` when it is prose
98
+ * this engine cannot interpret. Both are optional and independent, so a probe written against the
99
+ * original bare `{ reversible }` shape remains valid.
100
+ */
101
+ export interface ReversibilityVerdict {
102
+ reversible: boolean;
103
+ /** Free-text account, neutralized and length-capped by the gate before it reaches any card. */
104
+ reason?: string;
105
+ /** Structured account — see {@link import("./checkpoint-store.js").ProbeCause}. Validated by the gate;
106
+ * a malformed value costs the DISCLOSURE only, never the ask. */
107
+ cause?: import("./checkpoint-store.js").ProbeCause;
108
+ }
90
109
  export interface ToolSpec<TParams extends TSchema = TSchema> {
91
110
  /**
92
111
  * Model-facing tool name. MUST NOT contain `__` — that separator is reserved by the PROTOCOL TABLE
@@ -254,12 +273,37 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
254
273
  * approval timeout) and **fail-closed**: a non-`reversible` verdict, a timeout, or a throw all tighten to
255
274
  * `ask`. Declaring this probe defaults `irreversibility` to `"maybe"`. It is read from the spec at
256
275
  * prepare-time and captured in a closure — NOT a tool argument — so the model cannot monkey-patch it.
276
+ *
277
+ * **`reason` (optional, additive both ways)** — WHY this call was not proven reversible, in the probe's
278
+ * own words, minted in the same pass that reached the verdict (so nothing downstream re-derives it).
279
+ * Read ONLY on a tightening verdict: a `reversible: true` return ends the gate, and a timeout/throw
280
+ * produces no verdict at all, so the cause travels exactly when there is an approval card to put it on.
281
+ * The gate neutralizes and length-caps it, then carries it to both approval routes — the synchronous
282
+ * `AskRequest.probeReason` and the durable park's `RiskDescriptor.probeReason`. Compatible in BOTH
283
+ * directions by construction: a probe written against the older bare `{ reversible }` shape stays
284
+ * type-correct and simply supplies no cause, and an engine that does not know the member ignores it.
285
+ *
286
+ * **`cause` (optional)** — the STRUCTURED alternative, for a probe whose verdict is machine-describable:
287
+ * a `code` plus operand families as arrays with honest totals ({@link ProbeCause}), carried to
288
+ * `AskRequest.probeCause` / `RiskDescriptor.probeCause`. Prefer it wherever the cause has parts. Prose
289
+ * has to be written before the edge cases are known — a sentence about what a command DOES is false
290
+ * wherever the check over-fires, and a multi-part disclosure flattened into one capped string loses
291
+ * whichever part sorts last — whereas a code cannot be false and an array cannot be truncated into a
292
+ * lie. `reason` remains for deployments whose cause is genuinely just prose this engine cannot read.
293
+ * Both may be supplied; each is validated and carried independently.
294
+ *
295
+ * TRUST: the text is DEPLOYMENT-authored (and, for the built-in shell probe, derived from the model's
296
+ * own command), so it is DISPLAY/TRIAGE metadata only — never adjudication input, and never a channel
297
+ * that can widen a verdict. A probe cannot use it to auto-allow: `reversible` alone decides.
298
+ *
299
+ * That property is STRUCTURAL, and keeping it so constrains where the cause may be written. It is
300
+ * carried as a member and deliberately NOT folded into the ask's `message`, because the message is
301
+ * what an auto-mode classifier is handed as its `askMessage` — and that classifier may answer an ask
302
+ * with `allow`. Text on the prose channel is therefore steering input to a decider that can clear the
303
+ * very ask the probe raised, which is exactly the authority a probe must not have. Anything added
304
+ * later that renders this cause must keep it on display surfaces only.
257
305
  */
258
- reversibilityProbe?: (args: unknown) => {
259
- reversible: boolean;
260
- } | Promise<{
261
- reversible: boolean;
262
- }>;
306
+ reversibilityProbe?: (args: unknown) => ReversibilityVerdict | Promise<ReversibilityVerdict>;
263
307
  /**
264
308
  * design/178 §3 — CONTENT-ORIGIN class of what this tool can bring into the session, a fourth axis
265
309
  * orthogonal to {@link effect} (repeat-safety), {@link egress} (blast-radius) and
@@ -2128,10 +2172,13 @@ export interface TaskSpec {
2128
2172
  * design/199 件B — TASK-layer ADDITIONS to the built-in sensitive-path READ deny set
2129
2173
  * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}). Judged by the
2130
2174
  * structured read faces (Read/Grep/Glob/RepoMap, their traversals, the classify shell gate's
2131
- * auto-allow probe and the compaction attachment reader) in BOTH containment modes. Add-only
2132
- * everywhere (D-4 zero-shrink ruling): entries here UNION with the built-ins and the deployment's
2133
- * {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); nothing can remove a
2134
- * built-in. Bad entry shapes refuse loudly at prepare (#123). The write faces are untouched.
2175
+ * auto-allow probe and the compaction attachment reader) in BOTH containment modes. Add-only at
2176
+ * the task layer: entries here UNION with the active built-ins and the deployment's
2177
+ * {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); NOTHING at the task layer
2178
+ * can remove a built-in the one removal channel is the DEPLOYMENT's built-in configuration
2179
+ * ({@link RunnerDeps.readDenyBuiltinTiers} / {@link RunnerDeps.readDenyBuiltinExclude}, #245
2180
+ * revision of D-4), which no TaskSpec key reaches. Bad entry shapes refuse loudly at prepare
2181
+ * (#123). The write faces are untouched.
2135
2182
  */
2136
2183
  readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
2137
2184
  /**
@@ -2877,6 +2924,54 @@ export interface TaskResult {
2877
2924
  deliveryId: string;
2878
2925
  toolCallId: string;
2879
2926
  }>;
2927
+ /**
2928
+ * #240 (design/199 v1.1) — the READ-face this leg's read surfaces actually judged under, as an
2929
+ * engine-filled OBSERVATION (never a knob: writing it on a spec does nothing). It is the run's ONE
2930
+ * resolved face — the same value the hands toolkit enforced and the delegation carriers rode — with
2931
+ * the hands-less resume pin folded in (a row resumed on a hands-less worker still reports the
2932
+ * checkpoint-frozen `"roots"`).
2933
+ *
2934
+ * **In-presence condition (#242 widened):** present on every leg that completes prepare — hands
2935
+ * mounted (any run with the built-in fs band) AND every hands-less leg (resume legs fold the
2936
+ * checkpoint seed stricter-wins; non-resume legs report the live resolution). Absent only on
2937
+ * prepare-failure terminals (nothing ran). Consumers that keyed on absence = "no posture" must
2938
+ * re-key: absence now means only "prepare never completed". Rides every OTHER terminal, not just
2939
+ * `completed` — the posture is a fact about the leg that ran, whatever ended it.
2940
+ *
2941
+ * Purpose: a POST-COMPLETION follow-on leg spawned OUTSIDE the delegation tree (the verify gate's
2942
+ * verifier and fix legs are the canonical case) cannot see the checkpoint fold the completed leg ran
2943
+ * under — its spawner holds only the resume-side config, which never contained the frozen posture.
2944
+ * Folding this seat into the follow-on spec (stricter-wins: only `"roots"` ever tightens; an
2945
+ * `"open"` observation is never forwarded) closes that escape for ANY follow-on, not just the
2946
+ * verifier. See {@link effectiveReadDenyPatterns} for the deny-set half.
2947
+ */
2948
+ effectiveReadFace?: import("../tools/fs/read-face.js").ReadFace;
2949
+ /**
2950
+ * #240 (design/199 v1.1) — the sensitive-path deny ADDITIONS in force on this leg beyond the
2951
+ * built-in table, normalized (deployment ∪ task ∪ checkpoint-frozen seed), as an engine-filled
2952
+ * OBSERVATION. "In force" = judged by this leg's own read surfaces where they mounted, and carried
2953
+ * to its delegation subtree either way (a hands-less resume leg reports the entries its children
2954
+ * judge under).
2955
+ * This seat is the only place a checkpoint-FROZEN deny entry becomes visible after the resumed leg
2956
+ * completes: the resume-side caller's own config never contained it. A follow-on leg's spawner
2957
+ * unions these into the child's `readDenyPatterns` (add-only; the child's compile folds exact
2958
+ * duplicates, so re-supplying deployment-shared entries is idempotent) — that is how the frozen
2959
+ * posture rides every leg that continues this work, not just the delegation subtree.
2960
+ *
2961
+ * **In-presence condition:** present iff non-empty. Built-in entries are never listed (they are in
2962
+ * force on every run and re-compile locally). Same terminal coverage as
2963
+ * {@link effectiveReadFace}: rides any terminal of a leg that ran; absent on prepare failures.
2964
+ *
2965
+ * On a DURABLE-PAUSE hand-back these seats are additionally the RE-SUPPLY hint: a checkpoint
2966
+ * minted by a leg with no face resolution (a hands-less non-resume continuation) carries no
2967
+ * frozen face section of its own, so a caller resuming that token must fold these entries back
2968
+ * into the resume-side config (`readDenyPatterns` / `readFace: "roots"`). The resumed leg unions
2969
+ * them into its own judgment and delegation carriers; the union is NOT serialized into any
2970
+ * further checkpoint that leg may mint (a hands-less RESUME mint re-freezes only its original seed; a non-resume hands-less pause mints its live-resolved posture, #242), so
2971
+ * fold them on EVERY resume — the standing resume contract. The seats describe the posture
2972
+ * governing the WORK, never the contents of any checkpoint row.
2973
+ */
2974
+ effectiveReadDenyPatterns?: readonly import("../tools/fs/read-deny.js").NormalizedReadDenyEntry[];
2880
2975
  /**
2881
2976
  * `turns`/`tokens`/`costMicroUsd` are this task's OWN model usage. `nested` is the summed usage of any
2882
2977
  * delegated sub-runs (sub-agents) it spawned — present only when it delegated. The true total
@@ -4239,7 +4334,13 @@ export interface EngineNotice {
4239
4334
  * deps and `runTask` builds a Runner per call), unwired console arm once per process;
4240
4335
  * a library-direct `createHandsToolkit` mount announces at toolkit creation (one per mount,
4241
4336
  * through the band-local `HandsToolkitOptions.onNotice` seat, absent ⇒ `console.warn`);
4242
- * `detail: { seat, declared, inForce, cause }`. */
4337
+ * `detail: { seat, declared, inForce, cause }`.
4338
+ *
4339
+ * Deliberately NOT a notice family: brain retry/reconnect liveness (a rate limit, a 5xx, a
4340
+ * transient network failure being retried). Those are per-attempt liveness frames with their own
4341
+ * frequency semantics and ride the wire `status` channel ({@link BrainStatus}), whose sink the
4342
+ * Runner establishes unconditionally around every brain-driving call — no host seat to wire, no
4343
+ * console fallback to flood. A reader looking for retry disclosure should look there, not here. */
4243
4344
  code: string;
4244
4345
  /** The exact human-readable line the unwired build prints via `console.warn` — same words, one text. */
4245
4346
  message: string;
@@ -4320,11 +4421,37 @@ export interface RunnerDeps {
4320
4421
  /**
4321
4422
  * design/199 件B — DEPLOYMENT-layer ADDITIONS to the built-in sensitive-path READ deny set
4322
4423
  * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}); see
4323
- * {@link TaskSpec.readDenyPatterns} for the judged surfaces. Add-only (D-4 zero-shrink ruling):
4324
- * unions with the built-ins and any task-layer additions; there is no whole-table replacement
4325
- * escape hatch, and `[]` absent. Bad entry shapes refuse loudly at prepare (#123).
4424
+ * {@link TaskSpec.readDenyPatterns} for the judged surfaces. Add-only on THIS seat: unions with
4425
+ * the active built-ins and any task-layer additions, and `[]` absent — narrowing the built-in
4426
+ * set is the sibling knobs' job ({@link readDenyBuiltinTiers} / {@link readDenyBuiltinExclude},
4427
+ * #245 revision of D-4), not a replacement escape hatch here. Bad entry shapes refuse loudly at
4428
+ * prepare (#123).
4326
4429
  */
4327
4430
  readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
4431
+ /**
4432
+ * #245 (revises the D-4 zero-shrink ruling) — the deployment's built-in deny-table TIER selection:
4433
+ * EXACTLY the listed tiers of
4434
+ * {@link import("../tools/fs/read-deny.js").READ_FACE_BUILTIN_DENY_TABLE} are active (`[]` = none —
4435
+ * explicit and legal); absent = the default selection
4436
+ * ({@link import("../tools/fs/read-deny.js").READ_DENY_DEFAULT_TIERS}: every tier except
4437
+ * `shell-history`, which is unrestricted by default). Composes with
4438
+ * {@link readDenyBuiltinExclude} (tiers first, then row removal); never touches the additions
4439
+ * seats or the write faces. Unknown tier names refuse loudly at prepare (#123). DEPLOYMENT seat
4440
+ * ONLY: deliberately not a TaskSpec key and not in the governed workflow whitelist — a task or
4441
+ * governed script can only ADD deny entries, never widen the built-in face below its deployment.
4442
+ * Not frozen into checkpoints: the checkpoint face section persists ADDITIONS only, so a resumed
4443
+ * row's built-in face follows the CURRENT deployment configuration (the operator's live
4444
+ * authority), while frozen additions stay stricter-wins.
4445
+ */
4446
+ readDenyBuiltinTiers?: readonly string[];
4447
+ /**
4448
+ * #245 — per-row removal from the built-in deny table by STABLE NAME (= the row's canonical
4449
+ * pattern text, e.g. `".ssh"`, `".config/gcloud"`; the admin delete channel), applied after
4450
+ * {@link readDenyBuiltinTiers} selection. Naming a row of an inactive tier is a satisfied intent
4451
+ * (no-op); a name matching NO table row refuses loudly at prepare (#123 — never silently
4452
+ * ignored). Same deployment-only seat, clamp argument and checkpoint posture as the tiers key.
4453
+ */
4454
+ readDenyBuiltinExclude?: readonly string[];
4328
4455
  /**
4329
4456
  * design/199 件A — the DEPLOYMENT's read-face declaration
4330
4457
  * ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export { Runner, runTask } from "./core/runner/runtask.js";
9
9
  export { resolveTaskLimits } from "./core/runner/prepare-task.js";
10
+ export type { RunInternals, ResolvedWorkspace } from "./core/runner/prepare-task.js";
10
11
  export type { BudgetAxis } from "./core/runner/assemble-result.js";
11
12
  export { SESSION_LOG_DIGEST_SCHEME, sessionEntryDigest, sessionLogDigest, sessionLogDigestsComparable, } from "./engine/session/log-digest.js";
12
13
  export type { ResumeTaskConfig } from "./core/runner/runtask.js";
@@ -87,10 +88,10 @@ export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, Ch
87
88
  export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
88
89
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
89
90
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
90
- export { READ_FACE_DEFAULT_DENY_ENTRIES, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, } from "./tools/fs/index.js";
91
+ export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
91
92
  export { deploymentReadFaceClampNotice, resolveReadFace, type ReadFace, type ReadFaceInputs } from "./tools/fs/index.js";
92
93
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
93
- export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
94
+ export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
94
95
  export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
95
96
  export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
96
97
  export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./core/runner/prepare-task.js";
@@ -252,7 +253,7 @@ export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
252
253
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
253
254
  export { createAssistantMessageEventStream } from "./internal/llm.js";
254
255
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
255
- export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, EngineNotice, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
256
+ export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, EngineNotice, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ReversibilityVerdict, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
256
257
  export { Type } from "typebox";
257
258
  export type { TSchema, Static } from "typebox";
258
259
  export { explainPromptAssembly, describeDefaultPack, type DefaultPackDescription, type ExplainInput } from "./prompt-assembly/explain.js";