@sema-agent/core 7.11.2 → 7.13.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 +86 -13
- package/dist/core/ask-origin.d.ts +55 -0
- package/dist/core/ask-origin.js +21 -0
- package/dist/core/auto-mode-arming.d.ts +10 -14
- package/dist/core/auto-mode-arming.js +3 -9
- package/dist/core/auto-mode-defaults.d.ts +0 -2
- package/dist/core/auto-mode-defaults.js +0 -1
- package/dist/core/auto-mode-rebuild.d.ts +6 -13
- package/dist/core/auto-mode-rebuild.js +0 -2
- package/dist/core/auto-mode.d.ts +30 -89
- package/dist/core/auto-mode.js +12 -59
- package/dist/core/checkpoint-store.d.ts +1 -3
- package/dist/core/engine-notice.d.ts +18 -0
- package/dist/core/gate-fold.js +1 -9
- package/dist/core/gate-lanes.d.ts +0 -40
- package/dist/core/gate-lanes.js +18 -26
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +7 -1
- package/dist/core/hooks.js +1 -1
- package/dist/core/runner/assemble-result.d.ts +45 -1
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/compaction-seams.d.ts +42 -0
- package/dist/core/runner/compaction-seams.js +80 -0
- package/dist/core/runner/contracts.d.ts +85 -15
- package/dist/core/runner/denial-limit-arms.d.ts +10 -13
- package/dist/core/runner/denial-limit-arms.js +12 -9
- package/dist/core/runner/gate-exit.js +9 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.js +0 -5
- package/dist/core/runner/prepare-policy-chain.js +3 -2
- package/dist/core/runner/prepare-protocol-tools.js +5 -0
- package/dist/core/runner/prepare-safety-scan.js +11 -16
- package/dist/core/runner/prepare-suspend-saga.d.ts +0 -2
- package/dist/core/runner/prepare-suspend-saga.js +2 -10
- package/dist/core/runner/prepare-task.d.ts +9 -1
- package/dist/core/runner/prepare-task.js +7 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
- package/dist/core/runner/prepare-wiring-manifest.js +1 -8
- package/dist/core/runner/resume-claim.d.ts +2 -2
- package/dist/core/runner/resume-preflight.d.ts +2 -2
- package/dist/core/runner/run-attachment-seats.d.ts +6 -4
- package/dist/core/runner/run-attachment-seats.js +2 -2
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.js +2 -0
- package/dist/core/runner/run-identity-wiring.d.ts +21 -33
- package/dist/core/runner/run-identity-wiring.js +6 -3
- package/dist/core/runner/run-leg.d.ts +108 -0
- package/dist/core/runner/run-leg.js +713 -0
- package/dist/core/runner/run-notification-lane.d.ts +56 -0
- package/dist/core/runner/run-notification-lane.js +128 -0
- package/dist/core/runner/run-reasoning-seat.d.ts +5 -5
- package/dist/core/runner/run-reasoning-seat.js +7 -7
- package/dist/core/runner/run-recovery-lanes.d.ts +4 -17
- package/dist/core/runner/run-recovery-lanes.js +5 -4
- package/dist/core/runner/run-settle-and-teardown.d.ts +113 -0
- package/dist/core/runner/run-settle-and-teardown.js +432 -0
- package/dist/core/runner/run-stop-and-final-verify.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +99 -0
- package/dist/core/runner/run-terminal-adoption.js +200 -0
- package/dist/core/runner/run-turn-boundary.js +5 -4
- package/dist/core/runner/runtask.d.ts +10 -79
- package/dist/core/runner/runtask.js +83 -1557
- package/dist/core/runner/stream-lifecycle-verbs.js +10 -1
- package/dist/core/runner-deps.d.ts +4 -14
- package/dist/core/store-contracts/workflow-journal-store-contract.d.ts +7 -0
- package/dist/core/store-contracts/workflow-journal-store-contract.js +85 -0
- package/dist/core/task-stream.d.ts +10 -1
- package/dist/core/tool-face.d.ts +8 -0
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +42 -96
- package/dist/core/tool-policy.js +1 -11
- package/dist/core/tool-registry.d.ts +11 -3
- package/dist/core/tool-registry.js +7 -1
- package/dist/core/tool-roster.d.ts +26 -0
- package/dist/core/tool-roster.js +38 -7
- package/dist/core/trace.d.ts +6 -7
- package/dist/core/wiring-manifest.d.ts +5 -22
- package/dist/core/wiring-manifest.js +3 -11
- package/dist/core/workflow-journal-store.d.ts +35 -4
- package/dist/core/workflow-journal-store.js +19 -2
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -5
- package/dist/orchestration/workflow.js +2 -0
- package/dist/server/http.js +2 -1
- package/dist/stores/file/workflow-journal-store.d.ts +7 -10
- package/dist/stores/file/workflow-journal-store.js +2 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +21 -11
|
@@ -41,6 +41,9 @@ function assistantText(msg) {
|
|
|
41
41
|
.join("");
|
|
42
42
|
return stripThinkTags(text);
|
|
43
43
|
}
|
|
44
|
+
export function answerSettled(final, continuationCut = false) {
|
|
45
|
+
return final?.stopReason === "stop" && final.partialFinalized !== true && !continuationCut;
|
|
46
|
+
}
|
|
44
47
|
export function terminalCauseOf(final, stats, flags) {
|
|
45
48
|
let terminal;
|
|
46
49
|
let apiFailure;
|
|
@@ -96,7 +99,7 @@ export function terminalCauseOf(final, stats, flags) {
|
|
|
96
99
|
else if (flags.paused) {
|
|
97
100
|
terminal = flags.paused;
|
|
98
101
|
}
|
|
99
|
-
else if ((flags.abortedLive || final?.stopReason === "aborted") && !haltOwnsAbortedFinal) {
|
|
102
|
+
else if ((flags.abortedLive || final?.stopReason === "aborted") && !haltOwnsAbortedFinal && flags.answerSettled !== true) {
|
|
100
103
|
const code = flags.abortedForTimeout ? "limits.max_walltime_exceeded" : flags.abortedForTurns ? "limits.max_turns_exceeded" : undefined;
|
|
101
104
|
terminal = { kind: "failed", ...(code !== undefined ? { code } : {}), message: final?.errorMessage ?? (flags.abortedForTurns ? "max turns exceeded" : "run aborted") };
|
|
102
105
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MaybeCompactOptions } from "../auto-compaction.js";
|
|
2
|
+
import type { TaskSpec } from "../types.js";
|
|
3
|
+
import type { Prepared, RunnerDepsSeat } from "./contracts.js";
|
|
4
|
+
/**
|
|
5
|
+
* design/84 Seam C — the cost-optimization compaction options threaded into BOTH `maybeCompact` call
|
|
6
|
+
* sites (within-task turn boundary + `finish()`). All three fields come from `RunnerDeps` (a trusted
|
|
7
|
+
* FUNCTION seam — never `TaskSpec`, which is serializable/durable-resumable/untrusted-caller). The
|
|
8
|
+
* Runner OWNS the consecutive-reuse counter (`prepared.compactionReuseRef`) so the
|
|
9
|
+
* `maxConsecutiveProviderReuse` drift guard spans the whole task across both sites: it FEEDS the current
|
|
10
|
+
* count in as `consecutiveProviderReuse`, and {@link recordCompactionReuse} updates it from the result.
|
|
11
|
+
* Returns `undefined` when no provider is wired (so the call site spreads nothing → byte-identical to
|
|
12
|
+
* the pre-design/84 behavior).
|
|
13
|
+
*/
|
|
14
|
+
export declare function seamCCompactionOptions(runner: RunnerDepsSeat, prepared: Prepared): Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse"> | undefined;
|
|
15
|
+
/** design/134 §3.2 — resolve the pre/postCompact lifecycle callbacks (whole-slot `spec.hooks ??
|
|
16
|
+
* deps.hooks`, same resolution as the stop hook) and wrap each in a SWALLOW+TRACE shell before
|
|
17
|
+
* threading them into maybeCompact. The wrapper owns the observability half of the R3 MED contract
|
|
18
|
+
* (maybeCompact swallows defensively too, but has no sink): a throwing callback is reported via
|
|
19
|
+
* `onError(phase:"hook")` and treated as absent; a `block` returned under a "forced" trigger is
|
|
20
|
+
* reported as ignored (maybeCompact enforces the ignore — blocking a compaction the provider/trim
|
|
21
|
+
* layer already demanded would kill the run). */
|
|
22
|
+
export declare function compactionHookOptions(runner: RunnerDepsSeat, spec: TaskSpec, sessionId: string, trigger: "auto" | "manual" | "forced",
|
|
23
|
+
/** #281 件A — the leg's identity envelope, spread onto both compaction contexts by the wrappers
|
|
24
|
+
* below (the compaction module builds its contexts below the runner and cannot know the leg;
|
|
25
|
+
* the wrapper is the runner's seam). Every runner call site supplies `prepared.hookIdentity`;
|
|
26
|
+
* optional only so a context built without a leg stays honestly identity-less. */
|
|
27
|
+
identity?: import("../hooks.js").HookInvocationIdentity,
|
|
28
|
+
/** The leg's already-validated seat bound (`Prepared.hookTimeoutMs`) and its abort signal. Optional
|
|
29
|
+
* for the same reason `identity` is — a wrapper built without a prepared leg still works, on the
|
|
30
|
+
* default bound and with no signal. The signal is what keeps a hung compaction hook from holding a
|
|
31
|
+
* CANCELLED run's turn boundary for the whole deadline (the seat's post-abort grace). */
|
|
32
|
+
seatBound?: {
|
|
33
|
+
timeoutMs: number;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}): Pick<MaybeCompactOptions, "trigger" | "preCompact" | "postCompact">;
|
|
36
|
+
/** design/84 Seam C — fold a finished compaction's `reused` flag into the run-scoped consecutive-reuse
|
|
37
|
+
* counter: a reused (provider) summary increments it, a real (LLM) summary resets it to 0. A no-op
|
|
38
|
+
* compaction (`compacted:false`) leaves the counter untouched. No-op when no provider is wired. */
|
|
39
|
+
export declare function recordCompactionReuse(runner: RunnerDepsSeat, prepared: Prepared, comp: {
|
|
40
|
+
compacted: boolean;
|
|
41
|
+
reused?: boolean;
|
|
42
|
+
}): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
|
|
2
|
+
export function seamCCompactionOptions(runner, prepared) {
|
|
3
|
+
if (!runner.deps.summaryProvider)
|
|
4
|
+
return undefined;
|
|
5
|
+
return {
|
|
6
|
+
summaryProvider: runner.deps.summaryProvider,
|
|
7
|
+
onCompaction: runner.deps.onCompaction,
|
|
8
|
+
maxConsecutiveProviderReuse: runner.deps.maxConsecutiveProviderReuse,
|
|
9
|
+
consecutiveProviderReuse: prepared.compactionReuseRef.consecutive,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function compactionHookOptions(runner, spec, sessionId, trigger, identity, seatBound) {
|
|
13
|
+
const hooks = spec.hooks ?? runner.deps.hooks;
|
|
14
|
+
const pre = hooks?.preCompact;
|
|
15
|
+
const post = hooks?.postCompact;
|
|
16
|
+
const seatMs = seatBound?.timeoutMs ?? resolveHookTimeoutMs(hooks?.timeoutMs);
|
|
17
|
+
const seatSignal = seatBound?.signal;
|
|
18
|
+
const withIdentity = (ctx) => identity !== undefined ? { ...ctx, identity } : ctx;
|
|
19
|
+
return {
|
|
20
|
+
trigger,
|
|
21
|
+
...(pre
|
|
22
|
+
? {
|
|
23
|
+
preCompact: async (rawCtx) => {
|
|
24
|
+
const ctx = withIdentity(rawCtx);
|
|
25
|
+
const report = (err) => {
|
|
26
|
+
try {
|
|
27
|
+
runner.deps.onError?.(err, { phase: "hook", sessionId });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
const seat = await runHookSeat("preCompact", { timeoutMs: seatMs, ...(seatSignal !== undefined ? { signal: seatSignal } : {}) }, (sig) => pre.call(hooks, { ...ctx, signal: sig }));
|
|
34
|
+
if (seat.expired) {
|
|
35
|
+
report(hookSeatExpiredError("preCompact", seatMs, seat.cause, `the "${ctx.trigger}" compaction PROCEEDED unblocked and with no additional instructions`));
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const r = seat.value;
|
|
39
|
+
if (r?.block && ctx.trigger === "forced") {
|
|
40
|
+
report(new Error(`a preCompact callback blocked a "forced" compaction — ignored (PTL/trim-pressure compaction is not optional): ${r.block}`));
|
|
41
|
+
}
|
|
42
|
+
return r;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
...(post
|
|
52
|
+
? {
|
|
53
|
+
postCompact: async (rawCtx) => {
|
|
54
|
+
try {
|
|
55
|
+
const seat = await runHookSeat("postCompact", { timeoutMs: seatMs, ...(seatSignal !== undefined ? { signal: seatSignal } : {}) }, (sig) => post.call(hooks, { ...withIdentity(rawCtx), signal: sig }));
|
|
56
|
+
if (seat.expired) {
|
|
57
|
+
try {
|
|
58
|
+
runner.deps.onError?.(hookSeatExpiredError("postCompact", seatMs, seat.cause, "the observation was abandoned; the compaction that already landed is unchanged"), { phase: "hook", sessionId });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
try {
|
|
66
|
+
runner.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function recordCompactionReuse(runner, prepared, comp) {
|
|
77
|
+
if (!runner.deps.summaryProvider || !comp.compacted)
|
|
78
|
+
return;
|
|
79
|
+
prepared.compactionReuseRef.consecutive = comp.reused ? prepared.compactionReuseRef.consecutive + 1 : 0;
|
|
80
|
+
}
|
|
@@ -22,7 +22,7 @@ import type { WorkflowSizeGuideline } from "../../orchestration/workflow-size-gu
|
|
|
22
22
|
import type { ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
|
|
23
23
|
import type { CwdRef, ReadFace } from "../../tools/fs/index.js";
|
|
24
24
|
import type { MaterializedA2a } from "../a2a.js";
|
|
25
|
-
import type { CompactionForkContext, MaybeCompactOptions, RapidRefillState } from "../auto-compaction.js";
|
|
25
|
+
import type { CompactionForkContext, CompactionPhaseDurations, MaybeCompactOptions, RapidRefillState } from "../auto-compaction.js";
|
|
26
26
|
import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
27
27
|
import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
|
|
28
28
|
import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
|
|
@@ -652,6 +652,24 @@ export interface Prepared {
|
|
|
652
652
|
callIssuedAtRef: {
|
|
653
653
|
current?: number;
|
|
654
654
|
};
|
|
655
|
+
/** #677 — was the run's WORK cut by a stop? Two observation points, one record, read by the answer-settled
|
|
656
|
+
* verdict (`answerSettled`) beside the harness's own undrained-input account:
|
|
657
|
+
* - `continuation` — the loop decided to CONTINUE past its last assistant turn and the abort caught it
|
|
658
|
+
* before that continuation dispatched. Written by the loop-trace sink from the loop's own steps: a
|
|
659
|
+
* `continue` step (a drained steer / follow-up, a truncated-output or thinking-only or degenerate-output
|
|
660
|
+
* nudge, a next tool turn) sets it; the terminal step clears it unless the loop exited at its
|
|
661
|
+
* pre-dispatch abort guard (`aborted_before_stream`) — the one exit where `final` is still the PREVIOUS
|
|
662
|
+
* turn's message. A clean "stop" the loop itself had decided was not the end is not a settled answer.
|
|
663
|
+
* - `abortedBeforeTurnEnd` — the run's abort was already in force when a turn ENDED (written by the
|
|
664
|
+
* `turn_end` handler before the turns cap fires its own abort). The ruling's line is the last
|
|
665
|
+
* `turn_end`: a stop that landed before it landed on work — even when the brain ignored the signal
|
|
666
|
+
* and delivered a full "stop" answer through it (the loop then ends at its own decision, so the
|
|
667
|
+
* continuation bit never sets); a stop that landed after it landed on the tail.
|
|
668
|
+
* Always present; both false on a run nothing stopped. */
|
|
669
|
+
workCutRef: {
|
|
670
|
+
continuation: boolean;
|
|
671
|
+
abortedBeforeTurnEnd: boolean;
|
|
672
|
+
};
|
|
655
673
|
/** RB-458 — records the FIRST brain call this run's outer guardrail gave up on (see
|
|
656
674
|
* {@link import("../../brain/timeout.js").withBrainCallGuardrail}). Always present; `timedOut`
|
|
657
675
|
* stays absent unless the guardrail fired. The run loop reads it AFTER the loop settles and gives
|
|
@@ -1778,14 +1796,6 @@ export interface RunInternals {
|
|
|
1778
1796
|
* channel, same posture as every other field here.
|
|
1779
1797
|
*/
|
|
1780
1798
|
sessionReadStates?: SessionReadFileStates;
|
|
1781
|
-
/**
|
|
1782
|
-
* #616 — the Runner's per-session breaker ledger ({@link import("../auto-mode.js").AutoModeBreakerLedger}):
|
|
1783
|
-
* the arming site's `onBreakerOpen` wrap records a trip here, the wiring-manifest phase of a LATER leg of
|
|
1784
|
-
* the same session reads the most recent one onto `autoMode.breaker`. Always set by the Runner's own
|
|
1785
|
-
* prepare call (overriding any caller value, like `sessionReadStates` beside it); absent on a standalone
|
|
1786
|
-
* prepareTask, where no trip is recorded and none is reported. Trusted internals channel.
|
|
1787
|
-
*/
|
|
1788
|
-
autoModeBreakerLedger?: import("../auto-mode.js").AutoModeBreakerLedger;
|
|
1789
1799
|
onTaskNotification?: (notification: TaskNotificationPayload,
|
|
1790
1800
|
/** Injection tier (design/373 — the ladder is LIVE): "next" = the running turn's next boundary
|
|
1791
1801
|
* (arrival order, consecutive frames batch); "later" = the run's would-otherwise-stop seat
|
|
@@ -2340,13 +2350,11 @@ export interface TurnBoundaryDeps {
|
|
|
2340
2350
|
drainManualCompact: (outcome: CompactOutcome) => void;
|
|
2341
2351
|
runnerHooks: {
|
|
2342
2352
|
onError: RunnerDeps["onError"];
|
|
2343
|
-
seamCCompactionOptions: (prepared: Prepared) => Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse"> | undefined;
|
|
2344
|
-
compactionHookOptions: (spec: TaskSpec, sessionId: string, trigger: "auto" | "manual" | "forced") => Pick<MaybeCompactOptions, "trigger" | "preCompact" | "postCompact">;
|
|
2345
|
-
recordCompactionReuse: (prepared: Prepared, comp: {
|
|
2346
|
-
compacted: boolean;
|
|
2347
|
-
reused?: boolean;
|
|
2348
|
-
}) => void;
|
|
2349
2353
|
};
|
|
2354
|
+
/** The Runner's deployment deps, read LIVE ({@link RunnerDepsSeat}): the compaction seams (compaction-seams.ts) the
|
|
2355
|
+
* boundary's compaction pass reaches down for read the summary provider, the hooks slot and the error sink through it
|
|
2356
|
+
* on every pass — the three Runner methods that used to sit on `runnerHooks` as delegates (design/393 S7). */
|
|
2357
|
+
runner: RunnerDepsSeat;
|
|
2350
2358
|
}
|
|
2351
2359
|
/** design/157 B15 尾件 — R5(harness 事件处理器族)的依赖包。全部为 runLocked 内声明顺序早于
|
|
2352
2360
|
* 工厂调用点的 const 稳定引用 + 三个 runLocked 形参;this 面(deps.onError)经 runnerHooks 打包
|
|
@@ -2431,6 +2439,28 @@ export interface RunnerSelfSeat {
|
|
|
2431
2439
|
/** Streaming form of {@link RunnerSelfSeat.resume}: the pre-CAS guards and the CAS run first, then the live stream is returned. */
|
|
2432
2440
|
resumeStream(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, internals?: RunInternals): Promise<TaskStream>;
|
|
2433
2441
|
}
|
|
2442
|
+
/**
|
|
2443
|
+
* design/393 S7 (#675) — the orchestrator's entry (`prepareTask`, prepare-task.ts) as ONE contract: the notification lane's
|
|
2444
|
+
* `prepareTask` seat names it, and the orchestrator pins its own declaration against it (`PrepareTaskIsTheContract`), so a
|
|
2445
|
+
* signature change reds at the declaration — not at the driver's hand-in, and not as a seat spelled a second time. It lives
|
|
2446
|
+
* on the floor because a lane may not name the orchestrator (docs/LAYERING.json: layer 4 sits above the lanes). Positional,
|
|
2447
|
+
* exactly as the entry is declared — the seat carries the orchestrator's function itself, not a lane Input.
|
|
2448
|
+
*/
|
|
2449
|
+
export type PrepareTaskFn = (spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: RunnerSelfSeat, runIdSink?: {
|
|
2450
|
+
runId?: string;
|
|
2451
|
+
}) => Promise<Prepared>;
|
|
2452
|
+
/**
|
|
2453
|
+
* design/393 S7 (#670) — the Result of an INSTALLING phase: a lane whose every product is installed on a seat it borrowed
|
|
2454
|
+
* (the harness's stop gate and recovery chain, the run state's counter groups, the stream's result setter, the claimed
|
|
2455
|
+
* row's registries) hands nothing back, and says so by extending this marker. The marker is machine-read by gate:phase-api
|
|
2456
|
+
* (design/238 R-1's third clause): a Result with no members MUST extend it, a Result with members MAY NOT, and no Result
|
|
2457
|
+
* inherits anything else — so an empty Result is a stated fact about the phase, never an interface someone forgot to
|
|
2458
|
+
* fill, and a phase that starts handing a product back must drop the declaration in the same edit. Six lanes carry it:
|
|
2459
|
+
* the attachment seats, the stop gate, the recovery lanes, the settle and teardown (run-), the preflight and the claim
|
|
2460
|
+
* (resume-).
|
|
2461
|
+
*/
|
|
2462
|
+
export interface InstallingPhaseResult {
|
|
2463
|
+
}
|
|
2434
2464
|
/** The live-task handle `runLocked` publishes once the harness exists (design/47): the harness + abort
|
|
2435
2465
|
* controller, the loop-liveness latch (`ended` flips when the single `harness.prompt` settles; `userInterrupted`
|
|
2436
2466
|
* / `userHalted` are the interrupt and halt verbs' attribution seats), the run's reminder mark, its session and
|
|
@@ -2475,6 +2505,46 @@ export interface ManualCompactRef {
|
|
|
2475
2505
|
emitMooted?: (reason: string) => void;
|
|
2476
2506
|
closed?: boolean;
|
|
2477
2507
|
}
|
|
2508
|
+
/**
|
|
2509
|
+
* design/393 S6 — the end-of-task compaction pass's outcome (`Runner.finish`'s return): what the terminal-adoption lane
|
|
2510
|
+
* hands the settle lane, which reads it for the `compacted` frame, the phase-timings frame and the detector reset. Named
|
|
2511
|
+
* once here so the two lanes and the method spell one type.
|
|
2512
|
+
*/
|
|
2513
|
+
export type EndOfTaskCompaction = {
|
|
2514
|
+
compacted: boolean;
|
|
2515
|
+
tokensBefore?: number;
|
|
2516
|
+
triggerTokens?: number;
|
|
2517
|
+
postTriggerTokens?: number;
|
|
2518
|
+
durationMs?: number;
|
|
2519
|
+
phaseDurations?: CompactionPhaseDurations;
|
|
2520
|
+
firstKeptEntryId?: string;
|
|
2521
|
+
attachedFiles?: Array<{
|
|
2522
|
+
path: string;
|
|
2523
|
+
chars: number;
|
|
2524
|
+
truncated: boolean;
|
|
2525
|
+
}>;
|
|
2526
|
+
modelFallback?: true;
|
|
2527
|
+
fallbackReason?: "window";
|
|
2528
|
+
clampedRatio?: number;
|
|
2529
|
+
clampReason?: "budget" | "tolerance";
|
|
2530
|
+
} | undefined;
|
|
2531
|
+
/**
|
|
2532
|
+
* design/393 S6 — the notification lane's BINDINGS: the run-notification-lane's own four `let`s as ONE seat (getters
|
|
2533
|
+
* and setters over the lane's variables, never a copy). The identity-wiring lane binds `harness` / `sessionId` /
|
|
2534
|
+
* `ident` the moment `prepared` exists (the lane's closures read them by variable — the routing listener, the park
|
|
2535
|
+
* destination, the injection entry); the leg lane flips `live` in its finally, from which point every notification
|
|
2536
|
+
* parks per session for the next run. One seat, four slots; nothing copies a binding out of it.
|
|
2537
|
+
*/
|
|
2538
|
+
export interface NotificationLaneBindings {
|
|
2539
|
+
harness: AgentHarness | undefined;
|
|
2540
|
+
sessionId: string | undefined;
|
|
2541
|
+
ident: () => {
|
|
2542
|
+
eventId?: string;
|
|
2543
|
+
parentToolCallId?: string;
|
|
2544
|
+
sourceTaskId?: string;
|
|
2545
|
+
};
|
|
2546
|
+
live: boolean;
|
|
2547
|
+
}
|
|
2478
2548
|
/** design/144 §2 — the `notify()` bridge: `runLocked` binds `inject` the moment the task-notification lane exists. */
|
|
2479
2549
|
export interface NotifyRef {
|
|
2480
2550
|
inject?: (n: TaskNotificationPayload, opts?: {
|
|
@@ -32,8 +32,7 @@ export interface InheritedAutoMode {
|
|
|
32
32
|
* recorded recipe — `rebuildAutoModeDecider` hands back a decider and its arming, nothing live)
|
|
33
33
|
* carries no denial tracker, and an entry with a decider but no tracker would count nothing: the
|
|
34
34
|
* rebuilt classifier could block without bound on the redeemed leg, the exact gap the limit closes.
|
|
35
|
-
* Attach a FRESH tracker (a fresh COUNT is the documented cross-process semantics
|
|
36
|
-
* restarts the same way) under the ancestor's recorded BOUNDS tightened by this deployment's own
|
|
35
|
+
* Attach a FRESH tracker (a fresh COUNT is the documented cross-process semantics) under the ancestor's recorded BOUNDS tightened by this deployment's own
|
|
37
36
|
* (#556, `tightenDenialLimit` — the same rule and the same call the arming fold makes, so the
|
|
38
37
|
* recipe's account of the criteria and the tracker's actual bounds cannot drift). Before #556 the
|
|
39
38
|
* bounds came from this deployment alone, so an ancestor that allowed three consecutive blocks
|
|
@@ -84,8 +83,8 @@ export type InheritedAsk = Extract<PermissionResult, {
|
|
|
84
83
|
}>;
|
|
85
84
|
/** The members of a surviving ask that ride onto the approval request with a COMPUTED value — what the
|
|
86
85
|
* four request mint stations (the gate's own and the three inherited-lane ones) spread after the seats
|
|
87
|
-
* they spell themselves: the ask's origin word, its denial-limit fallback and
|
|
88
|
-
* fact (#616). */
|
|
86
|
+
* they spell themselves: the ask's origin word, its denial-limit fallback and a policy-declared
|
|
87
|
+
* classifier-unavailable fact (#616; the engine's own fact rides the DENY since #661). */
|
|
89
88
|
export interface AskRequestCarry {
|
|
90
89
|
origin?: AskOrigin;
|
|
91
90
|
denialLimitFallback?: DenialLimitFallback;
|
|
@@ -117,18 +116,15 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
117
116
|
kind: "deny";
|
|
118
117
|
result: PermissionResult;
|
|
119
118
|
}
|
|
120
|
-
/** Proceed to the frozen approver with `ask`: the incoming ask unchanged (classifier
|
|
121
|
-
*
|
|
119
|
+
/** Proceed to the frozen approver with `ask`: the incoming ask unchanged (classifier excluded or not
|
|
120
|
+
* armed), or — at the bound — the fallback ask minted from it (`mintedHere`).
|
|
122
121
|
* `fallback` is the member riding the ask either way (an incoming one is a deeper ancestor's).
|
|
123
122
|
* `origin` is the ask's word under the station facts, threaded to the request mint: on the two
|
|
124
123
|
* unchanged-ask branches it is the SAME read the exclusion judged by (a caller-owned decision with a
|
|
125
124
|
* stateful accessor cannot answer the exclusion with one word and the card with another); on the
|
|
126
125
|
* bound branch it is derived from the re-spoken ask this station minted (its own object). */
|
|
127
|
-
/** `ask` is the incoming ask unchanged
|
|
128
|
-
* carrying `classifierUnavailable: { cause }` (#
|
|
129
|
-
* wrapper arms hand back to the gate when the frozen approver answers unavailable (the inherited-unavailable
|
|
130
|
-
* float), and the gate's own park carry reads the fact off the decision it parks — a side member on this
|
|
131
|
-
* judgment would have been lost on that route (codex r1). The request mint reads the same object. */
|
|
126
|
+
/** `ask` is the incoming ask unchanged (the caller's own object); an UNAVAILABLE round never reaches this arm —
|
|
127
|
+
* it is a `deny` carrying `classifierUnavailable: { cause }` (#661). */
|
|
132
128
|
| {
|
|
133
129
|
kind: "resolve";
|
|
134
130
|
ask: A;
|
|
@@ -142,8 +138,9 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
142
138
|
* auto-allow (and the streak resets); `block` below the bound ⇒ deny, the exact verdict the ancestor's
|
|
143
139
|
* gate would have produced; `block` AT the bound ⇒ the ask is re-spoken in the classifier's voice as
|
|
144
140
|
* the fallback (`decisionReason:"classifier"`, `requiresRealApproval`, the member carrying counts +
|
|
145
|
-
* window) for the arm to resolve at the frozen approver;
|
|
146
|
-
*
|
|
141
|
+
* window) for the arm to resolve at the frozen approver; `parse_error` ⇒ the same block path with the CC
|
|
142
|
+
* parse-failure sentence (counted); `unavailable` ⇒ a DENY that says so, carrying the fact (#661, CC
|
|
143
|
+
* 2.1.250 form); excluded / no classifier ⇒ the incoming ask, unchanged. The member may ALREADY be on the incoming
|
|
147
144
|
* ask (a deeper ancestor's fallback, minted inside the nested fold — the case the exclusion names):
|
|
148
145
|
* it rides to the approver and the headless arm unchanged, whichever tracker minted it; only a
|
|
149
146
|
* tracker that counted THIS streak is reset by the person's later allow (`mintedHere`).
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
|
|
1
|
+
import { CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, classifierUnavailableDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
|
|
2
2
|
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
|
|
3
3
|
import { deliverEngineNotice } from "../types.js";
|
|
4
4
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
5
|
-
import { askOriginOf, classifierMayAnswer } from "../ask-origin.js";
|
|
5
|
+
import { askOriginOf, classifierMayAnswer, probeMandatedAsk } from "../ask-origin.js";
|
|
6
6
|
export function attachRebuiltDenialTrackers(entries, denialLimit) {
|
|
7
7
|
if (entries === undefined)
|
|
8
8
|
return undefined;
|
|
@@ -76,8 +76,9 @@ export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
|
76
76
|
export async function judgeInheritedClassifier(opts) {
|
|
77
77
|
const { autoMode, ask, req } = opts;
|
|
78
78
|
const origin = askOriginOf(ask, INHERITED_STATION_FACTS);
|
|
79
|
-
if (autoMode === undefined || !classifierMayAnswer(origin))
|
|
79
|
+
if (autoMode === undefined || !classifierMayAnswer(origin) || probeMandatedAsk(ask)) {
|
|
80
80
|
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
81
|
+
}
|
|
81
82
|
const verdict = await autoMode.decider
|
|
82
83
|
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
|
|
83
84
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -85,19 +86,21 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
85
86
|
autoMode.denialTracking?.recordAllow();
|
|
86
87
|
return { kind: "allow" };
|
|
87
88
|
}
|
|
88
|
-
if (verdict.kind
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if (verdict.kind === "unavailable") {
|
|
90
|
+
return {
|
|
91
|
+
kind: "deny",
|
|
92
|
+
result: { action: "deny", message: classifierUnavailableDenyMessage(req.toolName, verdict.cause), decisionReason: "classifier", classifierUnavailable: { cause: verdict.cause } },
|
|
93
|
+
};
|
|
91
94
|
}
|
|
92
|
-
const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
|
|
93
|
-
const category = verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
95
|
+
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE;
|
|
96
|
+
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
94
97
|
const tracked = autoMode.denialTracking?.recordBlock();
|
|
95
98
|
if (tracked?.limitReached !== true) {
|
|
96
99
|
return {
|
|
97
100
|
kind: "deny",
|
|
98
101
|
result: {
|
|
99
102
|
action: "deny",
|
|
100
|
-
message: `auto-mode classifier blocked ${opts.subject} at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
|
|
103
|
+
message: verdict.kind === "block" ? `auto-mode classifier blocked ${opts.subject} at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}` : `${reason} (${opts.subject}, at an inherited ancestor layer)`,
|
|
101
104
|
decisionReason: "classifier",
|
|
102
105
|
},
|
|
103
106
|
};
|
|
@@ -103,7 +103,15 @@ export async function exitGate(pass) {
|
|
|
103
103
|
if (pass.deniedBy === undefined)
|
|
104
104
|
throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
|
|
105
105
|
const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
|
|
106
|
-
await notifyPermissionDenied({
|
|
106
|
+
await notifyPermissionDenied({
|
|
107
|
+
toolName,
|
|
108
|
+
input: cloneObserverInput(pass.currentInput),
|
|
109
|
+
toolCallId,
|
|
110
|
+
reason: denyReason,
|
|
111
|
+
gate,
|
|
112
|
+
...(input.identity !== undefined ? { identity: input.identity } : {}),
|
|
113
|
+
...(pass.decision.action === "deny" && pass.decision.classifierUnavailable !== undefined ? { classifierUnavailable: { cause: pass.decision.classifierUnavailable.cause } } : {}),
|
|
114
|
+
});
|
|
107
115
|
return {
|
|
108
116
|
block: true,
|
|
109
117
|
reason: formatHookFeedback(denyReason, input.reminderMark),
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* silence the ungated-write warning for deployments that wired no policy at all.
|
|
27
27
|
*/
|
|
28
28
|
import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
|
|
29
|
-
import { persistedRuleMandateOf
|
|
29
|
+
import { persistedRuleMandateOf } from "../ask-origin.js";
|
|
30
|
+
import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
|
|
30
31
|
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
31
32
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
32
33
|
import type { PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { persistedRuleMandateOf } from "../
|
|
1
|
+
import { persistedRuleMandateOf } from "../ask-origin.js";
|
|
2
|
+
import {} from "../hooks.js";
|
|
2
3
|
import { adjudicatePersistedPathRules, adjudicatePersistedRules, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
3
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
4
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
@@ -49,7 +49,7 @@ export interface PrepareCapsAndWorkflowInput {
|
|
|
49
49
|
deps: RunnerDeps;
|
|
50
50
|
/** borrowed-readonly — the trusted spawn-side channel; the Workflow mount forwards `rootSessionId`, `placementRoot`,
|
|
51
51
|
* `onTaskNotification`, `workflowDepth`, and the capture-floor getter reads `memoryCaptureAncestors`. */
|
|
52
|
-
internals: Pick<RunInternals, "rootSessionId" | "placementRoot" | "onTaskNotification" | "workflowDepth" | "memoryCaptureAncestors" | "workflowParkedResume"
|
|
52
|
+
internals: Pick<RunInternals, "rootSessionId" | "placementRoot" | "onTaskNotification" | "workflowDepth" | "memoryCaptureAncestors" | "workflowParkedResume"> | undefined;
|
|
53
53
|
/** borrowed-readonly — the acquired session; the classifier leg rebuilds its transcript window from `buildContext`. */
|
|
54
54
|
session: Pick<StoredSession, "buildContext">;
|
|
55
55
|
/** borrowed-readonly — the acquired session id (operator-line context, the refusal codes' phase record). */
|
|
@@ -195,11 +195,6 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
195
195
|
autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
|
|
196
196
|
autoModeDecider = createAutoModeDecider({
|
|
197
197
|
...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
|
|
198
|
-
...(am.failureThreshold !== undefined ? { failureThreshold: am.failureThreshold } : {}),
|
|
199
|
-
onBreakerOpen: (info) => {
|
|
200
|
-
internals?.autoModeBreakerLedger?.record(sessionId, { openedAtMs: Date.now(), lastCause: info.lastCause, failures: info.consecutiveFailures, runId });
|
|
201
|
-
am.onBreakerOpen?.(info);
|
|
202
|
-
},
|
|
203
198
|
onClassified: (info) => emitTrace(deps.tracer, () => ({
|
|
204
199
|
kind: "auto_mode.classified",
|
|
205
200
|
version: 1,
|
|
@@ -10,6 +10,7 @@ import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
|
|
|
10
10
|
import { NAMESPACED_NAME_SHAPES, protocolOf } from "../protocol-table.js";
|
|
11
11
|
import { emitTrace } from "../trace.js";
|
|
12
12
|
import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
13
|
+
import { probeMandatedAsk } from "../ask-origin.js";
|
|
13
14
|
import { headlessDenyAtFold, headlessDenyAtRecheck, inheritedAskCarry, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
|
|
14
15
|
import { askGrantShapeOf } from "./inherited-ask-grants.js";
|
|
15
16
|
import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
|
|
@@ -374,7 +375,7 @@ export async function preparePolicyChain(input) {
|
|
|
374
375
|
if (judged.kind === "deny")
|
|
375
376
|
return judged.result;
|
|
376
377
|
const { ask: inheritedAsk, fallback, mintedHere } = judged;
|
|
377
|
-
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
|
|
378
|
+
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
|
|
378
379
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
379
380
|
return { action: "allow" };
|
|
380
381
|
}
|
|
@@ -454,7 +455,7 @@ export async function preparePolicyChain(input) {
|
|
|
454
455
|
if (judged.kind === "deny")
|
|
455
456
|
return judged.result;
|
|
456
457
|
const { ask: inheritedAsk, fallback, mintedHere } = judged;
|
|
457
|
-
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
|
|
458
|
+
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
|
|
458
459
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
459
460
|
return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
|
|
460
461
|
}
|
|
@@ -3,12 +3,17 @@ import { materializeMcpTools } from "../mcp.js";
|
|
|
3
3
|
import { materializeA2aTools } from "../a2a.js";
|
|
4
4
|
import { deliverEngineNotice } from "../types.js";
|
|
5
5
|
import { RosterBuilder } from "../tool-roster.js";
|
|
6
|
+
import { TOOL_WIRE_NAME_MAX_CHARS } from "../tool-face.js";
|
|
6
7
|
import { REFRESH_MCP_TOOLS_TOOL_NAME, toolFace } from "../tool-catalog-entries.js";
|
|
7
8
|
import { MCP_NAMESPACE, A2A_NAMESPACE } from "../protocol-table.js";
|
|
8
9
|
import { mintNamespacePrefix } from "../protocol-naming.js";
|
|
9
10
|
import { applyMcpToolFaces } from "./tool-face-overlay.js";
|
|
10
11
|
export async function prepareProtocolTools(input) {
|
|
11
12
|
const { lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback } = input;
|
|
13
|
+
const overPeer = (lockedPreflight.mcp ?? []).find((s) => s.name.length > TOOL_WIRE_NAME_MAX_CHARS);
|
|
14
|
+
if (overPeer !== undefined) {
|
|
15
|
+
throw Object.assign(new Error(`MCP server name ${JSON.stringify(overPeer.name.slice(0, 40) + "…")} is ${overPeer.name.length} characters; a peer rides every roster row of its tools and the roster's bound on a name is ${TOOL_WIRE_NAME_MAX_CHARS}.`), { code: "config.tool_name_too_long" });
|
|
16
|
+
}
|
|
12
17
|
const mcp = lockedPreflight.mcp?.length
|
|
13
18
|
? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
|
|
14
19
|
: { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js";
|
|
2
2
|
import { engineCardTypes, handBandEffects } from "../tool-registry.js";
|
|
3
|
-
import {
|
|
4
|
-
import { callerCardIdOf, offendingResultCard, schemaKeyBoundProblem } from "../tool-roster.js";
|
|
3
|
+
import { callerCardIdOf, offendingResultCard, rosterMemberBoundProblem, schemaKeyBoundProblem } from "../tool-roster.js";
|
|
5
4
|
import { toolFaceProblem } from "./tool-face-overlay.js";
|
|
6
5
|
import { OFFLOAD_TOOL_NAME } from "../tool-result-store.js";
|
|
7
6
|
import { PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
|
|
@@ -16,23 +15,19 @@ export function prepareSafetyScan(input) {
|
|
|
16
15
|
const axisExplicitNegatives = new Map();
|
|
17
16
|
const reversibilityProbes = new Map();
|
|
18
17
|
for (const t of spec.tools ?? []) {
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
e
|
|
18
|
+
{
|
|
19
|
+
const bound = rosterMemberBoundProblem({
|
|
20
|
+
name: t.name,
|
|
21
|
+
aliases: t.aliases,
|
|
22
|
+
...(t.contract !== undefined ? { contractId: t.contract.contractId, implementationRevision: t.contract.implementationRevision } : {}),
|
|
23
|
+
...(typeof t.modelGate === "string" ? { modelGate: t.modelGate } : {}),
|
|
24
|
+
});
|
|
25
|
+
if (bound !== undefined) {
|
|
26
|
+
const e = new Error(`Tool "${t.name.slice(0, 40)}" cannot ride a roster row: ${bound.message}.`);
|
|
27
|
+
e.code = bound.code;
|
|
28
28
|
throw e;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
if (t.contract !== undefined && (t.contract.contractId.length > TOOL_CONTRACT_MAX_CHARS || t.contract.implementationRevision.length > TOOL_CONTRACT_MAX_CHARS)) {
|
|
32
|
-
const e = new Error(`Tool "${t.name}" declares a contract member over ${TOOL_CONTRACT_MAX_CHARS} characters (contractId ${t.contract.contractId.length}, implementationRevision ${t.contract.implementationRevision.length}) — the roster's bound.`);
|
|
33
|
-
e.code = "config.tool_contract_too_long";
|
|
34
|
-
throw e;
|
|
35
|
-
}
|
|
36
31
|
{
|
|
37
32
|
const keyProblem = schemaKeyBoundProblem(t.parameters);
|
|
38
33
|
if (keyProblem !== undefined) {
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* the boundary parks and the park closure key off `saga`'s presence, so the orchestrator grows no branch.
|
|
18
18
|
*/
|
|
19
19
|
import type { AgentHarness, ExecutionEnv } from "../../internal/harness.js";
|
|
20
|
-
import type { AutoModeDecider } from "../auto-mode.js";
|
|
21
20
|
import type { OwnOrgAdmissionVerdict } from "../memory-admission.js";
|
|
22
21
|
import type { RemoteExecutionEnv, SnapshotId } from "../remote-env.js";
|
|
23
22
|
import type { CheckpointStore, SerializedCheckpointState } from "../checkpoint-store.js";
|
|
@@ -128,7 +127,6 @@ export interface PrepareSuspendSagaInput {
|
|
|
128
127
|
/** borrowed-readonly — the auto-mode intent (seat ∨ live ∨ seed), carried while the latch is healthy. */
|
|
129
128
|
autoModeIntent: boolean;
|
|
130
129
|
/** borrowed-readonly — the auto-mode decider, or undefined (the latch-health reading only). */
|
|
131
|
-
autoModeDecider: AutoModeDecider | undefined;
|
|
132
130
|
/** borrowed-readonly — the inherited admitted org scopes, or undefined (copied onto the row). */
|
|
133
131
|
inheritedAdmittedOrgScopes: readonly string[] | undefined;
|
|
134
132
|
/** borrowed-readonly — this leg's OWN org verdict seat; `current` is read at mint time. Not written here. */
|
|
@@ -77,16 +77,8 @@ export async function compensateUnparkedPause(remoteEnv, snapshotId, io) {
|
|
|
77
77
|
function stampPlacementRootSessionId(placementRootResolved) {
|
|
78
78
|
return placementValueOrAbsent(placementRootResolved);
|
|
79
79
|
}
|
|
80
|
-
function autoModeLatchHealthy(d) {
|
|
81
|
-
try {
|
|
82
|
-
return d.breakerOpen() !== true && typeof d.consecutiveFailures === "function" && d.consecutiveFailures() === 0;
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
80
|
export function prepareSuspendSaga(input) {
|
|
89
|
-
const { gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, inheritedParentConstraints, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, autoModeIntent,
|
|
81
|
+
const { gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, inheritedParentConstraints, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, autoModeIntent, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter } = input;
|
|
90
82
|
if (!gateMachineryActive)
|
|
91
83
|
return { saga: undefined };
|
|
92
84
|
const inFlightSpendMicroUsd = () => {
|
|
@@ -140,7 +132,7 @@ export function prepareSuspendSaga(input) {
|
|
|
140
132
|
const constraintDigest = (inheritedParentConstraints?.length ?? 0) > 0
|
|
141
133
|
? constraintChainDigest(constraintChain)
|
|
142
134
|
: seedInheritedGate?.constraintDigest;
|
|
143
|
-
const autoModeIntentCarried = autoModeIntent
|
|
135
|
+
const autoModeIntentCarried = autoModeIntent;
|
|
144
136
|
return inheritedAncestorRules !== undefined ||
|
|
145
137
|
inheritedShellGate !== undefined ||
|
|
146
138
|
autoModeIntentCarried ||
|
|
@@ -8,7 +8,7 @@ export { mcpManifestEntries } from "./prepare-wiring-manifest.js";
|
|
|
8
8
|
export { __resetMaterializeEnvAnnouncements } from "./prepare-tool-disclosure-mount.js";
|
|
9
9
|
export { fileHistoryFilesystemIdentity, resolveFileHistoryScope } from "./prepare-file-history.js";
|
|
10
10
|
import type { RunnerSelfSeat } from "./contracts.js";
|
|
11
|
-
import type { Prepared, PrepareResume, RunInternals } from "./contracts.js";
|
|
11
|
+
import type { Prepared, PrepareResume, PrepareTaskFn, RunInternals } from "./contracts.js";
|
|
12
12
|
export type { FileHistoryBoundarySeat, InheritedGate, Prepared, PreparedMicroCompact, PrepareResume, ResolvedWorkspace, RunInternals, UsageGovernance } from "./contracts.js";
|
|
13
13
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
14
14
|
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
@@ -114,3 +114,11 @@ runnerSelf?: RunnerSelfSeat,
|
|
|
114
114
|
runIdSink?: {
|
|
115
115
|
runId?: string;
|
|
116
116
|
}): Promise<Prepared>;
|
|
117
|
+
/** design/393 S7 (#675) — the entry IS the contract the notification lane's seat names ({@link PrepareTaskFn}, contracts.ts):
|
|
118
|
+
* a signature change reds HERE, at the declaration, as `false` failing the `true` bound. IDENTITY, not assignability (review
|
|
119
|
+
* round 1): the parameter tuple (arity, optionality, each type) and the return type must be the SAME type in both directions —
|
|
120
|
+
* one-way `extends` let a dropped trailing parameter, an added optional one or a narrowed return slip through. Type-level only —
|
|
121
|
+
* nothing is emitted. */
|
|
122
|
+
type Holds<T extends true> = T;
|
|
123
|
+
type Same<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
124
|
+
export type PrepareTaskIsTheContract = Holds<Same<Parameters<typeof prepareTask>, Parameters<PrepareTaskFn>>> & Holds<Same<ReturnType<typeof prepareTask>, ReturnType<PrepareTaskFn>>> & Holds<Same<ThisParameterType<typeof prepareTask>, ThisParameterType<PrepareTaskFn>>> & Holds<Same<typeof prepareTask, PrepareTaskFn>>;
|