@sema-agent/core 7.5.0 → 7.5.2
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 +44 -0
- package/dist/core/auto-mode.d.ts +9 -0
- package/dist/core/auto-mode.js +11 -0
- package/dist/core/checkpoint-store.js +5 -1
- 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/checkpoint-scope.d.ts +44 -0
- package/dist/core/runner/checkpoint-scope.js +7 -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 +1990 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +24 -1
- package/dist/core/runner/denial-limit-arms.js +21 -0
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -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 +108 -0
- package/dist/core/runner/park-commit.js +32 -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-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +110 -0
- package/dist/core/runner/prepare-ask-lane.js +133 -0
- package/dist/core/runner/prepare-boundary-parks.d.ts +105 -0
- package/dist/core/runner/prepare-boundary-parks.js +169 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- 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-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +177 -0
- package/dist/core/runner/prepare-gate-stations.js +290 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +9 -11
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- 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 +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-park-ask.d.ts +164 -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 +119 -0
- package/dist/core/runner/prepare-project-context.js +148 -0
- 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 +119 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +179 -0
- package/dist/core/runner/prepare-question-face.d.ts +101 -0
- package/dist/core/runner/prepare-question-face.js +84 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -7
- 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 +14 -1976
- package/dist/core/runner/prepare-task.js +182 -5162
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- 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 +192 -0
- package/dist/core/runner/prepare-wiring-manifest.js +247 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -56
- package/dist/core/runner/prepare-workspace-restore.js +4 -38
- 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/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/session.d.ts +12 -0
- package/dist/core/session.js +3 -0
- package/dist/core/tool-policy.d.ts +16 -0
- package/dist/core/tool-policy.js +3 -0
- package/dist/core/types.d.ts +2 -2
- package/dist/core/write-protect.js +3 -2
- package/package.json +6 -2
- /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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/390 §1.2 M19 (ask lane) — prepareTask's IN-STREAM ASK LANE, verbatim from the driver's gate-machinery block:
|
|
3
|
+
* the per-call signal composition bound to this task's run signal, the abort-bound `adjudicate` wrapper (the budget
|
|
4
|
+
* snapshot and the live tracked cwd stamped onto every request), the approval-preview projection over the live roster,
|
|
5
|
+
* the `resolveAskBound` mint (the inherited-unavailable intercept, the duplicate-frame grant reuse, the human-review
|
|
6
|
+
* ledger, the bare-human-rejection record), the pre-wrapped deny observer and the hook-crash notifier. The three
|
|
7
|
+
* module-scope helpers only this segment called moved with it (the deny-observer factory, the preview sanitizer and
|
|
8
|
+
* resolver). The interface is the dependency list the segment had implicitly (design/238 R-1) — the closures below
|
|
9
|
+
* used to capture every seat here off the driver's scope.
|
|
10
|
+
*
|
|
11
|
+
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (every await lives inside the closures
|
|
12
|
+
* it builds, which run per tool call), so the phase adds no yield between the wiring-manifest station before it and
|
|
13
|
+
* the suspend saga after it (design/390 §1.5: a stretch with no await is extracted as a sync function). Read-stability
|
|
14
|
+
* of the host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
|
|
15
|
+
*
|
|
16
|
+
* The lane exists exactly when the wiring-manifest phase's `gateMachineryActive` holds — the driver's `if` around the
|
|
17
|
+
* whole block became this phase's one early return (the block's other four phases key off the lane's presence), so
|
|
18
|
+
* the orchestrator grows no branch.
|
|
19
|
+
*/
|
|
20
|
+
import type { AgentTool } from "../../internal/harness.js";
|
|
21
|
+
import type { AutoModeDenialTracker } from "../auto-mode.js";
|
|
22
|
+
import { type Hooks } from "../hooks.js";
|
|
23
|
+
import { type OnAsk, type ToolCallRequest, type ToolPolicy } from "../tool-policy.js";
|
|
24
|
+
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
25
|
+
import type { CwdRef } from "../../tools/fs/fs-shared.js";
|
|
26
|
+
import type { AskLane, Prepared } from "./contracts.js";
|
|
27
|
+
import { consumeInheritedAskGrant } from "./inherited-ask-grants.js";
|
|
28
|
+
import type { createRuleOffersOf } from "./permission-rule-lanes.js";
|
|
29
|
+
export interface PrepareAskLaneInput {
|
|
30
|
+
/** borrowed-readonly — the wiring-manifest phase's ONE activation predicate; false ⇒ this phase builds nothing and
|
|
31
|
+
* returns an absent lane. */
|
|
32
|
+
gateMachineryActive: boolean;
|
|
33
|
+
/** borrowed-readonly — prepare's abort seat; only `.signal` is read (composed into every per-call signal and bound
|
|
34
|
+
* onto the deny observer). Never aborted here. */
|
|
35
|
+
abortController: {
|
|
36
|
+
readonly signal: AbortSignal;
|
|
37
|
+
};
|
|
38
|
+
/** borrowed-readonly — the policy chain's effective policy, or undefined (then no `adjudicate` wrapper is built and
|
|
39
|
+
* the gate reads a missing policy as allow). Called at RUN time, per tool call, through the abort-bound wrapper. */
|
|
40
|
+
effectivePolicy: ToolPolicy | undefined;
|
|
41
|
+
/** borrowed-readonly — the per-leg-immutable durable budget snapshot stamped onto EVERY adjudicated request. Same
|
|
42
|
+
* object every call; the policy cannot write it back. */
|
|
43
|
+
budgetSnapshot: NonNullable<ToolCallRequest["budget"]>;
|
|
44
|
+
/** borrowed-readonly — the hand's live tracked cwd, or undefined (hands-less). Read per call at RUN time (`current`),
|
|
45
|
+
* never captured: the fs hand tools resolve their relative paths against this very ref. Not written here. */
|
|
46
|
+
handsCwdRef: CwdRef | undefined;
|
|
47
|
+
/** borrowed-readonly — the shared mount roster; read at RUN time by the approval-preview projection (alias-aware
|
|
48
|
+
* lookup over whatever the roster holds when the ask is minted). Never mutated here. */
|
|
49
|
+
tools: AgentTool[];
|
|
50
|
+
/** borrowed-mutable — the inherited-unavailable marker set. Writer here: `resolveAskBound` consumes (`delete`) a
|
|
51
|
+
* marked call at its fail-closed intercept; the policy-chain phase records, the gate station sweeps. */
|
|
52
|
+
inheritedUnavailableAsks: Set<string>;
|
|
53
|
+
/** borrowed-mutable — the duplicate-frame ask-grant record. Writer here: the grant-reuse consume half
|
|
54
|
+
* (`consumeInheritedAskGrant`); the policy-chain phase records, the gate station sweeps. */
|
|
55
|
+
inheritedAskGrants: Parameters<typeof consumeInheritedAskGrant>[0];
|
|
56
|
+
/** borrowed-readonly — the run's own approver seat (spec over deps), or undefined: the resolve target, the
|
|
57
|
+
* live-approver test the carry mint reads, and the grant-reuse identity law's approver. */
|
|
58
|
+
onAsk: OnAsk | undefined;
|
|
59
|
+
/** borrowed-mutable — the per-task human-review accumulator (`Prepared.humanReviewRef`). Writers here: every
|
|
60
|
+
* synchronous resolve that a person actually judged (count / totalWaitMs / gates push); runtask adds the durable
|
|
61
|
+
* resume latency. */
|
|
62
|
+
humanReviewRef: Prepared["humanReviewRef"];
|
|
63
|
+
/** borrowed-mutable — the bare-human-rejection sideband. Writer here: a `settledBy:"human"` deny with no note
|
|
64
|
+
* (`add`); the gate station judges and sweeps it. */
|
|
65
|
+
humanBareRejections: Set<string>;
|
|
66
|
+
/** borrowed-readonly — the injectable wall clock every human-time observable reads (`Prepared.now`). */
|
|
67
|
+
now: () => number;
|
|
68
|
+
/** borrowed-readonly — the policy-chain phase's rule-offer factory (the suggestion members the ask mint threads). */
|
|
69
|
+
ruleOffersOf: ReturnType<typeof createRuleOffersOf>;
|
|
70
|
+
/** borrowed-readonly — the policy-chain phase's Runner-filled source identity (one factory, every ask mint site). */
|
|
71
|
+
askSourceIdentity: () => {
|
|
72
|
+
principal?: string;
|
|
73
|
+
sourceTaskId?: string;
|
|
74
|
+
fromSubagent?: true;
|
|
75
|
+
sourceAgentName?: string;
|
|
76
|
+
isDelegatedChild?: true;
|
|
77
|
+
};
|
|
78
|
+
/** borrowed-readonly — the policy-chain phase's per-tool risk-axes projection (`riskAxes` on the ask). */
|
|
79
|
+
riskAxesOf: (toolName: string) => {
|
|
80
|
+
riskAxes?: {
|
|
81
|
+
irreversible?: boolean;
|
|
82
|
+
egress?: boolean;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/** borrowed-mutable — the auto-mode denial tracker, or undefined. Writer here: the carry mint arms the auto-deny
|
|
86
|
+
* window on it iff the seat about to be called is live (the tracker's one writer). */
|
|
87
|
+
autoModeDenialTracking: AutoModeDenialTracker | undefined;
|
|
88
|
+
/** borrowed-readonly — the REBOUND spec; only `taskId` is read (the late-settlement observer's tag). */
|
|
89
|
+
spec: Pick<TaskSpec, "taskId">;
|
|
90
|
+
/** borrowed-readonly — the deployment seats this lane reports on, as a Pick over the SAME `deps` object (receiver
|
|
91
|
+
* preserved for `deps.onError?.()`): the late-settlement observer's notice/error sinks and the hook-crash notifier. */
|
|
92
|
+
deps: Pick<RunnerDeps, "onNotice" | "onError">;
|
|
93
|
+
/** borrowed-readonly — the acquired session id (the observer's tag, the hook-phase error tag). */
|
|
94
|
+
sessionId: string;
|
|
95
|
+
/** borrowed-readonly — the run's invocation id (the observer's tag). */
|
|
96
|
+
runId: string;
|
|
97
|
+
/** borrowed-readonly — the resolved hook record; only `permissionDenied` is read (the deny observer's seat). */
|
|
98
|
+
hooks: Hooks | undefined;
|
|
99
|
+
/** borrowed-readonly — the validated per-prepare hook seat bound (the deny observer's timeout). */
|
|
100
|
+
hookTimeoutMs: number;
|
|
101
|
+
/** borrowed-readonly — the own hook-crash reporter (the deny observer's swallow-guarded report sink). */
|
|
102
|
+
notifyOwnHookCrash: (err: unknown) => void;
|
|
103
|
+
}
|
|
104
|
+
export interface PrepareAskLaneResult {
|
|
105
|
+
/** owned — the lane, or undefined exactly when `gateMachineryActive` is false (then the suspend saga, the boundary
|
|
106
|
+
* parks, the park closure and the gate station are not built either — they key off this seat). */
|
|
107
|
+
askLane: AskLane | undefined;
|
|
108
|
+
}
|
|
109
|
+
/** The M19 ask-lane phase body — prepareTask's in-stream ask stretch, verbatim (see the module header). */
|
|
110
|
+
export declare function prepareAskLane(input: PrepareAskLaneInput): PrepareAskLaneResult;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { primaryActivityArg } from "../arg-summary.js";
|
|
2
|
+
import { hookSeatExpiredError, runHookSeat } from "../hooks.js";
|
|
3
|
+
import { isLiveApproverSeat, resolveAsk } from "../tool-policy.js";
|
|
4
|
+
import { composeCallSignal, raceAbort } from "./abort-race.js";
|
|
5
|
+
import { gateAskCarry, lateAskSettlementObserver } from "./denial-limit-arms.js";
|
|
6
|
+
import { consumeInheritedAskGrant } from "./inherited-ask-grants.js";
|
|
7
|
+
function createDenyObserverNotifier(hooks, hookTimeoutMs, signal, report) {
|
|
8
|
+
if (hooks?.permissionDenied === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
return async (payload) => {
|
|
11
|
+
try {
|
|
12
|
+
const seat = await runHookSeat("permissionDenied", { timeoutMs: hookTimeoutMs, signal }, (sig) => hooks.permissionDenied({ ...payload, signal: sig }));
|
|
13
|
+
if (seat.expired)
|
|
14
|
+
report(hookSeatExpiredError("permissionDenied", hookTimeoutMs, seat.cause, "the deny observation was abandoned; the deny itself is unchanged"));
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
report(err);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const sanitizePreview = (node, depth = 0) => {
|
|
22
|
+
if (depth > 6)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (typeof node === "string") {
|
|
25
|
+
return node.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, "\u2400");
|
|
26
|
+
}
|
|
27
|
+
if (node === null || typeof node !== "object")
|
|
28
|
+
return node;
|
|
29
|
+
if (Array.isArray(node))
|
|
30
|
+
return node.map((v) => sanitizePreview(v, depth + 1));
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const [k, v] of Object.entries(node)) {
|
|
33
|
+
out[sanitizePreview(k, depth + 1)] = sanitizePreview(v, depth + 1);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
};
|
|
37
|
+
function resolveApprovalPreview(tools, toolName, args) {
|
|
38
|
+
const t = tools.find((x) => x.name === toolName || (x.aliases?.includes(toolName) ?? false));
|
|
39
|
+
if (t?.approvalPreview === undefined)
|
|
40
|
+
return {};
|
|
41
|
+
try {
|
|
42
|
+
const raw = t.approvalPreview(args);
|
|
43
|
+
if (raw === undefined)
|
|
44
|
+
return {};
|
|
45
|
+
const bytes = JSON.stringify(raw);
|
|
46
|
+
if (bytes === undefined)
|
|
47
|
+
return { withheld: "unavailable" };
|
|
48
|
+
if (bytes.length > 16_384) {
|
|
49
|
+
return { preview: { truncated: true, note: `approval preview exceeded 16KiB (${bytes.length} chars serialized)` }, withheld: "oversize" };
|
|
50
|
+
}
|
|
51
|
+
return { preview: sanitizePreview(raw) };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { withheld: "unavailable" };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function prepareAskLane(input) {
|
|
58
|
+
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, humanBareRejections, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash } = input;
|
|
59
|
+
if (!gateMachineryActive)
|
|
60
|
+
return { askLane: undefined };
|
|
61
|
+
const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
|
|
62
|
+
const adjudicate = effectivePolicy
|
|
63
|
+
? (req, callSignal) => {
|
|
64
|
+
const signal = composedCallSignal(callSignal);
|
|
65
|
+
return raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}) }, signal)), signal, () => ({
|
|
66
|
+
action: "deny",
|
|
67
|
+
message: "policy check aborted (task timed out or cancelled)",
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
: undefined;
|
|
71
|
+
const approvalPreviewOf = (toolName, args) => resolveApprovalPreview(tools, toolName, args);
|
|
72
|
+
const resolveAskBound = async (decision, req, callSignal) => {
|
|
73
|
+
if (inheritedUnavailableAsks.delete(req.toolCallId)) {
|
|
74
|
+
return {
|
|
75
|
+
action: "deny",
|
|
76
|
+
message: `approval for "${req.toolName}" requires an ancestor task's approval that cannot be resolved here ` +
|
|
77
|
+
`(no live approver reachable, or a durable-park mandate applies), and no durable approval park is ` +
|
|
78
|
+
`available — denied fail-closed (the inherited constraint stands).`,
|
|
79
|
+
decisionReason: "mode",
|
|
80
|
+
approverUnavailable: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const grantReuse = consumeInheritedAskGrant(inheritedAskGrants, onAsk, humanReviewRef, decision, req);
|
|
84
|
+
if (grantReuse !== undefined)
|
|
85
|
+
return grantReuse;
|
|
86
|
+
const t0 = now();
|
|
87
|
+
const resolved = await resolveAsk({
|
|
88
|
+
toolName: req.toolName,
|
|
89
|
+
toolCallId: req.toolCallId,
|
|
90
|
+
args: req.args,
|
|
91
|
+
...(() => {
|
|
92
|
+
const p = approvalPreviewOf(req.toolName, req.args);
|
|
93
|
+
return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
|
|
94
|
+
})(),
|
|
95
|
+
...ruleOffersOf(req.toolName, req.args, decision.action === "ask" ? decision : undefined),
|
|
96
|
+
message: decision.message ?? `approval required for "${req.toolName}"`,
|
|
97
|
+
...askSourceIdentity(),
|
|
98
|
+
...riskAxesOf(req.toolName),
|
|
99
|
+
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
100
|
+
...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
|
|
101
|
+
...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking) : {}),
|
|
102
|
+
...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
|
|
103
|
+
...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
|
|
104
|
+
...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
|
|
105
|
+
}, onAsk, composedCallSignal(callSignal), lateAskSettlementObserver({ toolName: req.toolName, toolCallId: req.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
|
|
106
|
+
const waitMs = Math.max(0, now() - t0);
|
|
107
|
+
if (resolved.approverUnavailable !== true && resolved.resolution !== "task_aborted") {
|
|
108
|
+
humanReviewRef.count += 1;
|
|
109
|
+
humanReviewRef.totalWaitMs += waitMs;
|
|
110
|
+
const toolArg = primaryActivityArg(req.args);
|
|
111
|
+
humanReviewRef.gates.push({
|
|
112
|
+
kind: "human",
|
|
113
|
+
waitMs,
|
|
114
|
+
decision: resolved.action === "allow" ? "allow" : "deny",
|
|
115
|
+
toolName: req.toolName,
|
|
116
|
+
...(toolArg !== undefined ? { toolArg } : {}),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (resolved.action === "deny" && resolved.settledBy === "human" && resolved.humanRefusalNote !== true) {
|
|
120
|
+
humanBareRejections.add(req.toolCallId);
|
|
121
|
+
}
|
|
122
|
+
return resolved;
|
|
123
|
+
};
|
|
124
|
+
const notifyPermissionDenied = createDenyObserverNotifier(hooks, hookTimeoutMs, abortController.signal, notifyOwnHookCrash);
|
|
125
|
+
const notifyHookError = (err) => {
|
|
126
|
+
try {
|
|
127
|
+
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
return { askLane: { composedCallSignal, adjudicate, approvalPreviewOf, resolveAskBound, notifyPermissionDenied, notifyHookError } };
|
|
133
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/390 §1.2 M19 (boundary parks) — prepareTask's three BOUNDARY-FAMILY park lanes, verbatim from the driver's
|
|
3
|
+
* gate-machinery block: the ONE resource mint (`mintResourceSuspend`) behind the task-axis `suspendForResource` (exposed
|
|
4
|
+
* under the caller's `resourceSuspend` opt-in) and the infrastructure `suspendForPlatformLimit` (exposed on durable infra
|
|
5
|
+
* alone), and the `plan_review` mint behind `suspendForReview` (exposed with a checkpoint store). Every mint reuses the
|
|
6
|
+
* suspend saga's serializer, commit saga, loop cap and env split — this phase adds no second copy of any of them. The
|
|
7
|
+
* interface is the dependency list the segment had implicitly (design/238 R-1); the three lanes used to be forward-declared
|
|
8
|
+
* `let`s at task scope, assigned inside the block, so the driver's Prepared could expose them — they are this phase's
|
|
9
|
+
* Result now, and the driver binds them as consts.
|
|
10
|
+
*
|
|
11
|
+
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (the mints run at a turn boundary), so the
|
|
12
|
+
* phase adds no yield between the suspend saga before it and the park closure after it (design/390 §1.5). Read-stability
|
|
13
|
+
* of the host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
|
|
14
|
+
*
|
|
15
|
+
* The lanes exist exactly when the saga does (the ask lane's presence law) — an absent saga answers three absent lanes,
|
|
16
|
+
* so the orchestrator grows no branch.
|
|
17
|
+
*/
|
|
18
|
+
import type { AgentHarness } from "../../internal/harness.js";
|
|
19
|
+
import { type CheckpointStore } from "../checkpoint-store.js";
|
|
20
|
+
import type { SessionStore } from "../session.js";
|
|
21
|
+
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
22
|
+
import type { Prepared, SuspendSaga } from "./contracts.js";
|
|
23
|
+
export interface PrepareBoundaryParksInput {
|
|
24
|
+
/** borrowed-readonly — the suspend saga's eight seats (the serializer, the commit saga, the loop cap, the env split, the
|
|
25
|
+
* park-only handle, the in-flight spend), or undefined when the gate machinery is inactive (then all three lanes are
|
|
26
|
+
* absent). Called at park time; never rebuilt here. */
|
|
27
|
+
saga: SuspendSaga | undefined;
|
|
28
|
+
/** borrowed-readonly — the REBOUND spec. Read: `resourceSuspend` (the slice scope/ttl/maxSlices), `retainBackgroundProcesses`
|
|
29
|
+
* (the park-only sweep gate), `durableApproval` (the review pause's ttl), `principal` (the rows' identity record and
|
|
30
|
+
* the review scope). Never mutated. */
|
|
31
|
+
spec: Pick<TaskSpec, "resourceSuspend" | "retainBackgroundProcesses" | "durableApproval" | "principal">;
|
|
32
|
+
/** borrowed-readonly — the leg's checkpoint store, or undefined (then the review lane is absent and the resource mint
|
|
33
|
+
* answers false). */
|
|
34
|
+
checkpointStore: CheckpointStore | undefined;
|
|
35
|
+
/** borrowed-mutable — prepare's abort seat. `.signal` is read (a cancelling run mints nothing); `abort()` is called on
|
|
36
|
+
* the review lane's commit (the review pause stops the run the human gate's way). */
|
|
37
|
+
abortController: {
|
|
38
|
+
readonly signal: AbortSignal;
|
|
39
|
+
abort(): void;
|
|
40
|
+
};
|
|
41
|
+
/** borrowed-mutable — the built harness. Writers here: `requestStopAfterTurn()` (the resource lanes' clean stop) and
|
|
42
|
+
* `abort()` (the review lane's stop). */
|
|
43
|
+
harness: Pick<AgentHarness, "abort" | "requestStopAfterTurn">;
|
|
44
|
+
/** borrowed-readonly — the acquired session; only `getLeafId` is read (no committed leaf ⇒ no consistent resume point). */
|
|
45
|
+
session: Pick<Prepared["session"], "getLeafId">;
|
|
46
|
+
/** borrowed-readonly — the session store; only the optional `pin` is called (best-effort, after commit). */
|
|
47
|
+
sessions: Pick<SessionStore, "pin">;
|
|
48
|
+
/** borrowed-readonly — the acquired session id (the rows' session, the source attribution, every disclosure's tag). */
|
|
49
|
+
sessionId: string;
|
|
50
|
+
/** borrowed-readonly — the deployment's error face, as a Pick over the SAME `deps` object (receiver preserved for
|
|
51
|
+
* `deps.onError?.()`): the suspendVM refusals, the skipped-review lines, the pin failures. */
|
|
52
|
+
deps: Pick<RunnerDeps, "onError">;
|
|
53
|
+
/** borrowed-readonly — the cross-slice ledger carried on the resumed checkpoint, or undefined on the first slice. */
|
|
54
|
+
priorLedger: Prepared["resourceLedger"];
|
|
55
|
+
/** borrowed-readonly — the resource-slice cap, or undefined (no slice ceiling). */
|
|
56
|
+
maxSlices: number | undefined;
|
|
57
|
+
/** borrowed-readonly — the restart-loop cap the review lane is bounded by. */
|
|
58
|
+
maxSuspends: number;
|
|
59
|
+
/** borrowed-readonly — the human total the first slice seeds the ledger from, or undefined. */
|
|
60
|
+
resourceTotal: {
|
|
61
|
+
totalBudgetMicroUsd?: number;
|
|
62
|
+
totalTokens?: number;
|
|
63
|
+
} | undefined;
|
|
64
|
+
/** borrowed-readonly — how many times this task already suspended (carried on the resumed checkpoint); a slice carries
|
|
65
|
+
* it UNCHANGED. */
|
|
66
|
+
priorSuspendCount: number;
|
|
67
|
+
/** borrowed-readonly — the restart-loop chain base (resets after an approved execution); the review lane's count. */
|
|
68
|
+
suspendChainBase: () => number;
|
|
69
|
+
/** borrowed-readonly — the per-task human-review accumulator; snapshotted onto every row (never written here). */
|
|
70
|
+
humanReviewRef: Prepared["humanReviewRef"];
|
|
71
|
+
/** borrowed-readonly — the loop's live spend seat; `.get` is read at mint time for the review ledger. */
|
|
72
|
+
liveSpendRef: Prepared["liveSpendRef"];
|
|
73
|
+
/** borrowed-readonly — the injectable wall clock (the review pause's `suspendedAt`). */
|
|
74
|
+
now: () => number;
|
|
75
|
+
/** borrowed-readonly — the inherited-gate phase's three version-stamp predicates (face / F-012 / org admission). */
|
|
76
|
+
faceCheckpointState: () => boolean;
|
|
77
|
+
/** borrowed-readonly — see `faceCheckpointState`. */
|
|
78
|
+
f012CheckpointState: () => boolean;
|
|
79
|
+
/** borrowed-readonly — see `faceCheckpointState`. */
|
|
80
|
+
orgAdmissionCheckpointState: () => boolean;
|
|
81
|
+
/** borrowed-mutable — the durable-suspend holder (`Prepared.suspendRef`). Writer here: the commit-side publication of a
|
|
82
|
+
* committed resource row. */
|
|
83
|
+
suspendRef: Prepared["suspendRef"];
|
|
84
|
+
/** borrowed-mutable — the review holder (`Prepared.reviewRef`). Writer here: the commit-side publication of a committed
|
|
85
|
+
* plan_review row. */
|
|
86
|
+
reviewRef: Prepared["reviewRef"];
|
|
87
|
+
/** borrowed-mutable — the run-scoped remote-lifecycle failure log. Writer here: a refused `suspendVM` (`push`). */
|
|
88
|
+
remoteEnvFailures: Prepared["remoteEnvFailures"];
|
|
89
|
+
/** borrowed-readonly — the wiring-manifest phase's task-axis predicate (opt-in ∧ durable infra): exposes `suspendForResource`. */
|
|
90
|
+
resourceSuspendEligible: boolean;
|
|
91
|
+
/** borrowed-readonly — the wiring-manifest phase's durable-infra predicate: exposes `suspendForPlatformLimit`. */
|
|
92
|
+
durableSuspendInfraReady: boolean;
|
|
93
|
+
/** borrowed-readonly — the restore-incomplete adapter's missing members, or undefined (the review lane refuses loudly). */
|
|
94
|
+
incompleteSuspendAdapter: readonly ("resumeVM" | "postResumeInit")[] | undefined;
|
|
95
|
+
}
|
|
96
|
+
export interface PrepareBoundaryParksResult {
|
|
97
|
+
/** owned — the task-axis resource lane (`Prepared.suspendForResource`), or undefined (no opt-in / no infra / inactive). */
|
|
98
|
+
suspendForResource: Prepared["suspendForResource"];
|
|
99
|
+
/** owned — the platform lane (`Prepared.suspendForPlatformLimit`), or undefined (no durable infra / inactive). */
|
|
100
|
+
suspendForPlatformLimit: Prepared["suspendForPlatformLimit"];
|
|
101
|
+
/** owned — the plan-review lane (`Prepared.suspendForReview`), or undefined (no store / inactive). */
|
|
102
|
+
suspendForReview: Prepared["suspendForReview"];
|
|
103
|
+
}
|
|
104
|
+
/** The M19 boundary-parks phase body — prepareTask's three boundary lanes, verbatim (see the module header). */
|
|
105
|
+
export declare function prepareBoundaryParks(input: PrepareBoundaryParksInput): PrepareBoundaryParksResult;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
2
|
+
import { BINDING_CHECKPOINT_VERSION, debitLedger, F012_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, mintCheckpointId, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, } from "../checkpoint-store.js";
|
|
3
|
+
import { defaultTaskRegistry } from "../task-registry.js";
|
|
4
|
+
import { checkpointScopeOf } from "./checkpoint-scope.js";
|
|
5
|
+
import { DEFAULT_RESOURCE_TTL_MS, publishCommittedSuspend, sanitizedTtlMs, USAGE_WINDOW_REAP_MARGIN_MS } from "./park-commit.js";
|
|
6
|
+
import { remoteEnvFailureNote } from "./remote-env-retry.js";
|
|
7
|
+
export function prepareBoundaryParks(input) {
|
|
8
|
+
const { saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, suspendRef, reviewRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter } = input;
|
|
9
|
+
if (saga === undefined)
|
|
10
|
+
return { suspendForResource: undefined, suspendForPlatformLimit: undefined, suspendForReview: undefined };
|
|
11
|
+
const { inFlightSpendMicroUsd, serializeCheckpointState, commitSuspendSaga, suspendLoopCapHit, suspendableEnv, parkOnlyRemoteEnv, parkOnlyHandle } = saga;
|
|
12
|
+
const mintResourceSuspend = async (reason, sliceSpend, hint) => {
|
|
13
|
+
const rs = spec.resourceSuspend;
|
|
14
|
+
if (!checkpointStore)
|
|
15
|
+
return false;
|
|
16
|
+
const scope = rs?.scope ?? checkpointScopeOf(spec);
|
|
17
|
+
if (abortController.signal.aborted)
|
|
18
|
+
return false;
|
|
19
|
+
if (suspendLoopCapHit(priorLedger?.sliceCount ?? 0, maxSlices, " (resource slice limit)"))
|
|
20
|
+
return false;
|
|
21
|
+
const leafId = await session.getLeafId();
|
|
22
|
+
if (!leafId)
|
|
23
|
+
return false;
|
|
24
|
+
if (abortController.signal.aborted)
|
|
25
|
+
return false;
|
|
26
|
+
let remoteHandle;
|
|
27
|
+
if (suspendableEnv !== undefined) {
|
|
28
|
+
if (hasBackgroundShell(suspendableEnv))
|
|
29
|
+
await sweepBackgroundShells(suspendableEnv, defaultTaskRegistry);
|
|
30
|
+
const snap = await suspendableEnv.suspendVM({ abortSignal: abortController.signal });
|
|
31
|
+
if (!snap.ok) {
|
|
32
|
+
remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
|
|
33
|
+
deps.onError?.(new Error(`resource suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }), { phase: "config", sessionId });
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
|
|
37
|
+
}
|
|
38
|
+
else if (parkOnlyRemoteEnv !== undefined) {
|
|
39
|
+
if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
|
|
40
|
+
await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
|
|
41
|
+
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
42
|
+
}
|
|
43
|
+
const token = mintCheckpointToken();
|
|
44
|
+
const gate = { kind: "resource_limit", reason, ...(hint !== undefined ? { resumeAfterMs: hint.resumeAfterMs } : {}) };
|
|
45
|
+
const resourceLedgerOut = debitLedger(priorLedger, sliceSpend, resourceTotal);
|
|
46
|
+
const mintedAt = Date.now();
|
|
47
|
+
const cp = {
|
|
48
|
+
token,
|
|
49
|
+
checkpointId: mintCheckpointId(),
|
|
50
|
+
version: faceCheckpointState() ? FACE_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
|
|
51
|
+
scope,
|
|
52
|
+
sessionId,
|
|
53
|
+
leafId,
|
|
54
|
+
gate,
|
|
55
|
+
pendingAction: { kind: "resource_limit", reason },
|
|
56
|
+
state: serializeCheckpointState(remoteHandle),
|
|
57
|
+
status: "pending",
|
|
58
|
+
createdAt: mintedAt,
|
|
59
|
+
deadline: mintedAt + Math.max(sanitizedTtlMs(rs?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS, hint !== undefined ? hint.resumeAfterMs + USAGE_WINDOW_REAP_MARGIN_MS : 0),
|
|
60
|
+
suspendCount: priorSuspendCount,
|
|
61
|
+
humanReview: humanReviewRef.count > 0
|
|
62
|
+
? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
|
|
63
|
+
: undefined,
|
|
64
|
+
resourceLedger: resourceLedgerOut,
|
|
65
|
+
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
66
|
+
...(spec.principal ? { principal: spec.principal } : {}),
|
|
67
|
+
};
|
|
68
|
+
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
69
|
+
return false;
|
|
70
|
+
publishCommittedSuspend({ suspendRef, reviewRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
71
|
+
try {
|
|
72
|
+
await sessions.pin?.(sessionId);
|
|
73
|
+
}
|
|
74
|
+
catch (pinErr) {
|
|
75
|
+
deps.onError?.(pinErr, { phase: "config", sessionId });
|
|
76
|
+
}
|
|
77
|
+
harness.requestStopAfterTurn();
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
const suspendForResource = resourceSuspendEligible ? mintResourceSuspend : undefined;
|
|
81
|
+
const suspendForPlatformLimit = durableSuspendInfraReady ? mintResourceSuspend : undefined;
|
|
82
|
+
const suspendForReview = checkpointStore
|
|
83
|
+
? async (reason) => {
|
|
84
|
+
if (!checkpointStore)
|
|
85
|
+
return false;
|
|
86
|
+
if (abortController.signal.aborted)
|
|
87
|
+
return false;
|
|
88
|
+
if (incompleteSuspendAdapter !== undefined) {
|
|
89
|
+
try {
|
|
90
|
+
deps.onError?.(new Error(`plan_review park skipped: the per-task execution env declares capabilities.suspendable but its adapter does not implement ${incompleteSuspendAdapter.join(" or ")}, so its workspace could not be restored on resume (the plan-review request was dropped; the run continues without pausing)`), { phase: "config", sessionId });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (suspendLoopCapHit(suspendChainBase(), maxSuspends, " for a plan_review (likely a resume/restart loop)."))
|
|
97
|
+
return false;
|
|
98
|
+
const leafId = await session.getLeafId();
|
|
99
|
+
if (!leafId) {
|
|
100
|
+
try {
|
|
101
|
+
deps.onError?.(new Error("plan_review park skipped: no committed session leaf (the plan-review request was dropped; the run continues without pausing)"), { phase: "config", sessionId });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (abortController.signal.aborted)
|
|
108
|
+
return false;
|
|
109
|
+
let remoteHandle;
|
|
110
|
+
if (suspendableEnv !== undefined) {
|
|
111
|
+
if (hasBackgroundShell(suspendableEnv))
|
|
112
|
+
await sweepBackgroundShells(suspendableEnv, defaultTaskRegistry);
|
|
113
|
+
const snap = await suspendableEnv.suspendVM({ abortSignal: abortController.signal });
|
|
114
|
+
if (!snap.ok) {
|
|
115
|
+
remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
|
|
116
|
+
deps.onError?.(new Error(`plan_review suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }), { phase: "config", sessionId });
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
|
|
120
|
+
}
|
|
121
|
+
else if (parkOnlyRemoteEnv !== undefined) {
|
|
122
|
+
if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
|
|
123
|
+
await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
|
|
124
|
+
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
125
|
+
}
|
|
126
|
+
const token = mintCheckpointToken();
|
|
127
|
+
const scope = checkpointScopeOf(spec);
|
|
128
|
+
const gate = { kind: "plan_review", reason: reason ?? `plan review requested` };
|
|
129
|
+
const reviewLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
|
|
130
|
+
const mintedAt = Date.now();
|
|
131
|
+
const cp = {
|
|
132
|
+
token,
|
|
133
|
+
checkpointId: mintCheckpointId(),
|
|
134
|
+
version: faceCheckpointState() ? FACE_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
135
|
+
scope,
|
|
136
|
+
sessionId,
|
|
137
|
+
leafId,
|
|
138
|
+
gate,
|
|
139
|
+
pendingAction: { kind: "plan_review" },
|
|
140
|
+
state: serializeCheckpointState(remoteHandle, inFlightSpendMicroUsd()),
|
|
141
|
+
status: "pending",
|
|
142
|
+
createdAt: mintedAt,
|
|
143
|
+
suspendedAt: now(),
|
|
144
|
+
deadline: mintedAt + (sanitizedTtlMs(spec.durableApproval?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
|
|
145
|
+
suspendCount: suspendChainBase() + 1,
|
|
146
|
+
humanReview: humanReviewRef.count > 0
|
|
147
|
+
? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
|
|
148
|
+
: undefined,
|
|
149
|
+
resourceLedger: reviewLedger,
|
|
150
|
+
sourceTaskId: sessionId,
|
|
151
|
+
...(spec.principal ? { principal: spec.principal } : {}),
|
|
152
|
+
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
153
|
+
};
|
|
154
|
+
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
155
|
+
return false;
|
|
156
|
+
publishCommittedSuspend({ suspendRef, reviewRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
157
|
+
try {
|
|
158
|
+
await sessions.pin?.(sessionId);
|
|
159
|
+
}
|
|
160
|
+
catch (pinErr) {
|
|
161
|
+
deps.onError?.(pinErr, { phase: "config", sessionId });
|
|
162
|
+
}
|
|
163
|
+
abortController.abort();
|
|
164
|
+
void harness.abort();
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
: undefined;
|
|
168
|
+
return { suspendForResource, suspendForPlatformLimit, suspendForReview };
|
|
169
|
+
}
|