@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.
- package/CHANGELOG.md +97 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/observer.d.ts +3 -3
- package/dist/agents/peer-admission.d.ts +1 -1
- package/dist/agents/teacher.d.ts +1 -1
- package/dist/agents/verify.d.ts +6 -6
- package/dist/bench/metrics.d.ts +1 -1
- package/dist/brain/retry.d.ts +1 -1
- package/dist/core/arg-summary.d.ts +1 -1
- package/dist/core/ask-class.d.ts +2 -2
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/ask-question.js +14 -1
- package/dist/core/background-shell.d.ts +5 -5
- package/dist/core/checkpoint-store.d.ts +14 -14
- package/dist/core/file-snapshot-store.d.ts +1 -1
- package/dist/core/hooks.d.ts +25 -3
- package/dist/core/hooks.js +22 -3
- package/dist/core/human-input-projection.d.ts +2 -2
- package/dist/core/memory-admission.d.ts +2 -2
- package/dist/core/memory-engine/engine.d.ts +1 -1
- package/dist/core/memory-engine/engine.js +2 -4
- package/dist/core/memory-engine/file-backend.d.ts +68 -9
- package/dist/core/memory-engine/file-backend.js +69 -27
- package/dist/core/memory-engine/header-hints.d.ts +1 -1
- package/dist/core/memory-engine/layout.d.ts +32 -3
- package/dist/core/memory-engine/layout.js +132 -8
- package/dist/core/memory-engine/types.d.ts +6 -5
- package/dist/core/memory-recall.d.ts +1 -1
- package/dist/core/memory.d.ts +2 -2
- package/dist/core/oracle-isolation.d.ts +2 -2
- package/dist/core/permission-rule-consent.d.ts +83 -9
- package/dist/core/permission-rule-consent.js +92 -1
- package/dist/core/permission-rule-model.d.ts +18 -2
- package/dist/core/permission-rule-model.js +21 -0
- package/dist/core/permission-rule-org.d.ts +23 -4
- package/dist/core/permission-rule-org.js +67 -20
- package/dist/core/permission-rule-store.d.ts +1 -1
- package/dist/core/permission-rule-store.js +2 -2
- package/dist/core/permission-rule-sync.d.ts +15 -1
- package/dist/core/permission-rule-sync.js +89 -47
- package/dist/core/runner/active-skill-scope.d.ts +1 -1
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-task.d.ts +8 -3
- package/dist/core/runner/prepare-task.js +22 -7
- package/dist/core/runner/runtask.d.ts +1 -1
- package/dist/core/runner/runtask.js +13 -6
- package/dist/core/runner/session-rule-policy.d.ts +2 -2
- package/dist/core/runner/teardown-bounded.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +1 -1
- package/dist/core/scheduler.d.ts +4 -4
- package/dist/core/session-policy-store.d.ts +3 -3
- package/dist/core/shared-memory/normalize.d.ts +1 -1
- package/dist/core/skill-tool-specifier.d.ts +1 -1
- package/dist/core/task-notification.d.ts +2 -2
- package/dist/core/task-registry-agent.d.ts +18 -9
- package/dist/core/task-registry-agent.js +51 -21
- package/dist/core/task-registry-monitor.js +1 -1
- package/dist/core/task-registry-shared.d.ts +13 -4
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-policy.d.ts +78 -12
- package/dist/core/tool-policy.js +74 -7
- package/dist/core/tool-result-store.d.ts +109 -8
- package/dist/core/tool-result-store.js +95 -15
- package/dist/core/trace.d.ts +1 -1
- package/dist/core/types.d.ts +92 -22
- package/dist/core/types.js +30 -1
- package/dist/core/untrusted-text.d.ts +6 -0
- package/dist/core/untrusted-text.js +1 -0
- package/dist/engine/compaction/compaction.d.ts +1 -1
- package/dist/engine/harness/messages.d.ts +1 -1
- package/dist/engine/harness/types.d.ts +2 -2
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/types.d.ts +2 -2
- package/dist/engine/session/import-validate.d.ts +1 -1
- package/dist/engine/session/log-digest.d.ts +1 -1
- package/dist/fixtures/index.d.ts +18 -2
- package/dist/fixtures/index.js +11 -0
- package/dist/index.d.ts +2 -2
- package/dist/orchestration/goal.d.ts +1 -1
- package/dist/orchestration/workflow-types.d.ts +5 -4
- package/dist/orchestration/workflow.js +11 -3
- package/dist/prompt-assembly/artifact.d.ts +1 -1
- package/dist/prompts/default.d.ts +1 -1
- package/dist/prompts/default.js +1 -1
- package/dist/scenarios/scenario-registry.d.ts +3 -3
- package/dist/stores/file/tool-result-store.d.ts +43 -3
- package/dist/stores/file/tool-result-store.js +107 -19
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -2
- package/dist/tools/fs/fs-bash.d.ts +8 -1
- package/dist/tools/fs/fs-search-tools.d.ts +1 -1
- package/dist/tools/fs/fs-shared.d.ts +6 -1
- package/dist/tools/fs/fs-shared.js +11 -7
- package/dist/tools/fs/index.d.ts +6 -0
- package/dist/tools/fs/index.js +2 -0
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/scheduler-tools.d.ts +1 -1
- package/dist/tools/task-list.d.ts +1 -1
- package/dist/tools/web.d.ts +3 -3
- 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`
|
|
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
|
|
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 `..`-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
* -
|
|
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
|
|
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
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
1262
|
-
? ` (error_kind: ${
|
|
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: ${
|
|
1271
|
-
${
|
|
1272
|
-
` : ""}${
|
|
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:
|
|
1308
|
+
status: snap.status,
|
|
1279
1309
|
retrievalStatus: retrieval,
|
|
1280
|
-
...(
|
|
1281
|
-
...(
|
|
1282
|
-
...(
|
|
1283
|
-
...(
|
|
1284
|
-
...(
|
|
1285
|
-
...(
|
|
1286
|
-
...(
|
|
1287
|
-
...(
|
|
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
|
-
...(
|
|
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
|
|
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
|
|
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
|
|
101
|
-
* (including an idempotent re-call from a
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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"`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"`
|
|
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
|
|
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
|
|
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
|
|
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 {};
|