@sema-agent/core 5.27.0 → 5.29.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 (99) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/observer.d.ts +3 -3
  4. package/dist/agents/peer-admission.d.ts +1 -1
  5. package/dist/agents/teacher.d.ts +1 -1
  6. package/dist/agents/verify.d.ts +6 -6
  7. package/dist/bench/metrics.d.ts +1 -1
  8. package/dist/brain/retry.d.ts +1 -1
  9. package/dist/core/arg-summary.d.ts +1 -1
  10. package/dist/core/ask-class.d.ts +2 -2
  11. package/dist/core/ask-question.d.ts +1 -1
  12. package/dist/core/ask-question.js +14 -1
  13. package/dist/core/background-shell.d.ts +5 -5
  14. package/dist/core/checkpoint-store.d.ts +14 -14
  15. package/dist/core/file-snapshot-store.d.ts +1 -1
  16. package/dist/core/hooks.d.ts +25 -3
  17. package/dist/core/hooks.js +22 -3
  18. package/dist/core/human-input-projection.d.ts +2 -2
  19. package/dist/core/memory-admission.d.ts +2 -2
  20. package/dist/core/memory-engine/engine.d.ts +1 -1
  21. package/dist/core/memory-engine/engine.js +2 -4
  22. package/dist/core/memory-engine/file-backend.d.ts +68 -9
  23. package/dist/core/memory-engine/file-backend.js +69 -27
  24. package/dist/core/memory-engine/header-hints.d.ts +1 -1
  25. package/dist/core/memory-engine/layout.d.ts +32 -3
  26. package/dist/core/memory-engine/layout.js +132 -8
  27. package/dist/core/memory-engine/types.d.ts +6 -5
  28. package/dist/core/memory-recall.d.ts +1 -1
  29. package/dist/core/memory.d.ts +2 -2
  30. package/dist/core/oracle-isolation.d.ts +2 -2
  31. package/dist/core/permission-rule-consent.d.ts +83 -9
  32. package/dist/core/permission-rule-consent.js +92 -1
  33. package/dist/core/permission-rule-model.d.ts +18 -2
  34. package/dist/core/permission-rule-model.js +21 -0
  35. package/dist/core/permission-rule-org.d.ts +23 -4
  36. package/dist/core/permission-rule-org.js +67 -20
  37. package/dist/core/permission-rule-store.d.ts +1 -1
  38. package/dist/core/permission-rule-store.js +2 -2
  39. package/dist/core/permission-rule-sync.d.ts +15 -1
  40. package/dist/core/permission-rule-sync.js +89 -47
  41. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  42. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  43. package/dist/core/runner/prepare-task.d.ts +8 -3
  44. package/dist/core/runner/prepare-task.js +22 -7
  45. package/dist/core/runner/runtask.d.ts +1 -1
  46. package/dist/core/runner/runtask.js +13 -6
  47. package/dist/core/runner/session-rule-policy.d.ts +2 -2
  48. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  49. package/dist/core/runner/tool-disclosure.d.ts +1 -1
  50. package/dist/core/scheduler.d.ts +4 -4
  51. package/dist/core/session-policy-store.d.ts +3 -3
  52. package/dist/core/shared-memory/normalize.d.ts +1 -1
  53. package/dist/core/skill-tool-specifier.d.ts +1 -1
  54. package/dist/core/task-notification.d.ts +2 -2
  55. package/dist/core/task-registry-agent.d.ts +18 -9
  56. package/dist/core/task-registry-agent.js +51 -21
  57. package/dist/core/task-registry-monitor.js +1 -1
  58. package/dist/core/task-registry-shared.d.ts +13 -4
  59. package/dist/core/tool-errors.d.ts +2 -2
  60. package/dist/core/tool-policy.d.ts +78 -12
  61. package/dist/core/tool-policy.js +74 -7
  62. package/dist/core/tool-result-store.d.ts +109 -8
  63. package/dist/core/tool-result-store.js +95 -15
  64. package/dist/core/trace.d.ts +1 -1
  65. package/dist/core/types.d.ts +92 -22
  66. package/dist/core/types.js +30 -1
  67. package/dist/core/untrusted-text.d.ts +6 -0
  68. package/dist/core/untrusted-text.js +1 -0
  69. package/dist/engine/compaction/compaction.d.ts +1 -1
  70. package/dist/engine/harness/messages.d.ts +1 -1
  71. package/dist/engine/harness/types.d.ts +2 -2
  72. package/dist/engine/llm/types.d.ts +1 -1
  73. package/dist/engine/loop/types.d.ts +2 -2
  74. package/dist/engine/session/import-validate.d.ts +1 -1
  75. package/dist/engine/session/log-digest.d.ts +1 -1
  76. package/dist/fixtures/index.d.ts +18 -2
  77. package/dist/fixtures/index.js +11 -0
  78. package/dist/index.d.ts +2 -2
  79. package/dist/orchestration/goal.d.ts +1 -1
  80. package/dist/orchestration/workflow-types.d.ts +5 -4
  81. package/dist/orchestration/workflow.js +11 -3
  82. package/dist/prompt-assembly/artifact.d.ts +1 -1
  83. package/dist/prompts/default.d.ts +1 -1
  84. package/dist/prompts/default.js +1 -1
  85. package/dist/scenarios/scenario-registry.d.ts +3 -3
  86. package/dist/stores/file/tool-result-store.d.ts +43 -3
  87. package/dist/stores/file/tool-result-store.js +107 -19
  88. package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -2
  89. package/dist/tools/fs/fs-bash.d.ts +8 -1
  90. package/dist/tools/fs/fs-search-tools.d.ts +1 -1
  91. package/dist/tools/fs/fs-shared.d.ts +6 -1
  92. package/dist/tools/fs/fs-shared.js +11 -7
  93. package/dist/tools/fs/index.d.ts +6 -0
  94. package/dist/tools/fs/index.js +2 -0
  95. package/dist/tools/fs/safety.d.ts +1 -1
  96. package/dist/tools/scheduler-tools.d.ts +1 -1
  97. package/dist/tools/task-list.d.ts +1 -1
  98. package/dist/tools/web.d.ts +3 -3
  99. package/package.json +1 -1
