@sema-agent/core 7.5.1 → 7.6.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 +61 -0
- package/dist/agents/cascade.d.ts +2 -2
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +5 -3
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/subagent.d.ts +24 -42
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +4 -3
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +3 -3
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hooks.d.ts +18 -92
- package/dist/core/hooks.js +88 -85
- package/dist/core/mcp-failure.d.ts +104 -0
- package/dist/core/mcp-failure.js +128 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/protocol-table.d.ts +5 -0
- package/dist/core/protocol-table.js +1 -0
- package/dist/core/runner/abort-race.d.ts +41 -0
- package/dist/core/runner/abort-race.js +38 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/checkpoint-scope.d.ts +15 -3
- package/dist/core/runner/checkpoint-scope.js +3 -0
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/content-ask-bindings.d.ts +27 -0
- package/dist/core/runner/content-ask-bindings.js +1 -0
- package/dist/core/runner/contracts.d.ts +144 -50
- package/dist/core/runner/denial-limit-arms.d.ts +24 -1
- package/dist/core/runner/denial-limit-arms.js +24 -3
- package/dist/core/runner/gate-exit.d.ts +74 -0
- package/dist/core/runner/gate-exit.js +55 -0
- package/dist/core/runner/inherited-ask-grants.d.ts +46 -0
- package/dist/core/runner/inherited-ask-grants.js +29 -0
- package/dist/core/runner/park-commit.d.ts +102 -0
- package/dist/core/runner/park-commit.js +31 -0
- package/dist/core/runner/{prepare-permission-rules.d.ts → permission-rule-lanes.d.ts} +109 -3
- package/dist/core/runner/{prepare-permission-rules.js → permission-rule-lanes.js} +47 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +107 -0
- package/dist/core/runner/prepare-ask-lane.js +131 -0
- package/dist/core/runner/prepare-boundary-parks.d.ts +102 -0
- package/dist/core/runner/prepare-boundary-parks.js +169 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-context-lane.d.ts +119 -0
- package/dist/core/runner/prepare-context-lane.js +230 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +174 -0
- package/dist/core/runner/prepare-gate-stations.js +265 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -4
- package/dist/core/runner/prepare-inherited-gate.d.ts +4 -4
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory-engine-session.d.ts +84 -0
- package/dist/core/runner/prepare-memory-engine-session.js +233 -0
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +162 -0
- package/dist/core/runner/prepare-park-ask.js +377 -0
- package/dist/core/runner/prepare-policy-chain.d.ts +208 -0
- package/dist/core/runner/prepare-policy-chain.js +584 -0
- package/dist/core/runner/prepare-project-context.d.ts +1 -13
- package/dist/core/runner/prepare-project-context.js +1 -3
- package/dist/core/runner/prepare-prompt-assembly.d.ts +95 -0
- package/dist/core/runner/prepare-prompt-assembly.js +162 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -20
- package/dist/core/runner/prepare-protocol-tools.d.ts +3 -3
- package/dist/core/runner/prepare-protocol-tools.js +0 -3
- package/dist/core/runner/prepare-question-face.d.ts +3 -21
- package/dist/core/runner/prepare-question-face.js +2 -1
- package/dist/core/runner/prepare-safety-scan.d.ts +0 -5
- package/dist/core/runner/prepare-safety-scan.js +1 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +170 -0
- package/dist/core/runner/prepare-suspend-saga.js +308 -0
- package/dist/core/runner/prepare-task.d.ts +9 -136
- package/dist/core/runner/prepare-task.js +47 -2745
- package/dist/core/runner/prepare-turn-wiring.d.ts +154 -0
- package/dist/core/runner/prepare-turn-wiring.js +201 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +17 -17
- package/dist/core/runner/prepare-wiring-manifest.js +16 -10
- package/dist/core/runner/prepare-workspace-restore.d.ts +2 -29
- package/dist/core/runner/prepare-workspace-restore.js +3 -16
- package/dist/core/runner/prompt-hash-salt.d.ts +1 -0
- package/dist/core/runner/prompt-hash-salt.js +2 -0
- package/dist/core/runner/remote-env-retry.d.ts +29 -0
- package/dist/core/runner/remote-env-retry.js +16 -0
- package/dist/core/runner/runtask.d.ts +16 -31
- package/dist/core/runner/runtask.js +110 -121
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/session.d.ts +12 -0
- package/dist/core/session.js +3 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/types.d.ts +67 -164
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +10 -7
- package/dist/index.js +8 -5
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +11 -0
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +74 -22
- /package/dist/core/runner/{prepare-announce-once.d.ts → announce-once-ledger.d.ts} +0 -0
- /package/dist/core/runner/{prepare-announce-once.js → announce-once-ledger.js} +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { TaskResult } from "../core/types.js";
|
|
1
|
+
import type { PausedCause, TaskResult, TerminalCause } from "../core/types.js";
|
|
2
2
|
/**
|
|
3
|
-
* design/45 §11 Q6 — the **durable-suspend hard boundary** (v1). A `
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* "unexpected.
|
|
10
|
-
*
|
|
3
|
+
* design/45 §11 Q6 — the **durable-suspend hard boundary** (v1). A `paused` terminal is only safe to
|
|
4
|
+
* handle at the **top-level `runTask`** boundary: the orchestrators (cascade / verify / teacher / team)
|
|
5
|
+
* run their work as *nested* `runTask`s, and none of them can drive a resume from the inside. If a nested
|
|
6
|
+
* task pauses on a durable gate, the orchestrator must **fail fast** — not silently treat it as an
|
|
7
|
+
* ordinary failure (which would escalate/re-run side effects) and not release its pinned session (which
|
|
8
|
+
* would orphan the checkpoint). It maps `paused → failed` with the code `"unexpected.suspended"` (an
|
|
9
|
+
* approval / resource pause) or `"unexpected.needs_review"` (a review pause) and **carries the pause's own
|
|
10
|
+
* cause on the failed terminal's `nestedPause` seat** so the top-level caller can still resume.
|
|
11
11
|
*
|
|
12
12
|
* The structural prevention is upstream — an orchestrator should run nested tasks under a **non-durable**
|
|
13
13
|
* approval policy (e.g. the verifier already forces `handsReadOnly` + headless auto-deny, DESIGN#5) so a
|
|
@@ -15,20 +15,32 @@ import type { TaskResult } from "../core/types.js";
|
|
|
15
15
|
* a hard boundary, or a deployment wires durable-suspend into verify and produces silent errors."
|
|
16
16
|
*/
|
|
17
17
|
export declare const UNEXPECTED_SUSPENDED = "unexpected.suspended";
|
|
18
|
-
/** design/80 D-B: the
|
|
19
|
-
*
|
|
18
|
+
/** design/80 D-B: the review durable-pause family (dry-run `needs_review` AND `plan_review`) hits the SAME
|
|
19
|
+
* nested hard boundary as an approval pause — a distinct code so a caller can tell a review pause from an
|
|
20
|
+
* approval. */
|
|
20
21
|
export declare const UNEXPECTED_NEEDS_REVIEW = "unexpected.needs_review";
|
|
21
22
|
/**
|
|
22
|
-
* design/80 D-B: the durable-pause hard boundary covers
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* design/80 D-B: the durable-pause hard boundary covers EVERY `paused` cause — an approval gate, a resource
|
|
24
|
+
* slice, a dry-run review, a `plan_review` plan-gate. All of them persist a checkpoint + PIN their session,
|
|
25
|
+
* and a nested orchestrator can't drive their resume — so it must surface the pause and NOT release the
|
|
26
|
+
* pinned session (releasing it orphans the checkpoint). A type guard, so the caller reads the token/gate
|
|
27
|
+
* off the narrowed cause.
|
|
27
28
|
*/
|
|
28
|
-
export declare function isDurablePause(
|
|
29
|
+
export declare function isDurablePause(terminal: TerminalCause): terminal is PausedCause;
|
|
30
|
+
/** A result whose terminal is known to be a pause — what {@link isDurablePause} narrows a result's
|
|
31
|
+
* `terminal` to, spelled as a type so an orchestrator can HOLD a paused result without re-checking. */
|
|
32
|
+
export type PausedResult = TaskResult & {
|
|
33
|
+
terminal: PausedCause;
|
|
34
|
+
};
|
|
29
35
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
36
|
+
* Which family a pause belongs to — its gate kind's registry row: `"suspended"` (an approval gate, a
|
|
37
|
+
* resource slice, a platform park) or `"needs_review"` (a dry-run or plan review). The same row the
|
|
38
|
+
* status word is projected from; read here by the orchestrators that name the family in their own faces.
|
|
39
|
+
*/
|
|
40
|
+
export declare function pauseFamily(t: PausedCause): "suspended" | "needs_review";
|
|
41
|
+
/**
|
|
42
|
+
* If `result` durably paused, return it mapped to a `failed` result whose terminal carries the pause on
|
|
43
|
+
* `nestedPause`; otherwise return it unchanged. The orchestrator should also **stop** (not escalate/re-run)
|
|
32
44
|
* and **not release** the session when this maps a pause — the checkpoint still references that session.
|
|
33
45
|
*/
|
|
34
46
|
export declare function mapNestedSuspend(result: TaskResult): TaskResult;
|
|
@@ -1,18 +1,24 @@
|
|
|
1
|
+
import { PAUSE_REGISTRY } from "../core/pause-registry.js";
|
|
1
2
|
export const UNEXPECTED_SUSPENDED = "unexpected.suspended";
|
|
2
3
|
export const UNEXPECTED_NEEDS_REVIEW = "unexpected.needs_review";
|
|
3
|
-
export function isDurablePause(
|
|
4
|
-
return
|
|
4
|
+
export function isDurablePause(terminal) {
|
|
5
|
+
return terminal.kind === "paused";
|
|
6
|
+
}
|
|
7
|
+
export function pauseFamily(t) {
|
|
8
|
+
return PAUSE_REGISTRY[t.gate.kind].taskStatus;
|
|
5
9
|
}
|
|
6
10
|
export function mapNestedSuspend(result) {
|
|
7
|
-
|
|
11
|
+
const t = result.terminal;
|
|
12
|
+
if (!isDurablePause(t)) {
|
|
8
13
|
return result;
|
|
9
14
|
}
|
|
15
|
+
const word = pauseFamily(t);
|
|
16
|
+
const code = word === "needs_review" ? UNEXPECTED_NEEDS_REVIEW : UNEXPECTED_SUSPENDED;
|
|
17
|
+
const message = `a nested task durably paused ("${word}") on a HITL gate; durable suspend/resume and plan/` +
|
|
18
|
+
"dry-run review are only supported at the top-level runTask (design/45 v1 / design/80). Resume it via " +
|
|
19
|
+
"the pause's token, or run the orchestrated task under a non-durable policy so it auto-adjudicates instead of pausing.";
|
|
10
20
|
return {
|
|
11
21
|
...result,
|
|
12
|
-
|
|
13
|
-
errorCode: result.status === "needs_review" ? UNEXPECTED_NEEDS_REVIEW : UNEXPECTED_SUSPENDED,
|
|
14
|
-
errorMessage: `a nested task durably paused ("${result.status}") on a HITL gate; durable suspend/resume and plan/` +
|
|
15
|
-
"dry-run review are only supported at the top-level runTask (design/45 v1 / design/80). Resume it via " +
|
|
16
|
-
"checkpointToken, or run the orchestrated task under a non-durable policy so it auto-adjudicates instead of pausing.",
|
|
22
|
+
terminal: { kind: "failed", code, message, nestedPause: t },
|
|
17
23
|
};
|
|
18
24
|
}
|
package/dist/agents/teacher.js
CHANGED
|
@@ -119,7 +119,7 @@ export async function runWithTeacher(runner, studentSpec, teacher = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
const result = await runTeacherCore(runner, spec, teacher);
|
|
122
|
-
if (store && scope && result.
|
|
122
|
+
if (store && scope && result.terminal.kind === "completed" && result.escalations.length > 0) {
|
|
123
123
|
const advice = result.escalations[result.escalations.length - 1].teacher;
|
|
124
124
|
if (advice.strategy && advice.confidence >= (teacher.minConfidenceToStore ?? 2)) {
|
|
125
125
|
try {
|
|
@@ -271,7 +271,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
271
271
|
...(studentSpec.restoreGatedTools !== undefined
|
|
272
272
|
? { restoreGatedTools: Array.isArray(studentSpec.restoreGatedTools) ? [...studentSpec.restoreGatedTools] : studentSpec.restoreGatedTools }
|
|
273
273
|
: {}),
|
|
274
|
-
...(studentSpec.checkpointStore ===
|
|
274
|
+
...(studentSpec.checkpointStore === "disabled" ? { checkpointStore: "disabled" } : {}),
|
|
275
275
|
};
|
|
276
276
|
const helperBase = () => teacher.helperModel
|
|
277
277
|
? { ...inheritedDeploymentConfig, model: teacher.helperModel }
|
|
@@ -407,9 +407,8 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
407
407
|
const syntheticAborted = (sid) => ({
|
|
408
408
|
taskId: sid ?? "aborted",
|
|
409
409
|
sessionId: sid ?? "aborted",
|
|
410
|
-
|
|
410
|
+
terminal: { kind: "failed", message: "student run did not settle promptly after stuck-abort" },
|
|
411
411
|
result: "",
|
|
412
|
-
errorMessage: "student run did not settle promptly after stuck-abort",
|
|
413
412
|
stats: { turns: 0, tokens: 0 },
|
|
414
413
|
});
|
|
415
414
|
const abortFallback = (sid) => {
|
|
@@ -451,7 +450,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
451
450
|
toolLog.length = 0;
|
|
452
451
|
const { result, stuck, recent } = await runStudent();
|
|
453
452
|
sessionId = result.sessionId;
|
|
454
|
-
if (isDurablePause(result.
|
|
453
|
+
if (isDurablePause(result.terminal)) {
|
|
455
454
|
return finalize(mapNestedSuspend(result));
|
|
456
455
|
}
|
|
457
456
|
let trigger;
|
|
@@ -459,10 +458,10 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
459
458
|
if (stuck) {
|
|
460
459
|
trigger = "stuck";
|
|
461
460
|
}
|
|
462
|
-
else if (result.
|
|
461
|
+
else if (result.terminal.kind === "blocked") {
|
|
463
462
|
trigger = "blocked";
|
|
464
463
|
}
|
|
465
|
-
else if (result.
|
|
464
|
+
else if (result.terminal.kind === "failed") {
|
|
466
465
|
trigger = "failed";
|
|
467
466
|
}
|
|
468
467
|
else if (!verifyOutput) {
|
|
@@ -482,7 +481,8 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
482
481
|
return finalize(result);
|
|
483
482
|
}
|
|
484
483
|
if (trigger === "failed" &&
|
|
485
|
-
|
|
484
|
+
result.terminal.kind === "failed" &&
|
|
485
|
+
/truncat|max[ _-]?tokens?|maximum tokens?|token (limit|budget)|context (length|window)|no answer text|(consumed|exceeded).{0,12}budget/i.test(result.terminal.message ?? "")) {
|
|
486
486
|
return finalize(result);
|
|
487
487
|
}
|
|
488
488
|
if (escalations.length >= maxEscalations) {
|
|
@@ -528,7 +528,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
528
528
|
signal: extSignal,
|
|
529
529
|
});
|
|
530
530
|
addTeacher(r);
|
|
531
|
-
if (isDurablePause(r.
|
|
531
|
+
if (isDurablePause(r.terminal)) {
|
|
532
532
|
return finalize(mapNestedSuspend(r));
|
|
533
533
|
}
|
|
534
534
|
return finalize(r);
|
package/dist/agents/team.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Runner } from "../core/runner/runtask.js";
|
|
2
|
-
import type { McpServerSpec, ModelRef, ModelRole,
|
|
2
|
+
import type { McpServerSpec, ModelRef, ModelRole, TaskSpec, ToolSpec } from "../core/types.js";
|
|
3
|
+
import type { CheckpointGate, CheckpointToken } from "../core/checkpoint-store.js";
|
|
3
4
|
export interface TeamMember {
|
|
4
5
|
/** The member's role / specialty, e.g. "安全评审" or "performance". */
|
|
5
6
|
role: string;
|
|
@@ -206,8 +207,8 @@ export interface TeamResult {
|
|
|
206
207
|
* the discussion STOPS (no synthesis on a half-done team) and surfaces the resume capability so the caller
|
|
207
208
|
* can resume the paused member via the token, then re-run. `conclusionValid` is false in this case. */
|
|
208
209
|
durablePause?: boolean;
|
|
209
|
-
checkpointToken?:
|
|
210
|
-
checkpointGate?:
|
|
210
|
+
checkpointToken?: CheckpointToken;
|
|
211
|
+
checkpointGate?: CheckpointGate;
|
|
211
212
|
}
|
|
212
213
|
/**
|
|
213
214
|
* Run a multi-round discussion across several role-specialized agents, then synthesize a conclusion.
|
package/dist/agents/team.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveToolSubset } from "./tool-filter.js";
|
|
2
2
|
import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
|
|
3
|
+
import { terminalProjection } from "../core/runner/terminal-projection.js";
|
|
3
4
|
import { sanitizeUntrustedText } from "../core/untrusted-text.js";
|
|
4
5
|
const DISCUSSION_WRAPPER_TAGS = ["statement", "discussion", "discussion-final", "earlier-discussion-summary", "previous-summary", "topic"];
|
|
5
6
|
const RETIRED_WRAPPER_TAGS = ["team-discussion", "team-discussion-final"];
|
|
@@ -87,7 +88,7 @@ export async function runTeamDiscussion(opts) {
|
|
|
87
88
|
return {
|
|
88
89
|
status: "failed",
|
|
89
90
|
result: conclusion,
|
|
90
|
-
errorCode: p.errorCode,
|
|
91
|
+
errorCode: terminalProjection(mapNestedSuspend(p).terminal).errorCode,
|
|
91
92
|
conclusion,
|
|
92
93
|
conclusionValid: false,
|
|
93
94
|
transcript,
|
|
@@ -96,8 +97,8 @@ export async function runTeamDiscussion(opts) {
|
|
|
96
97
|
failures,
|
|
97
98
|
transcriptCompactions,
|
|
98
99
|
durablePause: true,
|
|
99
|
-
checkpointToken: p.
|
|
100
|
-
checkpointGate: p.
|
|
100
|
+
checkpointToken: p.terminal.token,
|
|
101
|
+
checkpointGate: p.terminal.gate,
|
|
101
102
|
};
|
|
102
103
|
};
|
|
103
104
|
const runTurn = async (objective, systemPrompt, modelRef, role, tools, mcp) => {
|
|
@@ -122,16 +123,17 @@ export async function runTeamDiscussion(opts) {
|
|
|
122
123
|
tokens += res.stats.tokens + (res.stats.nested?.tokens ?? 0);
|
|
123
124
|
turns += res.stats.turns + (res.stats.nested?.turns ?? 0);
|
|
124
125
|
costMicroUsd += (res.stats.costMicroUsd ?? 0) + (res.stats.nested?.costMicroUsd ?? 0);
|
|
125
|
-
if (isDurablePause(res.
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
if (isDurablePause(res.terminal)) {
|
|
127
|
+
pendingPause = { ...res, terminal: res.terminal };
|
|
128
|
+
const m = terminalProjection(mapNestedSuspend(res).terminal);
|
|
128
129
|
return { text: `[unavailable: ${m.status}/${m.errorCode}]`, ok: false };
|
|
129
130
|
}
|
|
130
131
|
await opts.runner.sessions.release(res.sessionId).catch(() => undefined);
|
|
131
|
-
if (res.
|
|
132
|
+
if (res.terminal.kind === "completed" && res.result.trim()) {
|
|
132
133
|
return { text: res.result, ok: true };
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
+
const face = terminalProjection(res.terminal);
|
|
136
|
+
last = `[unavailable: ${face.status}${face.errorCode ? `/${face.errorCode}` : ""}${face.errorMessage ? ` — ${face.errorMessage}` : ""}]`;
|
|
135
137
|
}
|
|
136
138
|
return { text: last || "[unavailable]", ok: false };
|
|
137
139
|
};
|
package/dist/agents/verify.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Type, type Static } from "typebox";
|
|
|
2
2
|
import type { Runner, ResumeTaskConfig } from "../core/runner/runtask.js";
|
|
3
3
|
import type { RunInternals } from "../core/runner/prepare-task.js";
|
|
4
4
|
import type { CheckpointGate, CheckpointToken, ResumeOutcome } from "../core/checkpoint-store.js";
|
|
5
|
-
import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js";
|
|
5
|
+
import type { ModelRef, TaskResult, TaskSpec, TaskStatus, ToolSpec } from "../core/types.js";
|
|
6
6
|
/**
|
|
7
7
|
* Verification gate (developer mode, design/28 §4). An **independent falsification-style verifier** runs after
|
|
8
8
|
* an implementation task and tries to BREAK it — read-only, evidence-required, returning a structured
|
|
@@ -131,7 +131,7 @@ export interface VerifyConfig {
|
|
|
131
131
|
*/
|
|
132
132
|
onVerifierPause?: (info: {
|
|
133
133
|
round: number;
|
|
134
|
-
status:
|
|
134
|
+
status: TaskStatus;
|
|
135
135
|
sessionId: string;
|
|
136
136
|
checkpointToken?: CheckpointToken;
|
|
137
137
|
checkpointGate?: CheckpointGate;
|
|
@@ -200,7 +200,7 @@ export interface VerificationOutcome {
|
|
|
200
200
|
*/
|
|
201
201
|
verifierPause?: {
|
|
202
202
|
/** The verifier run's terminal pause status (`"suspended"` or `"needs_review"`). */
|
|
203
|
-
status:
|
|
203
|
+
status: TaskStatus;
|
|
204
204
|
/** The paused verifier's own session id (independent of the implementation's). */
|
|
205
205
|
sessionId: string;
|
|
206
206
|
/** The resume capability for the verifier's checkpoint. Undefined only if the pause carried none. */
|
package/dist/agents/verify.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { CODE_AGENT_PROMPT } from "../prompts/default.js";
|
|
3
3
|
import { releaseSession } from "./session-util.js";
|
|
4
|
-
import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
|
|
4
|
+
import { mapNestedSuspend, isDurablePause, pauseFamily } from "./suspend-guard.js";
|
|
5
5
|
import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
|
|
6
6
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
7
7
|
import { createCumulativeStatsTracker } from "./cumulative-stats.js";
|
|
@@ -102,7 +102,7 @@ function verifierObjective(originalObjective, impl, evidence) {
|
|
|
102
102
|
}
|
|
103
103
|
export async function verifyCompleted(runner, result, specBase, objective, config, internals) {
|
|
104
104
|
refuseUnhonorableInternals(internals, "door");
|
|
105
|
-
if (result.
|
|
105
|
+
if (result.terminal.kind !== "completed") {
|
|
106
106
|
return { ...result, verification: { verdict: "unverified", unverifiedReason: "impl_incomplete", rounds: 0, findings: [] } };
|
|
107
107
|
}
|
|
108
108
|
const maxRounds = Number.isFinite(config.maxRounds) ? Math.max(1, Math.floor(config.maxRounds)) : 2;
|
|
@@ -168,10 +168,10 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
168
168
|
}, legInternals());
|
|
169
169
|
try {
|
|
170
170
|
const cost = (v.stats.costMicroUsd ?? 0) + (v.stats.nested?.costMicroUsd ?? 0);
|
|
171
|
-
return { verdict: v.structuredOutput, cost, ...(isDurablePause(v.
|
|
171
|
+
return { verdict: v.structuredOutput, cost, ...(isDurablePause(v.terminal) ? { paused: { ...v, terminal: v.terminal } } : {}) };
|
|
172
172
|
}
|
|
173
173
|
finally {
|
|
174
|
-
if (!isDurablePause(v.
|
|
174
|
+
if (!isDurablePause(v.terminal))
|
|
175
175
|
await releaseSession(runner, v.sessionId);
|
|
176
176
|
}
|
|
177
177
|
};
|
|
@@ -202,18 +202,18 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
202
202
|
findings: [],
|
|
203
203
|
verifierCost: verifierSpend,
|
|
204
204
|
verifierPause: {
|
|
205
|
-
status:
|
|
206
|
-
sessionId:
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
status: pauseFamily(paused.terminal),
|
|
206
|
+
sessionId: paused.sessionId,
|
|
207
|
+
checkpointToken: paused.terminal.token,
|
|
208
|
+
checkpointGate: paused.terminal.gate,
|
|
209
209
|
},
|
|
210
210
|
};
|
|
211
211
|
notifier.notify(() => config.onVerifierPause?.({
|
|
212
212
|
round,
|
|
213
|
-
status: paused.
|
|
213
|
+
status: pauseFamily(paused.terminal),
|
|
214
214
|
sessionId: paused.sessionId,
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
checkpointToken: paused.terminal.token,
|
|
216
|
+
checkpointGate: paused.terminal.gate,
|
|
217
217
|
}), VERIFY_ON_VERIFIER_PAUSE_SITE);
|
|
218
218
|
notifier.notify(() => config.onRound?.({ round, verdict: outcome.verdict, findings: outcome.findings }), VERIFY_ON_ROUND_SITE);
|
|
219
219
|
break;
|
|
@@ -248,10 +248,10 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
248
248
|
}, legInternals());
|
|
249
249
|
spend += (current.stats.costMicroUsd ?? 0) + (current.stats.nested?.costMicroUsd ?? 0);
|
|
250
250
|
implAccount.add(current.stats);
|
|
251
|
-
if (isDurablePause(current.
|
|
251
|
+
if (isDurablePause(current.terminal)) {
|
|
252
252
|
return { ...mapNestedSuspend(carryFrozenPosture(withImplAccount(current))), verification: outcome };
|
|
253
253
|
}
|
|
254
|
-
if (current.
|
|
254
|
+
if (current.terminal.kind !== "completed")
|
|
255
255
|
break;
|
|
256
256
|
}
|
|
257
257
|
return { ...carryFrozenPosture(withImplAccount(current)), verification: outcome };
|
|
@@ -259,8 +259,8 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
259
259
|
export async function runWithVerification(runner, implSpec, config = {}, internals) {
|
|
260
260
|
refuseUnhonorableInternals(internals, "door");
|
|
261
261
|
const result = await runner.runTask(implSpec, internals);
|
|
262
|
-
if (isDurablePause(result.
|
|
263
|
-
return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: result.
|
|
262
|
+
if (isDurablePause(result.terminal)) {
|
|
263
|
+
return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: pauseFamily(result.terminal), rounds: 0, findings: [] } };
|
|
264
264
|
}
|
|
265
265
|
refuseUnhonorableInternals(internals, "before_leg");
|
|
266
266
|
return verifyCompleted(runner, result, implSpec, implSpec.objective, config, internals);
|
|
@@ -268,8 +268,8 @@ export async function runWithVerification(runner, implSpec, config = {}, interna
|
|
|
268
268
|
export async function resumeWithVerification(runner, token, outcome, taskConfig, objective, config = {}, internals) {
|
|
269
269
|
refuseUnhonorableInternals(internals, "door");
|
|
270
270
|
const result = await runner.resume(token, outcome, taskConfig, internals);
|
|
271
|
-
if (isDurablePause(result.
|
|
272
|
-
return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: result.
|
|
271
|
+
if (isDurablePause(result.terminal)) {
|
|
272
|
+
return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: pauseFamily(result.terminal), rounds: 0, findings: [] } };
|
|
273
273
|
}
|
|
274
274
|
refuseUnhonorableInternals(internals, "before_leg");
|
|
275
275
|
return verifyCompleted(runner, result, taskConfig, objective, config, internals);
|
package/dist/core/a2a.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { A2A_TASK_STATES } from "./a2a-task-state.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveProtocolHttpHeaders } from "./mcp.js";
|
|
5
|
+
import { describeHttpTransportFailure } from "./mcp-failure.js";
|
|
5
6
|
import { findNamespacePrefixCollision, mintNamespacePrefix, mintNamespacedToolName } from "./protocol-naming.js";
|
|
6
7
|
import { A2A_NAMESPACE } from "./protocol-table.js";
|
|
7
8
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
@@ -47,11 +47,21 @@ import type { PermissionResult } from "./tool-policy.js";
|
|
|
47
47
|
* an instruction rather than hesitation.
|
|
48
48
|
* - `denial_limit_fallback` — the auto-mode classifier reached its denial bound and handed the call
|
|
49
49
|
* to a person; the classifier stands BEFORE this ask, never after it.
|
|
50
|
-
* - `
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* - `shell_gate_tighten` — the gate's own irreversibility tighten raised the ask over a tier the COARSE
|
|
51
|
+
* `shellGate` doctrine installed (a shell command under `shellGate: "classify"`/`"always"`), not an
|
|
52
|
+
* explicit per-tool mark. The gate records that its own tighten spoke; a policy stamping
|
|
53
|
+
* `decisionReason: "safety"` on its own ask does not become one.
|
|
54
|
+
* - `safety_tighten` — the gate's own post-fold tightens over an EXPLICIT fact of the call: the tool's
|
|
55
|
+
* egress mark, its irreversibility mark, a peer-message referral in auto mode, a write onto the
|
|
56
|
+
* write-protected table. Same recording rule as above.
|
|
57
|
+
* - `policy` — everything else: a deployment `ToolPolicy` ask or a mode-default ask. This is the
|
|
58
|
+
* hesitation family — the one an armed classifier exists to resolve. The two tighten words are
|
|
59
|
+
* classifier-eligible too (arming auto mode is the deployment's explicit choice to let the
|
|
60
|
+
* classifier be the explicit ask-resolution for those classes); they are separate words so a deny
|
|
61
|
+
* observer or an approval card can say WHICH engine layer raised the question, which one word
|
|
62
|
+
* could not.
|
|
53
63
|
*/
|
|
54
|
-
export declare const ASK_ORIGINS: readonly ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "policy"];
|
|
64
|
+
export declare const ASK_ORIGINS: readonly ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
55
65
|
export type AskOrigin = (typeof ASK_ORIGINS)[number];
|
|
56
66
|
/** Whether a value is a member of the closed origin set. Exported because the word crosses process
|
|
57
67
|
* boundaries: a host validating one it received must not hand-roll the set. */
|
|
@@ -77,6 +87,10 @@ export interface AskOriginFacts {
|
|
|
77
87
|
* read-back off the decision (a policy can put `decisionReason: "org_rule"` on its own ask; the
|
|
78
88
|
* gate's cell cannot be written by a policy). */
|
|
79
89
|
readonly org: "rule" | "unavailable" | undefined;
|
|
90
|
+
/** Which of the gate's OWN post-fold tightens minted the standing ask, if one did — again the gate's
|
|
91
|
+
* record of its own layer (a tighten replaces an `allow` wholesale, so at most one fires per pass),
|
|
92
|
+
* never a read-back of `decisionReason: "safety"` off a decision a policy could have composed. */
|
|
93
|
+
readonly tightened: "shell_gate" | "safety" | undefined;
|
|
80
94
|
}
|
|
81
95
|
/**
|
|
82
96
|
* The ONE derivation of an ask's origin, from the surviving decision plus the gate's own facts. The
|
|
@@ -89,6 +103,39 @@ export interface AskOriginFacts {
|
|
|
89
103
|
export declare function askOriginOf(decision: Extract<PermissionResult, {
|
|
90
104
|
action: "ask";
|
|
91
105
|
}>, facts: AskOriginFacts): AskOrigin;
|
|
106
|
+
/**
|
|
107
|
+
* Which origins IMPLY the `requiresRealApproval` bit, and WHERE that implication is verified — the
|
|
108
|
+
* consistency table {@link askOriginOf} reads. An ask of a must-carry origin exists so that no automatic
|
|
109
|
+
* lane clears it (the org's asks: "no configuration clears this, a person nods"; the classifier's
|
|
110
|
+
* fallback: no automatic lane may re-clear what the classifier already refused), so the bit must ride
|
|
111
|
+
* it. The bit is NOT derived from this table: the write sites each have their own reason, and a
|
|
112
|
+
* table-derived bit would have to either strip a safety policy's mandate or make every hook ask
|
|
113
|
+
* non-budgetable. The table only refuses the one shape that is a defect: a must-carry origin without
|
|
114
|
+
* the bit — and says where that refusal can honestly happen:
|
|
115
|
+
* - `"at_derivation"` — the origin is derived from a GATE fact only the engine writes (`facts.org`),
|
|
116
|
+
* so a missing bit is an engine defect and {@link askOriginOf} fails loud;
|
|
117
|
+
* - `"at_mint"` — the origin is derived from a decision MEMBER a policy may compose
|
|
118
|
+
* (`denialLimitFallback`: a self-declared one opts the policy's own ask out of automatic
|
|
119
|
+
* resolution, the safe direction, not a defect), so the invariant is pinned at the engine's two
|
|
120
|
+
* mint sites (the gate's fallback re-mint and the inherited station's) rather than thrown at
|
|
121
|
+
* derivation, where a policy's own object could trip it;
|
|
122
|
+
* - `false` — the origin says nothing about the bit: a `policy` ask may carry it (a safety policy's
|
|
123
|
+
* own mandate) or not, and both are legal.
|
|
124
|
+
*/
|
|
125
|
+
export declare const ORIGIN_IMPLIES_REAL_APPROVAL: {
|
|
126
|
+
readonly content_question: false;
|
|
127
|
+
readonly unresolvable: false;
|
|
128
|
+
readonly org_unavailable: "at_derivation";
|
|
129
|
+
readonly org_rule: "at_derivation";
|
|
130
|
+
readonly hook: false;
|
|
131
|
+
readonly ask_rule: false;
|
|
132
|
+
readonly denial_limit_fallback: "at_mint";
|
|
133
|
+
readonly shell_gate_tighten: false;
|
|
134
|
+
readonly safety_tighten: false;
|
|
135
|
+
readonly policy: false;
|
|
136
|
+
};
|
|
137
|
+
/** The fence over the consistency table: `never` while every origin has a row. */
|
|
138
|
+
export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<AskOrigin, keyof typeof ORIGIN_IMPLIES_REAL_APPROVAL>>;
|
|
92
139
|
/**
|
|
93
140
|
* The auto-mode CLASSIFIER's table: may a live classifier verdict resolve an ask of this origin?
|
|
94
141
|
*
|
|
@@ -107,9 +154,13 @@ export declare function askOriginOf(decision: Extract<PermissionResult, {
|
|
|
107
154
|
* whether the person is asked at all, which is the rule's whole content.
|
|
108
155
|
* - `denial_limit_fallback`: the classifier already spoke (its bound is what minted this ask); an
|
|
109
156
|
* allow here would un-say the fallback, a block would count it twice.
|
|
110
|
-
* - `
|
|
111
|
-
*
|
|
112
|
-
*
|
|
157
|
+
* - `shell_gate_tighten` / `safety_tighten`: YES — the engine's own tightens are hesitation the
|
|
158
|
+
* deployment armed the classifier to resolve (the classify doctrine's whole point for the shell
|
|
159
|
+
* tier; the safety marks make the ask NON-BUDGETABLE at the durable park, a different axis from who
|
|
160
|
+
* may answer it live).
|
|
161
|
+
* - `policy`: YES — including a safety policy's `requiresRealApproval` ask (a live verdict is
|
|
162
|
+
* judgment, and arming auto mode is the deployment's explicit choice to let the classifier be the
|
|
163
|
+
* explicit ask-resolution for those classes).
|
|
113
164
|
* Exclusion is skip-not-deny: the ask flows the original chain (park / approver / headless deny) —
|
|
114
165
|
* narrower autonomy, never a lost ask.
|
|
115
166
|
*/
|
|
@@ -121,6 +172,8 @@ declare const CLASSIFIER_MAY_ANSWER: {
|
|
|
121
172
|
readonly hook: false;
|
|
122
173
|
readonly ask_rule: false;
|
|
123
174
|
readonly denial_limit_fallback: false;
|
|
175
|
+
readonly shell_gate_tighten: true;
|
|
176
|
+
readonly safety_tighten: true;
|
|
124
177
|
readonly policy: true;
|
|
125
178
|
};
|
|
126
179
|
/** The fence over the classifier table: `never` while every origin has a row (pinned by a type test). */
|
package/dist/core/ask-origin.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
export const ASK_ORIGINS = ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "policy"];
|
|
1
|
+
export const ASK_ORIGINS = ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
|
|
2
2
|
const ASK_ORIGIN_SET = new Set(ASK_ORIGINS);
|
|
3
3
|
export function isAskOrigin(v) {
|
|
4
4
|
return ASK_ORIGIN_SET.has(v);
|
|
5
5
|
}
|
|
6
6
|
export function askOriginOf(decision, facts) {
|
|
7
|
+
const origin = originLadder(decision, facts);
|
|
8
|
+
if (ORIGIN_IMPLIES_REAL_APPROVAL[origin] === "at_derivation" && decision.requiresRealApproval !== true) {
|
|
9
|
+
throw new Error(`ask origin "${origin}" requires the requiresRealApproval bit on the decision it was derived from, and the decision carries none — an engine mint site dropped the bit`);
|
|
10
|
+
}
|
|
11
|
+
return origin;
|
|
12
|
+
}
|
|
13
|
+
function originLadder(decision, facts) {
|
|
7
14
|
if (facts.contentQuestion)
|
|
8
15
|
return "content_question";
|
|
9
16
|
if (facts.markedUnresolvable)
|
|
@@ -18,8 +25,24 @@ export function askOriginOf(decision, facts) {
|
|
|
18
25
|
return "ask_rule";
|
|
19
26
|
if (decision.denialLimitFallback !== undefined)
|
|
20
27
|
return "denial_limit_fallback";
|
|
28
|
+
if (facts.tightened === "shell_gate")
|
|
29
|
+
return "shell_gate_tighten";
|
|
30
|
+
if (facts.tightened === "safety")
|
|
31
|
+
return "safety_tighten";
|
|
21
32
|
return "policy";
|
|
22
33
|
}
|
|
34
|
+
export const ORIGIN_IMPLIES_REAL_APPROVAL = {
|
|
35
|
+
content_question: false,
|
|
36
|
+
unresolvable: false,
|
|
37
|
+
org_unavailable: "at_derivation",
|
|
38
|
+
org_rule: "at_derivation",
|
|
39
|
+
hook: false,
|
|
40
|
+
ask_rule: false,
|
|
41
|
+
denial_limit_fallback: "at_mint",
|
|
42
|
+
shell_gate_tighten: false,
|
|
43
|
+
safety_tighten: false,
|
|
44
|
+
policy: false,
|
|
45
|
+
};
|
|
23
46
|
const CLASSIFIER_MAY_ANSWER = {
|
|
24
47
|
content_question: false,
|
|
25
48
|
unresolvable: false,
|
|
@@ -28,6 +51,8 @@ const CLASSIFIER_MAY_ANSWER = {
|
|
|
28
51
|
hook: false,
|
|
29
52
|
ask_rule: false,
|
|
30
53
|
denial_limit_fallback: false,
|
|
54
|
+
shell_gate_tighten: true,
|
|
55
|
+
safety_tighten: true,
|
|
31
56
|
policy: true,
|
|
32
57
|
};
|
|
33
58
|
export function classifierMayAnswer(origin) {
|