@@ -16,7 +16,7 @@
16
16
  * can't lose an update (mirrors the checkpoint store's `resolve` OCC).
17
17
  * - **structured fields only**: core interprets `toolAllow/Deny`, `allowDirs`, `commandAllow/Deny` (argv[0]
18
18
  * names). A richer rule DSL is the service's/profile's job — core grows no rule interpreter.
19
- * - **NOT A SANDBOX**: command rules inherit the coarse argv[0] matching (wrappers like `sh -c`/`sudo` bypass);
19
+ * - **NOT A SANDBOX**: command rules inherit the coarse argv[0] matching (wrappers like `sh -c`/`sudo` circumvention);
20
20
  * the real isolation boundary is the executionEnv, never these rules.
21
21
  */
22
22
  /** The structured per-session rules. All optional; an absent field imposes no constraint of that kind. */
@@ -86,11 +86,11 @@ export interface SessionPolicyStore {
86
86
  /**
87
87
  * LEXICAL path containment for the tighten gate. **Must agree with the runtime's view of what a dir IS** (the
88
88
  * runtime resolves `..`/`.`/`//` via `canonicalizeTarget`): both args are `posix.normalize`d FIRST so a
89
- * non-operator can't smuggle a write-confinement OUTSIDE the prior dir by writing `<prior>/../etc` — which is
89
+ * non-operator can't carry across a write-confinement OUTSIDE the prior dir by writing `<prior>/../etc` — which is
90
90
  * lexically "within `<prior>`" by a naive `startsWith` but canonicalizes to `/etc` at run time (the tighten
91
91
  * gate must never UNDER-report a loosening).
92
92
  *
93
- * The `..`-resolving `posix.normalize` pass is THIS function's own job (the tighten gate's `..`-smuggling
93
+ * The `..`-resolving `posix.normalize` pass is THIS function's own job (the tighten gate's `..`-carrying across
94
94
  * defense above) and is NOT shared with the runtime's `isWithin` (session-rule-policy.ts), which compares
95
95
  * already-canonicalized keys and never resolves `..` itself — the two functions agree on every OTHER input
96
96
  * (same win-family fold, same prefix semantics) but diverge on inputs that still carry an unresolved `..`,
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * WHY A GATE AT ALL. A TypeScript annotation is a compile-time claim, and a Provider is third-party
6
6
  * (BYOM) code that may be plain JavaScript. Every value crossing this seam is therefore treated as
7
- * foreign data: exotic shapes, throwing getters and hostile metadata all have a defined verdict here,
7
+ * foreign data: exotic shapes, throwing getters and untrusted metadata all have a defined verdict here,
8
8
  * so no "shape outside the rules" can leak an exception into a tool body or a crafted string onto a
9
9
  * prompt line.
10
10
  *
@@ -35,7 +35,7 @@
35
35
  * admitting a second program smuggled in behind the first.
36
36
  *
37
37
  * Reusing that parser rather than writing a second tokenizer is deliberate: two argv[0] parsers drift,
38
- * and the drift shows up as a bypass rather than as a test failure.
38
+ * and the drift shows up as a circumvention rather than as a test failure.
39
39
  *
40
40
  * ## Scope: the shell-command lane only
41
41
  *
@@ -6,7 +6,7 @@ export interface TaskNotificationPayload {
6
6
  task_id: string;
7
7
  /** design/144 §2: `"external"` is CORE-MINTED ONLY — `TaskStream.notify()` casts it onto every
8
8
  * externally injected event, and the input type ({@link ExternalNotificationInput}) deliberately has
9
- * no `task_type` field, so an outside producer can never wear an internal lane's type (a forged
9
+ * no `task_type` field, so an outside producer can never wear an internal lane's type (a caller-declared
10
10
  * "background_bash completed" impersonating a real child). */
11
11
  task_type: "background_bash" | "workflow" | "background_agent" | "monitor" | "external";
12
12
  toolUseId?: string;
@@ -27,7 +27,7 @@ export interface TaskNotificationPayload {
27
27
  * `cycleSeq`, or — on a tier-3 cross-process revival / a parked resume, which deliberately carry
28
28
  * no retain entry — the durable row's own `seq`. ABSENT only when neither exists (retain OFF on a
29
29
  * store-less spawn, an expired ledger with no durable row, the fork lane): the cycle count is not
30
- * knowable there, and an honest downgrade to the old single-cycle key beats a forged period
30
+ * knowable there, and an honest downgrade to the old single-cycle key beats a caller-declared period
31
31
  * number. So `seq` present does NOT imply a retain entry exists.
32
32
  * - `external`: caller-supplied verbatim ({@link ExternalNotificationInput.seq}) — the injector's
33
33
  * own dedup discriminator across same-task_id+status events.
@@ -14,14 +14,14 @@ export declare function ensureDurableHeartbeatLane(core: DurableAgentCore): void
14
14
  * re-apply (the real terminal truth must beat a reaper's guess); only a row carrying a
15
15
  * FOREIGN writerId poisons the lane (never clobber another writer). */
16
16
  export declare function durableAgentWriteLane(handle: BackgroundAgentTaskHandle, patch: Partial<BackgroundAgentRecord>, clear?: readonly (keyof BackgroundAgentRecord)[]): void;
17
- /** design/151 S1b — is this row's durable lane ARMED (store wired at registration, not poisoned)?
17
+ /** design/151 S1b — is this row's durable lane ARMED (store wired at registration, not contaminated)?
18
18
  * Observability/test face. ⚠️ NOT the lifecycle-flip key: arming says a row was ATTEMPTED, not
19
19
  * that it EXISTS — release anchors gate on {@link durableAgentRowProbe} (codex r2 F2). */
20
20
  export declare function durableAgentArmedLane(core: DurableAgentCore, id: string): boolean;
21
21
  /** design/151 S1b (codex r2 F2) — the AWAITABLE lifecycle-flip key. Returns undefined when no
22
22
  * lane was armed; otherwise a probe CLOSURE over the HANDLE (outlives its eviction from the map —
23
23
  * retain-ledger TTL arms fire hours after terminal GC) that flushes the queued writes and answers
24
- * whether a durable row actually EXISTS right now (initial put confirmed, not poisoned). A store
24
+ * whether a durable row actually EXISTS right now (initial put confirmed, not contaminated). A store
25
25
  * that never accepted the put answers false — the caller then RELEASES the session (pre-151 path)
26
26
  * instead of stranding an unreachable transcript.
27
27
  *
@@ -102,13 +102,13 @@ export declare function bindBackgroundAgentSessionLane(core: DurableAgentCore, i
102
102
  * per leg at the injector-ready barrier (prepare has succeeded by then, so the verdict exists); a
103
103
  * revival cycle overwrites the previous cycle's record with its own, which the admission door
104
104
  * guarantees is a subset. Same durable write lane as every other post-registration fact — a
105
- * store-less registration is a no-op, and a poisoned lane stays poisoned.
105
+ * store-less registration is a no-op, and a contaminated lane stays contaminated.
106
106
  *
107
107
  * A DROPPED write is disclosed. This record is the only carrier a narrowing has across a process
108
108
  * boundary, so what stands on the row when the write does not land is the PREVIOUS leg's wider
109
- * verdict — which the next revival then seeds from. Two drop shapes, one message each: a poisoned
109
+ * verdict — which the next revival then seeds from. Two drop shapes, one message each: a contaminated
110
110
  * lane refuses at its front door, and an exhausted flush leaves the value unwritten with the lane
111
- * still armed. The channel is the same durable-agents warning channel the lane uses for the poison
111
+ * still armed. The channel is the same durable-agents warning channel the lane uses for the contamination
112
112
  * itself (that one says writes stopped; this one says WHICH fact stopped with them). The write
113
113
  * remains best-effort: no leg faults because its bookkeeping failed.
114
114
  *
@@ -197,7 +197,7 @@ export declare function rollbackParkedClaimLane(core: DurableAgentCore, stores:
197
197
  * design/153 §7.2d (件3c, codex 3c H-1) — the CONSUME FLIP, SERIALIZED THROUGH THE DURABLE LANE:
198
198
  * the guarded parked→running store CAS runs INSIDE the lane chain (ordered after every seeded
199
199
  * write — the session-bind CAS included), so no in-flight lane write can race it, CAS-fail on the
200
- * flip's rev bump, read the new epoch as foreign, and poison the lane (which would permanently
200
+ * flip's rev bump, read the new epoch as foreign, and contaminate the lane (which would permanently
201
201
  * drop finalize/terminal/re-park writes). On a won CAS the lane record/rev swap happens in the
202
202
  * SAME chain step and the live handle adopts (running, arbitration dropped — the token is
203
203
  * consumed; `parkClaimId` stays visible until finalize). Returns false when the row moved
@@ -284,7 +284,7 @@ export declare function markRetainedContinuationLane(core: DurableAgentCore, id:
284
284
  * The DURABLE transition is a {@link claimTerminalRowForRevive} claim, awaited BEFORE the in-memory
285
285
  * flip — the two orders are not equivalent: flipping memory first (the pre-arbitration shape, a plain lane
286
286
  * write with no row guard) let a foreign claim win the row while this process kept serving
287
- * `running`, and every later write of this cycle then died in the poisoned lane without a trace.
287
+ * `running`, and every later write of this cycle then died in the contaminated lane without a trace.
288
288
  * Three lane shapes, three treatments:
289
289
  * - NO lane (store-less registration): the pre-arbitration in-memory-only transition, unchanged — there is
290
290
  * no row, hence no arbitration domain and no competitor;
@@ -293,11 +293,11 @@ export declare function markRetainedContinuationLane(core: DurableAgentCore, id:
293
293
  * the chain (row absent AND the put still unconfirmed after the chain drained), never by the
294
294
  * entry-time flag: a put merely in flight, or one that committed under a failed response, would
295
295
  * otherwise exempt a row that does exist — the split-brain window this claim exists to close;
296
- * - POISONED lane (stale-flip ownership transfer / row reaped mid-write / heartbeat re-drive
296
+ * - Contaminated lane (stale-flip ownership transfer / row reaped mid-write / heartbeat re-drive
297
297
  * ceiling): the claim runs DIRECTLY through the store — the dead lane's chain is no longer a
298
298
  * serialization point, the rev guard is. Re-arming that lane object in place is what is NOT
299
299
  * done: an in-flight write from it would then see its own writerId on the re-claimed row and
300
- * adopt it. It stays poisoned (its record keeps the pre-claim writerId/epoch, so its read-back
300
+ * adopt it. It stays contaminated (its record keeps the pre-claim writerId/epoch, so its read-back
301
301
  * answers "foreign" and it stops), and a row that cannot be claimed — gone, or live under
302
302
  * another writer — REFUSES the revive rather than resuming with a durably unreachable row.
303
303
  * A WON claim always hands the row to a FRESH lane for the claimed generation (both paths), for the
@@ -471,6 +471,15 @@ export declare function serveDurableAgentRowLane(row: BackgroundAgentRecord): Un
471
471
  *
472
472
  * No store configured (a deployment that never wired a `toolResultStore`) ⇒ returns `clipped` UNCHANGED
473
473
  * — the legal degrade design/158 §2.2 calls for, byte-identical to pre-S1 behavior.
474
+ *
475
+ * Backlog #169 — a FAILING `put` (throwing or rejecting; the store contract admits both dialects) is
476
+ * NOT fatal to the poll: the clipped text is still served, with {@link AGENT_SPILL_FAILED_NOTE} in
477
+ * place of the ref disclosure (advertising a ref nobody can read back would be a false promise), and
478
+ * `handle.spillFailed` latches so later polls of the same cycle repeat the honest note instead of
479
+ * retrying the write — see the catch below for why latch-not-retry. Same "store failure is never
480
+ * fatal" posture as the monitor/offload/budget/compaction write sites. A put that settles AFTER a
481
+ * revive (stale cycle) is generation-guarded: it answers its own caller honestly but never writes
482
+ * spill state onto the new cycle (see `mintCycle` below).
474
483
  */
475
484
  export declare function spillClippedAgentResult(handle: BackgroundAgentTaskHandle, full: string, clipped: string, store: ToolResultStore | undefined, sessionId: string | undefined): Promise<string>;
476
485
  export declare function pollBackgroundAgentLane(handle: BackgroundAgentTaskHandle, deadline?: number, signal?: AbortSignal,
@@ -981,6 +981,7 @@ export async function reviveBackgroundAgentLane(core, id, access, abort) {
981
981
  handle.result = undefined;
982
982
  handle.resultFull = undefined;
983
983
  handle.spillRef = undefined;
984
+ handle.spillFailed = undefined;
984
985
  handle.error = undefined;
985
986
  handle.errorCode = undefined;
986
987
  handle.errorRetryable = undefined;
@@ -1226,17 +1227,33 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
1226
1227
  ...(row.status === "failed" ? { isError: true } : {}),
1227
1228
  };
1228
1229
  }
1230
+ const AGENT_SPILL_FAILED_NOTE = "\n\n[full output could not be spilled to the offload store (the write failed or went unconfirmed) — no ref is available to read the dropped middle back.]";
1229
1231
  export async function spillClippedAgentResult(handle, full, clipped, store, sessionId) {
1230
1232
  if (clipped === full)
1231
1233
  return clipped;
1232
1234
  if (store === undefined)
1233
1235
  return clipped;
1234
- if (handle.spillRef === undefined) {
1235
- const ref = buildToolResultRef(sessionId ?? "no-session", handle.id, `c${handle.reviveCycle ?? 0}`);
1236
- await store.put(ref, full, sessionId === undefined ? undefined : toolResultProvenanceOf(sessionId, handle.id));
1237
- handle.spillRef = ref;
1236
+ const disclose = (ref) => `${clipped}\n\n[full output persisted — call ${OFFLOAD_TOOL_NAME} with ref "${ref}" to read it back.]`;
1237
+ if (handle.spillRef !== undefined)
1238
+ return disclose(handle.spillRef);
1239
+ if (handle.spillFailed === true)
1240
+ return `${clipped}${AGENT_SPILL_FAILED_NOTE}`;
1241
+ const mintCycle = handle.reviveCycle ?? 0;
1242
+ const ref = buildToolResultRef(sessionId ?? "no-session", handle.id, `c${mintCycle}`);
1243
+ try {
1244
+ await store.put(ref, full, sessionId === undefined ? undefined : toolResultProvenanceOf(sessionId));
1245
+ }
1246
+ catch {
1247
+ if ((handle.reviveCycle ?? 0) === mintCycle) {
1248
+ if (handle.spillRef !== undefined)
1249
+ return disclose(handle.spillRef);
1250
+ handle.spillFailed = true;
1251
+ }
1252
+ return `${clipped}${AGENT_SPILL_FAILED_NOTE}`;
1238
1253
  }
1239
- return `${clipped}\n\n[full output persisted — call ${OFFLOAD_TOOL_NAME} with ref "${handle.spillRef}" to read it back.]`;
1254
+ if ((handle.reviveCycle ?? 0) === mintCycle)
1255
+ handle.spillRef = ref;
1256
+ return disclose(ref);
1240
1257
  }
1241
1258
  export async function pollBackgroundAgentLane(handle, deadline, signal, oneShot, store, sessionId) {
1242
1259
  while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
@@ -1256,10 +1273,23 @@ The agent is durably suspended, waiting for an approval decision. It resumes whe
1256
1273
  }),
1257
1274
  };
1258
1275
  }
1259
- const fullResult = handle.result ? (handle.resultFull ?? handle.result) : undefined;
1276
+ const snap = {
1277
+ status: handle.status,
1278
+ result: handle.result,
1279
+ resultIsPartial: handle.resultIsPartial,
1280
+ error: handle.error,
1281
+ errorKind: handle.errorKind,
1282
+ errorCode: handle.errorCode,
1283
+ errorRetryable: handle.errorRetryable,
1284
+ errorRetryAfterMs: handle.errorRetryAfterMs,
1285
+ cycleSeq: handle.cycleSeq,
1286
+ stoppedBy: handle.stoppedBy,
1287
+ completionId: handle.completionId,
1288
+ };
1289
+ const fullResult = snap.result ? (handle.resultFull ?? snap.result) : undefined;
1260
1290
  const resultText = fullResult !== undefined ? await spillClippedAgentResult(handle, fullResult, clipTaskOutput(fullResult, handle.outputFile), store, sessionId) : undefined;
1261
- const kindClause = handle.status === "failed" && handle.errorKind !== undefined && handle.errorRetryable !== undefined
1262
- ? ` (error_kind: ${handle.errorKind}, retryable: ${handle.errorRetryable}${handle.errorRetryAfterMs !== undefined ? `, retry_after_ms: ${handle.errorRetryAfterMs}` : ""})`
1291
+ const kindClause = snap.status === "failed" && snap.errorKind !== undefined && snap.errorRetryable !== undefined
1292
+ ? ` (error_kind: ${snap.errorKind}, retryable: ${snap.errorRetryable}${snap.errorRetryAfterMs !== undefined ? `, retry_after_ms: ${snap.errorRetryAfterMs}` : ""})`
1263
1293
  : "";
1264
1294
  const body = running
1265
1295
  ? oneShot === true
@@ -1267,26 +1297,26 @@ The agent is durably suspended, waiting for an approval decision. It resumes whe
1267
1297
  This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput({ task_id: "${handle.id}", block: true }). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.`
1268
1298
  : `status: running
1269
1299
  The agent is still working — you will be notified when it completes.`
1270
- : `status: ${handle.status}
1271
- ${handle.error ? `error: ${handle.error}${kindClause}
1272
- ` : ""}${handle.result ? `--- result${handle.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
1300
+ : `status: ${snap.status}
1301
+ ${snap.error ? `error: ${snap.error}${kindClause}
1302
+ ` : ""}${snap.result ? `--- result${snap.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
1273
1303
  ${resultText}` : "(no result text)"}`;
1274
1304
  return {
1275
1305
  content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
1276
1306
  details: buildAgentPollDetails({
1277
1307
  taskId: handle.id,
1278
- status: handle.status,
1308
+ status: snap.status,
1279
1309
  retrievalStatus: retrieval,
1280
- ...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
1281
- ...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
1282
- ...(handle.error !== undefined ? { error: handle.error } : {}),
1283
- ...(handle.errorCode !== undefined ? { errorCode: handle.errorCode } : {}),
1284
- ...(handle.errorRetryable !== undefined ? { errorRetryable: handle.errorRetryable } : {}),
1285
- ...(handle.errorRetryAfterMs !== undefined ? { errorRetryAfterMs: handle.errorRetryAfterMs } : {}),
1286
- ...(handle.resultIsPartial === true ? { resultIsPartial: true } : {}),
1287
- ...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
1310
+ ...(snap.cycleSeq !== undefined ? { seq: snap.cycleSeq } : {}),
1311
+ ...(snap.stoppedBy !== undefined ? { stoppedBy: snap.stoppedBy } : {}),
1312
+ ...(snap.error !== undefined ? { error: snap.error } : {}),
1313
+ ...(snap.errorCode !== undefined ? { errorCode: snap.errorCode } : {}),
1314
+ ...(snap.errorRetryable !== undefined ? { errorRetryable: snap.errorRetryable } : {}),
1315
+ ...(snap.errorRetryAfterMs !== undefined ? { errorRetryAfterMs: snap.errorRetryAfterMs } : {}),
1316
+ ...(snap.resultIsPartial === true ? { resultIsPartial: true } : {}),
1317
+ ...(snap.completionId !== undefined ? { completionId: snap.completionId } : {}),
1288
1318
  }),
1289
- ...(handle.status === "failed" ? { isError: true } : {}),
1319
+ ...(snap.status === "failed" ? { isError: true } : {}),
1290
1320
  };
1291
1321
  }
1292
1322
  export async function stopBackgroundAgentLane(core, handle) {
@@ -68,7 +68,7 @@ function spillRolledMonitorChunk(handle, stream, dropped) {
68
68
  else
69
69
  handle.spillErrSegCount = n + 1;
70
70
  try {
71
- const provenance = handle.spillSessionId === undefined ? undefined : toolResultProvenanceOf(handle.spillSessionId, handle.id);
71
+ const provenance = handle.spillSessionId === undefined ? undefined : toolResultProvenanceOf(handle.spillSessionId);
72
72
  void Promise.resolve(store.put(ref, dropped, provenance)).catch(() => {
73
73
  handle.spillFailed = true;
74
74
  });
@@ -68,7 +68,7 @@ export interface UnifiedTaskOutput {
68
68
  * (`noTask`/`noTaskForStop`/`formatWorkflowRun`/`serveDurableAgentRow`/`pollTask`/`stopTask` and every
69
69
  * per-task-kind poll/stop arm). `UnifiedTaskOutput` (the `details` face) has no isError channel of its
70
70
  * own — it is a REQUIRED-field wire shape consumed by callers beyond these two tools too, so isError is
71
- * carried as a sibling on the WRAPPER instead (trap avoidance per the E1 sweep: never smuggle isError
71
+ * carried as a sibling on the WRAPPER instead (trap avoidance per the E1 sweep: never carry across isError
72
72
  * through a narrower helper contract). Optional, absent/false = success — `createTaskOutputTool`/
73
73
  * `createTaskStopTool` forward it onto the real `ToolReturn` at the tool boundary. Every internal helper
74
74
  * keeps constructing `content`/`details` exactly as before; this only adds a place to say "and this one
@@ -97,8 +97,8 @@ export interface SemaTaskHandle {
97
97
  /** P1-3(黑板 [1920]/[1921]/[1924]/[1925]) — mint a cross-channel completion correlation id ONCE per
98
98
  * handle/record, fill-once (first writer wins): every settle/direct-terminal-flip site calls this
99
99
  * instead of writing `completionId` directly, so no matter how many terminal-transition sites a lane
100
- * has (settle / TaskStop bypass / backfill), the value is minted exactly once and every later caller
101
- * (including an idempotent re-call from a bypass path that already ran) gets back the SAME id. `string`
100
+ * has (settle / TaskStop circumvention / backfill), the value is minted exactly once and every later caller
101
+ * (including an idempotent re-call from a circumvention path that already ran) gets back the SAME id. `string`
102
102
  * is intentionally loose (any object with an optional `completionId` field) so the same helper serves
103
103
  * both a `RegisteredTaskHandle` and a `WorkflowRun` (orchestration/workflow.ts — a different module,
104
104
  * no shared base type, same fill-once shape). */
@@ -324,6 +324,15 @@ export interface BackgroundAgentTaskHandle extends SemaTaskHandle {
324
324
  * eventual spill lands under a DIFFERENT ref rather than colliding with (and being silently refused
325
325
  * by the write-once store under) cycle 1's. */
326
326
  spillRef?: string;
327
+ /** Backlog #169 — the spill `put` for the CURRENT revive cycle threw or rejected. One-time latch,
328
+ * scoped to the cycle ({@link import("./task-registry-agent.js").reviveBackgroundAgentLane} clears
329
+ * it in lockstep with `spillRef`): later polls serve the clipped text with an honest ref-free loss
330
+ * note instead of retrying the write — the ref is minted deterministically, so the known permanent
331
+ * failure class (the store's write-once/provenance conflict) would fail identically on every poll,
332
+ * each retry a guaranteed-failure round trip to a durable backend. The monitor lane's
333
+ * `spillFailed` twin (same degrade discipline, different write shape — that one is fire-and-forget,
334
+ * this one is awaited). */
335
+ spillFailed?: true;
327
336
  error?: string;
328
337
  /** RB-386② ([2090]) — the machine-readable failure code beside `error`, threaded from the settle
329
338
  * mint point (subagent.ts computes it ONCE from the child's TaskResult.errorCode taxonomy /
@@ -825,7 +834,7 @@ export interface RegisterBackgroundAgentInput extends TaskAccess {
825
834
  * pre-consume window keeps the live face's checkpoint arbitration; the consume flip
826
835
  * {@link TaskRegistry.adoptParkedResume} is the only path to running). Pass the ticket's token
827
836
  * and a stop-arbitration closure alongside. Also licenses replacing an in-process PARKED prior
828
- * handle of the same id (the planned park→resume handoff; lane poisoned like the terminal replace). */
837
+ * handle of the same id (the planned park→resume handoff; lane contaminated like the terminal replace). */
829
838
  initialStatus?: "parked";
830
839
  /** design/153 §7.2d — the parked checkpoint token this parked-born handle waits on. */
831
840
  parkedCheckpointToken?: string;
@@ -77,7 +77,7 @@ export declare function formatToolError(error: unknown): string;
77
77
  * in-presence condition) for that one code, or the errorCode's own doc for every other
78
78
  * member (`env.lifetime_expired`'s doc says "retrying needs a NEW environment, not a
79
79
  * smaller budget" — there is no `retryAfterMs` for it, by design: an expiring
80
- * environment has no return time to give). Codex adversarial review (rounds 2 and 4)
80
+ * environment has no return time to give). Codex falsification review (rounds 2 and 4)
81
81
  * twice suggested a distinct governance/platform class or leaving these `unknown`
82
82
  * instead — rejected both times: (a) explicit calling-convention direction; (b)
83
83
  * `WorkerErrorClass` is a small CLOSED public exported union — adding a member is an
@@ -86,7 +86,7 @@ export declare function formatToolError(error: unknown): string;
86
86
  * terminal codes, not foreign ones); (c) pre-existing precedent (`config.compliance_required`,
87
87
  * NOT part of this diff) already folds a transient/retryable governance code into `config`.
88
88
  * `memory.admission_required`/`_denied`/`env.lifetime_expired` are EXACT codes, not broad
89
- * `memory.`/`env.` prefixes — codex adversarial review (A-005.3 follow-up) caught an
89
+ * `memory.`/`env.` prefixes — codex falsification review (A-005.3 follow-up) caught an
90
90
  * earlier draft folding EVERY `memory.*`/`env.*` code here, which misclassified the
91
91
  * memory-engine's non-governance incident codes (`memory.secret_detected` et al.,
92
92
  * explicitly listed as NON-governance in `NON_GOVERNANCE_MEMORY_CODES` — a content-safety
@@ -78,7 +78,8 @@ export interface ToolCallRequest {
78
78
  * - `"org_unavailable"` — an org-governed deployment could not adjudicate against a snapshot, so the
79
79
  * whole decision boundary failed closed (see `ORG_UNAVAILABLE_DECISION_REASON`, the single
80
80
  * spelling this word is minted from). */
81
- export type DecisionReason = "rule" | "mode" | "hook" | "safety" | "classifier" | "persisted_rule" | "sandbox" | "org_rule" | "org_unavailable";
81
+ declare const DECISION_REASONS: readonly ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable"];
82
+ export type DecisionReason = (typeof DECISION_REASONS)[number];
82
83
  /**
83
84
  * WHO (or what) ENDED an approval — the machine-readable twin of a settlement's human-readable text,
84
85
  * so a consumer tells "a person decided this" from "nobody answered" without prose-matching a sentence.
@@ -109,7 +110,7 @@ export type DecisionReason = "rule" | "mode" | "hook" | "safety" | "classifier"
109
110
  * be reported as a person's refusal.
110
111
  *
111
112
  * ABSENT means this settlement named no source: an older caller that predates the field, or a verdict
112
- * that settled no wait at all (a policy POSTURE — headless auto-deny, a blanket bypass — where nobody
113
+ * that settled no wait at all (a policy POSTURE — headless auto-deny, a blanket circumvention — where nobody
113
114
  * was ever asked). A consumer MUST NOT read a semantic out of the absence; render the text instead.
114
115
  */
115
116
  export type ApprovalSettledBy = "human" | "timeout" | "aborted";
@@ -128,7 +129,7 @@ export declare function isApprovalSettledBy(v: unknown): v is ApprovalSettledBy;
128
129
  * stale un-redacted ones.
129
130
  * - `ask` requests human confirmation; with no approver wired it resolves deterministically to deny
130
131
  * (headless auto-deny, see `RunnerDeps.onAsk`). `requiresRealApproval: true` (RB-203 codex review,
131
- * confirmed P1) additionally refuses to let a BLANKET `onAsk: "allow"` bypass clear it — that
132
+ * confirmed P1) additionally refuses to let a BLANKET `onAsk: "allow"` circumvention clear it — that
132
133
  * configuration is "no approver looked at this at all", indistinguishable from headless, for a rule
133
134
  * whose whole point is that a blanket policy setting must not be the thing that clears it (see
134
135
  * {@link resolveAsk}). A live auto-mode classifier verdict (tried earlier in the gate, if configured)
@@ -336,8 +337,39 @@ export declare function constraintChainDigest(chain: readonly ConstraintChainEnt
336
337
  * out-of-contract decision object, so it is not a rewrite this layer is willing to report as audited.
337
338
  * (A rewrite from an EARLIER, contract-shaped policy still rides out — {@link combinePolicies} adds it
338
339
  * on the deny path per the observer contract.)
340
+ *
341
+ * The second arm screens `decisionReason` against the closed set ({@link DecisionReason}): a
342
+ * deployment-authored `ToolPolicy.check` returning a value outside it would otherwise ride silently
343
+ * into every consumer that BRANCHES on the word — provenance-keyed exclusions, audit rows, checkpoint
344
+ * discriminants — each of which would treat the unclassifiable value as "none of the reasons I know",
345
+ * a meaning the producing policy never chose. Same doctrine as the retired-field arm: out-of-contract
346
+ * input to the permission face is refused loudly, not passed through as an accidental tenth reason.
347
+ * An ABSENT `decisionReason` stays legal (it is optional; `undefined` is the typed spelling of
348
+ * absence), and hook results never reach here with a foreign word — the hook seam stamps its own
349
+ * (`hooks.ts`) — so this arm's live producers are exactly the deployment policies the tripwire exists
350
+ * to screen. Unlike the retired-field arm this one reads the property PLAINLY (prototype chain
351
+ * included), because it answers a different question: the retired arm asks whether the AUTHOR wrote
352
+ * the old field (own property = authorship), this arm asks what a CONSUMER would read — and every
353
+ * consumer branches on a plain `d.decisionReason` read, which the prototype can satisfy.
354
+ *
355
+ * SCOPE of the carrier check (ruled after three review rounds converged on the same root): it screens
356
+ * STRUCTURALLY unstable carriers — getters, prototype-supplied values — i.e. shapes an ordinary
357
+ * deployment can write by accident. It does NOT try to defeat a Proxy whose descriptor trap reports a
358
+ * data property while its get trap stays stateful: same-process JavaScript has no trust boundary a
359
+ * function can enforce (an untrusted party who ships such a Proxy can as easily patch this module), so
360
+ * chasing that shape adds complexity without adding a guarantee. The screen's promise is against
361
+ * drift and accident, not against a untrusted co-resident.
362
+ *
363
+ * `reasonIsNonInput` (the HOOK seam's spelling — merged-code scan, 5.28 window): at that seam the
364
+ * field is documented as DISCARDED — every mint point downstream unconditionally re-stamps
365
+ * `decisionReason:"hook"` (fold, deny arm, delegated twin), and the allow path never reads it — so
366
+ * both reason arms are skipped there: refusing a value that cannot travel would fail-close a call
367
+ * over a field with no consumer, which inverted the discard contract the stamp exists to enforce.
368
+ * The retired-`reason` arm still applies (that one is about the MESSAGE channel, which does travel).
339
369
  */
340
- export declare function refuseOutOfContractDecision(d: PermissionResult): PermissionResult;
370
+ export declare function refuseOutOfContractDecision(d: PermissionResult, opts?: {
371
+ reasonIsNonInput?: boolean;
372
+ }): PermissionResult;
341
373
  /** The raw tool-name lists a name-keyed policy was built from (audit feed, see block note above). */
342
374
  export interface ToolPolicyNameSets {
343
375
  readonly allow?: readonly string[];
@@ -443,14 +475,14 @@ export declare const COARSE_SHELL_TOOLS: string[];
443
475
  * design/center §10 — a COARSE allow/deny gate keyed on the LEADING command NAME (`argv[0]`) of a single
444
476
  * simple shell command. It reuses the ONE shared simple-command parser
445
477
  * ({@link import("../tools/fs/index.js").parseLeadingCommandName}) — it does NOT re-implement argv[0] parsing
446
- * (a second parser would drift and open a bypass). For a `bash`/`bash_readonly` call it extracts the leading
478
+ * (a second parser would drift and open a circumvention). For a `bash`/`bash_readonly` call it extracts the leading
447
479
  * command name and:
448
480
  * - if the name is in `deny` → `deny` (deny-wins, matching {@link createAllowDenyPolicy}; NOT last-match-wins,
449
481
  * which would fight {@link combinePolicies}'s deny>ask>allow fold);
450
482
  * - else if `allow` is provided and the name is NOT in it → `defaultAction` (`"ask"` default, or `"deny"`);
451
483
  * - else → `allow`.
452
484
  * A command the parser cannot reduce to a single bare name (it has shell operators / a path-prefix / a leading
453
- * env-assignment) is treated by `defaultAction` (fail-toward-gate): such a command is exactly what would bypass
485
+ * env-assignment) is treated by `defaultAction` (fail-toward-gate): such a command is exactly what would circumvent
454
486
  * an argv[0] filter, so it should not silently `allow`. NON-shell tools are out of scope → `allow` (this gate
455
487
  * only speaks about shell command names; compose it with other policies for the rest).
456
488
  *
@@ -458,7 +490,7 @@ export declare const COARSE_SHELL_TOOLS: string[];
458
490
  * injected by the profile/config/deployment.
459
491
  *
460
492
  * ⚠️ **NOT A SANDBOX — coarse filter / defense-in-depth only.** This matches `argv[0]` NAME and nothing else,
461
- * so the bypass surface is large and the gate MUST NOT be relied on as containment:
493
+ * so the circumvention surface is large and the gate MUST NOT be relied on as containment:
462
494
  * - command substitution / subshells (`$(curl …)`, backticks), pipes, redirects, `;`/`&&` chaining — all
463
495
  * rejected as un-parseable here (→ `defaultAction`), so they don't sneak past `allow`, but that is a GATE
464
496
  * not a guarantee of safety;
@@ -542,7 +574,7 @@ export declare function findUnverifiableRecursiveDelete(command: string, safeVar
542
574
  *
543
575
  * codex review (confirmed P1): the one piece of NEW plumbing this DOES need — `requiresRealApproval: true`
544
576
  * (see {@link PermissionResult}'s `ask` doc) — closes a real gap the first cut of this fix missed. A
545
- * deployment using the documented `onAsk: "allow"` bypass mode (sandbox-is-the-boundary posture, the
577
+ * deployment using the documented `onAsk: "allow"` circumvention mode (sandbox-is-the-boundary posture, the
546
578
  * shape a sandboxed autonomy container runs) would otherwise have this rule's `ask` rubber-stamped by that blanket
547
579
  * configuration with NO classifier ever consulted and NO judgment of any kind applied — silently WEAKER
548
580
  * than the unconditional deny it replaced, for exactly the deployments that opted into the most permissive
@@ -670,7 +702,7 @@ export interface AskRequest {
670
702
  readonly sourceTaskId?: string;
671
703
  /** design/153 §1 (RB-39②) — Runner-filled: present (`true`) exactly when the gate raising this ask
672
704
  * belongs to a DELEGATED SUBAGENT (the prepareTask `isSubagent` fact — trusted `internals.parentTaskId`
673
- * presence; a child cannot forge or suppress it, internals never ride a TaskSpec). Undefined ⇒ the
705
+ * presence; a child cannot self-declare or suppress it, internals never ride a TaskSpec). Undefined ⇒ the
674
706
  * host / a top-level task. THE explicit "from a background agent" discriminator (replaces presence-
675
707
  * or equality-heuristics on {@link sourceTaskId}). `readonly`, sync-path-only, S1d posture. */
676
708
  readonly fromSubagent?: true;
@@ -679,6 +711,20 @@ export interface AskRequest {
679
711
  * (the spawning model chooses names): redact + bound before rendering; never adjudication input.
680
712
  * `readonly`, same posture as {@link fromSubagent}. */
681
713
  readonly sourceAgentName?: string;
714
+ /** Runner-filled: present (`true`) exactly when the issuing run is a **delegated child** — ANY
715
+ * spawn leg, FORK INCLUDED (the trusted `RunInternals.isDelegatedChild` fact, set unconditionally
716
+ * by every core spawn path). It selects which REFUSAL POSTURE a human deny speaks back to this
717
+ * run's model: a parent-thread run is told to stop and wait for the user (its transcript has a
718
+ * user turn coming), a delegated child is told to adapt or report the limitation. A fork is
719
+ * deliberately IN: it inherits the parent's authority (design/110 — which is why the RB-330
720
+ * `isDelegatedNonForkChild` derivation, serving the authority/context faces, excludes it), but
721
+ * its interaction contract is one-shot — "report once and stop … no waiting for the user"
722
+ * (FORK_DIRECTIVE_FRAME) — so a stop-and-wait refusal would instruct it to do the impossible
723
+ * (codex adversarial round, confirmed). NOT the same fact as {@link fromSubagent} either: that is
724
+ * the design/153 attribution discriminator (`parentTaskId` presence), which a directly-started
725
+ * workflow's children lack even though refusals must still speak the child posture to them.
726
+ * `readonly`, trusted (internals never ride a TaskSpec), sync-path-only like its siblings. */
727
+ readonly isDelegatedChild?: true;
682
728
  /** RB-203 (codex review, confirmed P1) — carried from the originating {@link PermissionResult}'s ask
683
729
  * variant of the same name: `true` ⇒ {@link resolveAsk} must not let a blanket `onAsk: "allow"`
684
730
  * clear this ask (see that field's doc). `readonly`, filled by the gate from the decision, never a
@@ -730,7 +776,7 @@ export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal)
730
776
  * untyped bridge is a fail-closed deny naming the defect (the historical truthy leniency was a
731
777
  * fail-open on the security face with no live producer).
732
778
  * - `"unavailable"` — the G1 per-ask routing verdict (see {@link OnAsk}).
733
- * - `{ allow, updatedInput?, settledBy? }` — verdict PLUS an operator EDIT of the presented args
779
+ * - `{ allow, updatedInput?, settledBy?, reason? }` — verdict PLUS an operator EDIT of the presented args
734
780
  * (whole-replacement form, e.g. ctrl+g "edit script in $EDITOR"): the human approved a MODIFIED
735
781
  * action, and executing the un-edited args would betray that consent. `allow` folds STRICTLY
736
782
  * (`allow === true`) — an object arm is a deliberate caller, so no truthy leniency — and
@@ -751,11 +797,30 @@ export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal)
751
797
  * `"aborted"` is deliberately NOT accepted here — that word names the engine's OWN fail-closed ends
752
798
  * (abort, throw, unavailable, out-of-contract value), each already stamped at its own arm, and a
753
799
  * self-reported one would let a host relabel its refusal as an engine failure.
800
+ *
801
+ * `reason` — the SYNCHRONOUS leg's seat for a model-readable reason attached to a deny, the exact
802
+ * counterpart of the durable leg's `ResumeOutcome` `policy_ask` `reason` ("Model-readable reason
803
+ * attached to a deny"): the words the decider typed with their "no" (an approval card's rejection
804
+ * input), relayed to the model so it can follow the person's direction instead of guessing. In-place
805
+ * conditions, all screened at {@link resolveAsk}:
806
+ * - meaningful ONLY on a deny (`allow: false`); on an allow the seat is NEVER READ (accepted and
807
+ * unused, literally — codex round: a junk value or a throwing accessor riding an approval cannot
808
+ * destroy the approval). Same net posture as the durable leg, where a `reason` on an `allow` row
809
+ * is never rendered;
810
+ * - UNTRUSTED input (a human's free text riding a callback): it reaches the model only inside a
811
+ * `delimitUntrusted` fence, the same containment the durable resume's reviewer note gets, and
812
+ * bounded to `REVIEWER_NOTE_MAX_BODY` code points (an oversized note arrives truncated with an
813
+ * ellipsis, never verbatim and never silently dropped — both legs share the one bound);
814
+ * - on a deny, a present non-string is refused loudly (fail-closed deny naming the defect — the
815
+ * domain the durable leg enforces pre-CAS for the same field), a THROWING read is a fail-closed
816
+ * deny naming the true cause (never a raw rejection out of the gate), and the empty string reads
817
+ * as absent (truthiness, the durable consumer's own read).
754
818
  */
755
819
  export type AskOutcome = boolean | "unavailable" | {
756
820
  allow: boolean;
757
821
  updatedInput?: unknown;
758
822
  settledBy?: Extract<ApprovalSettledBy, "human" | "timeout">;
823
+ reason?: string;
759
824
  };
760
825
  /**
761
826
  * ruled 2026-08-04 — forward an approver into a delegated child, stamping every ask it raises with the
@@ -802,7 +867,7 @@ export declare function tryCloneArgs<T>(v: T): {
802
867
  };
803
868
  /**
804
869
  * A label for a value that was THROWN at us, safe to put in a fail-closed refusal. Never coerces: `String(x)`
805
- * throws on a null-prototype object or a hostile `toString`/`Symbol.toPrimitive`, and the thrower here is
870
+ * throws on a null-prototype object or a untrusted `toString`/`Symbol.toPrimitive`, and the thrower here is
806
871
  * deployment-supplied (a getter inside the args can throw any object at `structuredClone`, an approver can
807
872
  * throw one at the gate). A coercion failure inside the catch would escape `resolveAsk` as a raw TypeError —
808
873
  * turning a classified fail-closed DENY into an unclassified crash, i.e. the failing input escaping the very
@@ -819,7 +884,7 @@ export declare function tryCloneArgs<T>(v: T): {
819
884
  *
820
885
  * Exported for the durable half of the same boundary (the runner's park mint), which now has a SECOND
821
886
  * way a deployment-supplied value can throw at capture time — the target backend's own codec refusing to
822
- * encode it. One containment point, so the two refusals cannot differ on how much of a hostile value's
887
+ * encode it. One containment point, so the two refusals cannot differ on how much of a untrusted value's
823
888
  * text they let through. Not part of the package's public surface.
824
889
  */
825
890
  export declare function describeThrown(err: unknown): string;
@@ -851,3 +916,4 @@ export type ResolvedAsk = PermissionResult & {
851
916
  * have ended, and `decisionReason: "mode"` is already the honest word for what produced them.
852
917
  */
853
918
  export declare function resolveAsk(req: AskRequest, onAsk: OnAsk | undefined, signal?: AbortSignal): Promise<ResolvedAsk>;
919
+ export {};