@sema-agent/core 5.7.0 → 5.8.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 +37 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/subagent.d.ts +4 -7
- package/dist/agents/subagent.js +10 -19
- package/dist/agents/teacher.js +3 -3
- package/dist/agents/team.d.ts +1 -1
- package/dist/agents/team.js +1 -1
- package/dist/agents/verify.js +1 -1
- package/dist/brain/anthropic.js +24 -35
- package/dist/brain/openai.js +22 -33
- package/dist/brain/stream-engine.d.ts +0 -2
- package/dist/brain/stream-engine.js +5 -47
- package/dist/brain/stream-shared.d.ts +0 -10
- package/dist/brain/stream-shared.js +0 -23
- package/dist/brain/terminal-cause.d.ts +0 -1
- package/dist/brain/terminal-cause.js +0 -3
- package/dist/brain/timeout.d.ts +4 -2
- package/dist/brain/timeout.js +4 -14
- package/dist/config/catalog.d.ts +3 -11
- package/dist/config/catalog.js +30 -69
- package/dist/config/defaults.d.ts +0 -3
- package/dist/config/defaults.js +0 -3
- package/dist/core/auto-compaction.d.ts +2 -11
- package/dist/core/auto-compaction.js +3 -67
- package/dist/core/checkpoint-store.d.ts +10 -8
- package/dist/core/checkpoint-store.js +7 -7
- package/dist/core/memory-engine/file-backend.js +20 -17
- package/dist/core/memory-engine/layout.d.ts +5 -0
- package/dist/core/memory-engine/layout.js +72 -47
- package/dist/core/runner/assemble-result.d.ts +2 -8
- package/dist/core/runner/assemble-result.js +15 -16
- package/dist/core/runner/prepare-task.d.ts +26 -6
- package/dist/core/runner/prepare-task.js +203 -141
- package/dist/core/runner/runtask.d.ts +0 -2
- package/dist/core/runner/runtask.js +200 -244
- package/dist/core/session-policy-store.d.ts +5 -0
- package/dist/core/session-policy-store.js +3 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/task-registry-agent.d.ts +2 -0
- package/dist/core/task-registry-agent.js +33 -1
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +9 -1
- package/dist/core/tool-errors.js +10 -2
- package/dist/core/trace.d.ts +1 -8
- package/dist/core/types.d.ts +24 -30
- package/dist/core/usage-window-store.d.ts +39 -0
- package/dist/core/usage-window-store.js +115 -0
- package/dist/engine/execution-env/node-execution-env.js +0 -22
- package/dist/engine/harness/agent-harness.d.ts +0 -7
- package/dist/engine/harness/agent-harness.js +13 -53
- package/dist/engine/harness/types.d.ts +2 -15
- package/dist/engine/llm/types.d.ts +1 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +5 -171
- package/dist/engine/loop/types.d.ts +0 -18
- package/dist/index.d.ts +10 -5
- package/dist/index.js +8 -4
- package/dist/orchestration/goal.js +1 -5
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +1 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -6
- package/dist/orchestration/workflow-governance.js +33 -47
- package/dist/prompt-assembly/event-registry.js +2 -3
- package/dist/stores/cc/lockfile.js +9 -11
- package/dist/stores/cc/mailbox-store.js +49 -38
- package/dist/stores/file/index.d.ts +3 -0
- package/dist/stores/file/index.js +4 -0
- package/dist/stores/file/usage-window-store.d.ts +9 -0
- package/dist/stores/file/usage-window-store.js +86 -0
- package/dist/tools/fs/fs-bash.d.ts +1 -10
- package/dist/tools/fs/fs-bash.js +20 -60
- package/dist/tools/fs/index.d.ts +0 -2
- package/dist/tools/fs/index.js +1 -2
- package/package.json +1 -4
- package/dist/bin/sema-tb.d.ts +0 -31
- package/dist/bin/sema-tb.js +0 -448
- package/dist/bin/tb-env.d.ts +0 -2
- package/dist/bin/tb-env.js +0 -17
- package/dist/brain/walltime.d.ts +0 -1
- package/dist/brain/walltime.js +0 -1
- package/dist/core/runner/call-cap.d.ts +0 -67
- package/dist/core/runner/call-cap.js +0 -145
- package/dist/core/runner/cut-kill.d.ts +0 -10
- package/dist/core/runner/cut-kill.js +0 -37
|
@@ -19,8 +19,6 @@ import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send
|
|
|
19
19
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
20
20
|
import { combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
21
21
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
22
|
-
import { computeCallCap, createCallCapRef, softExecDeadlineMs, toolCutDeadlineMs, WALLTIME_STALL_CONNECT_MS, WALLTIME_STALL_FIRST_TOKEN_MS, WALLTIME_STALL_IDLE_MS } from "./call-cap.js";
|
|
23
|
-
import { createCutKillRegistry } from "./cut-kill.js";
|
|
24
22
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
25
23
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
26
24
|
import { emitTrace } from "../trace.js";
|
|
@@ -29,7 +27,7 @@ import { cloneObserverInput, formatHookFeedback, runToolGate } from "../hooks.js
|
|
|
29
27
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
30
28
|
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
|
|
31
29
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
32
|
-
import { resolveBrainCallGuardrailMs, withBrainCallGuardrail } from "../../brain/timeout.js";
|
|
30
|
+
import { resolveBrainCallGuardrailMs, STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
|
|
33
31
|
import { defineTool, isDefineToolProduct } from "../tools.js";
|
|
34
32
|
import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
|
|
35
33
|
import { protocolOf, PROTOCOL_TABLE } from "../protocol-table.js";
|
|
@@ -69,13 +67,96 @@ import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestrati
|
|
|
69
67
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
70
68
|
import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
|
|
71
69
|
import { resolveKey } from "../../tools/fs/safety.js";
|
|
72
|
-
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION,
|
|
70
|
+
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
|
|
73
71
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
72
|
+
import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
|
|
74
73
|
const RECONCILE_MAX_RETRIES = 3;
|
|
75
74
|
const DEFAULT_MAX_SUSPENDS = 5;
|
|
76
|
-
const
|
|
75
|
+
const TASK_LIMIT_KEYS = [
|
|
76
|
+
"maxTokens",
|
|
77
|
+
"maxCostUsd",
|
|
78
|
+
"maxTurns",
|
|
79
|
+
"maxWalltimeMs",
|
|
80
|
+
"maxOutputTokens",
|
|
81
|
+
"approachNotice",
|
|
82
|
+
"budgetStreamCancel",
|
|
83
|
+
"degrade",
|
|
84
|
+
"brainCallGuardrailMs",
|
|
85
|
+
];
|
|
86
|
+
const NUMERIC_TASK_LIMIT_KEYS = ["maxTokens", "maxCostUsd", "maxTurns", "maxWalltimeMs", "maxOutputTokens"];
|
|
87
|
+
const RETIRED_TASK_LIMIT_KEYS = {
|
|
88
|
+
timeoutSec: "maxWalltimeMs (milliseconds, not seconds)",
|
|
89
|
+
deadlineNudge: "approachNotice",
|
|
90
|
+
callCapByDeadline: "(retired — no replacement)",
|
|
91
|
+
gracefulFinalize: "(retired — the approach notice is the only end-of-budget prompt)",
|
|
92
|
+
};
|
|
93
|
+
function limitConfigError(code, message) {
|
|
94
|
+
const e = new Error(message);
|
|
95
|
+
e.code = code;
|
|
96
|
+
return e;
|
|
97
|
+
}
|
|
98
|
+
export function resolveTaskLimits(limits) {
|
|
99
|
+
if (limits === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
if (typeof limits !== "object" || Array.isArray(limits)) {
|
|
102
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.limits must be an object (got ${Array.isArray(limits) ? "an array" : typeof limits})`);
|
|
103
|
+
}
|
|
104
|
+
const raw = limits;
|
|
105
|
+
const legal = TASK_LIMIT_KEYS;
|
|
106
|
+
for (const key of Object.keys(raw)) {
|
|
107
|
+
if (legal.includes(key))
|
|
108
|
+
continue;
|
|
109
|
+
const replacement = RETIRED_TASK_LIMIT_KEYS[key];
|
|
110
|
+
throw limitConfigError("config.limit_unknown_key", `TaskSpec.limits.${key} is not a limit this engine reads` +
|
|
111
|
+
(replacement !== undefined ? ` — it was replaced by \`${replacement}\`` : "") +
|
|
112
|
+
`. Legal keys: ${TASK_LIMIT_KEYS.join(", ")}. Refused rather than ignored: a limit that is silently dropped reads to the caller as an armed ceiling.`);
|
|
113
|
+
}
|
|
114
|
+
for (const key of NUMERIC_TASK_LIMIT_KEYS) {
|
|
115
|
+
const value = raw[key];
|
|
116
|
+
if (value === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
119
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.limits.${key} must be a finite, non-negative number (got ${String(value)}) — an unevaluable ceiling is not a ceiling, and folding it to a default would run the task under a limit nobody chose.`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const notice = raw.approachNotice;
|
|
123
|
+
if (notice !== undefined && notice !== false) {
|
|
124
|
+
if (typeof notice !== "object" || notice === null || Array.isArray(notice)) {
|
|
125
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice must be \`false\` or { at?: [first, second] } (got ${String(notice)})`);
|
|
126
|
+
}
|
|
127
|
+
const at = notice.at;
|
|
128
|
+
if (at !== undefined) {
|
|
129
|
+
const ok = Array.isArray(at) &&
|
|
130
|
+
at.length === 2 &&
|
|
131
|
+
at.every((n) => typeof n === "number" && Number.isFinite(n) && n > 0 && n <= 1) &&
|
|
132
|
+
at[0] <= at[1];
|
|
133
|
+
if (!ok) {
|
|
134
|
+
throw limitConfigError("config.limit_invalid", `TaskSpec.limits.approachNotice.at must be two fractions in (0, 1] with first <= second (got ${JSON.stringify(at)})`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return limits;
|
|
139
|
+
}
|
|
77
140
|
const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
|
|
78
141
|
const DEFAULT_RESOURCE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
142
|
+
export const ENV_LIFETIME_SUSPEND_MARGIN_MS = 60_000;
|
|
143
|
+
export const USAGE_WINDOW_REAP_MARGIN_MS = 60 * 60 * 1000;
|
|
144
|
+
export function resolveEnvLifetimeExpiry(env, observedAt) {
|
|
145
|
+
const lifetimeMs = env.lifetimeMs;
|
|
146
|
+
if (lifetimeMs === undefined)
|
|
147
|
+
return undefined;
|
|
148
|
+
if (typeof lifetimeMs !== "number" || !Number.isFinite(lifetimeMs) || lifetimeMs <= 0) {
|
|
149
|
+
throw limitConfigError("config.env_lifetime_invalid", `ExecutionEnv.lifetimeMs must be a finite number of milliseconds > 0 (got ${String(lifetimeMs)}) — an unevaluable lifetime is not a lifetime, and treating it as absent would run the task with no protection against the platform reclaiming its environment.`);
|
|
150
|
+
}
|
|
151
|
+
const declaredStart = env.lifetimeStartedAt;
|
|
152
|
+
if (declaredStart !== undefined && (typeof declaredStart !== "number" || !Number.isFinite(declaredStart))) {
|
|
153
|
+
throw limitConfigError("config.env_lifetime_invalid", `ExecutionEnv.lifetimeStartedAt must be a finite epoch-ms timestamp (got ${String(declaredStart)}).`);
|
|
154
|
+
}
|
|
155
|
+
const anchor = declaredStart ?? observedAt;
|
|
156
|
+
if (anchor === undefined)
|
|
157
|
+
return { unanchored: true };
|
|
158
|
+
return { expiresAt: anchor + lifetimeMs };
|
|
159
|
+
}
|
|
79
160
|
const DEFAULT_UNATTENDED_APPROVAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
80
161
|
function sanitizedTtlMs(ttlMs) {
|
|
81
162
|
if (ttlMs === undefined)
|
|
@@ -237,6 +318,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
237
318
|
e.code = "config.empty_objective";
|
|
238
319
|
throw e;
|
|
239
320
|
}
|
|
321
|
+
resolveTaskLimits(spec.limits);
|
|
322
|
+
const usageWindows = resolveUsageWindows(deps.usageWindows);
|
|
240
323
|
const brainCallGuardrailRef = {};
|
|
241
324
|
const brainCallGuardrailMs = resolveBrainCallGuardrailMs(spec.limits?.brainCallGuardrailMs ?? deps.brainCallGuardrailMs);
|
|
242
325
|
if (spec.agents !== undefined && spec.agents.length > 0) {
|
|
@@ -497,6 +580,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
497
580
|
throw e;
|
|
498
581
|
}
|
|
499
582
|
let ownedEnv;
|
|
583
|
+
let ownedEnvObservedAt;
|
|
500
584
|
let mcp;
|
|
501
585
|
let a2a;
|
|
502
586
|
if (internals?.requestedCwd !== undefined && deps.executionEnvFactory === undefined) {
|
|
@@ -514,6 +598,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
514
598
|
...(internals?.parentCwd ? { parentCwd: internals.parentCwd } : {}),
|
|
515
599
|
})
|
|
516
600
|
: undefined;
|
|
601
|
+
if (ownedEnv !== undefined)
|
|
602
|
+
ownedEnvObservedAt = Date.now();
|
|
517
603
|
}
|
|
518
604
|
catch (factoryErr) {
|
|
519
605
|
await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (env-factory-throw leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
|
|
@@ -552,6 +638,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
552
638
|
if (spec.signal?.aborted)
|
|
553
639
|
abortController.abort();
|
|
554
640
|
try {
|
|
641
|
+
const envLifetime = resolveEnvLifetimeExpiry(executionEnv, ownedEnvObservedAt);
|
|
642
|
+
if (envLifetime !== undefined && "unanchored" in envLifetime) {
|
|
643
|
+
deps.onError?.(new Error("ExecutionEnv.lifetimeMs is declared but NOT enforceable: this env was not minted by the run's executionEnvFactory and reports no `lifetimeStartedAt`, so the engine cannot tell how old it is. The run will NOT suspend before the platform reclaims the environment — have the adapter report `lifetimeStartedAt`."), { phase: "config", sessionId });
|
|
644
|
+
}
|
|
645
|
+
const envLifetimeSuspendAt = envLifetime !== undefined && "expiresAt" in envLifetime ? envLifetime.expiresAt - ENV_LIFETIME_SUSPEND_MARGIN_MS : undefined;
|
|
555
646
|
if (internals?.isolation === "worktree") {
|
|
556
647
|
const failIsolation = (reason) => {
|
|
557
648
|
const e = new Error(`isolation "worktree" was requested but cannot be honored: ${reason}. The sub-agent was not started (fail-closed — no silent fallback to the shared working tree).`);
|
|
@@ -1034,12 +1125,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1034
1125
|
};
|
|
1035
1126
|
const priorSuspendCount = resume?.priorSuspendCount ?? 0;
|
|
1036
1127
|
const maxSuspends = spec.maxSuspends ?? deps.maxSuspends ?? DEFAULT_MAX_SUSPENDS;
|
|
1037
|
-
const maxSlices = spec.resourceSuspend?.maxSlices
|
|
1128
|
+
const maxSlices = spec.resourceSuspend?.maxSlices;
|
|
1038
1129
|
const priorLedger = resume?.priorLedger;
|
|
1039
1130
|
const resourceTotal = spec.resourceSuspend
|
|
1040
1131
|
? {
|
|
1041
1132
|
totalBudgetMicroUsd: spec.resourceSuspend.totalBudgetUsd !== undefined ? Math.round(spec.resourceSuspend.totalBudgetUsd * 1e6) : undefined,
|
|
1042
|
-
|
|
1133
|
+
totalTokens: spec.resourceSuspend.totalTokens,
|
|
1043
1134
|
}
|
|
1044
1135
|
: undefined;
|
|
1045
1136
|
const budgetSnapshot = {
|
|
@@ -1058,6 +1149,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1058
1149
|
let worktreeSessionRef;
|
|
1059
1150
|
let backgroundTaskToolsActive = false;
|
|
1060
1151
|
let suspendForResource;
|
|
1152
|
+
let suspendForPlatformLimit;
|
|
1061
1153
|
let suspendForReview;
|
|
1062
1154
|
const outputRef = {};
|
|
1063
1155
|
if (resume?.seed.outputRef) {
|
|
@@ -1212,29 +1304,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1212
1304
|
}
|
|
1213
1305
|
tools.push(...a2a.tools.map((t) => remoteToolOffload(t)));
|
|
1214
1306
|
foldProtocolAxes(a2a.toolAxes, "A2A");
|
|
1215
|
-
const callCapOn = spec.limits?.callCapByDeadline !== false;
|
|
1216
|
-
const callCapRef = spec.limits?.timeoutSec &&
|
|
1217
|
-
spec.limits.timeoutSec > 0 &&
|
|
1218
|
-
(callCapOn || spec.limits.gracefulFinalize !== false)
|
|
1219
|
-
? createCallCapRef()
|
|
1220
|
-
: undefined;
|
|
1221
|
-
if (callCapRef) {
|
|
1222
|
-
callCapRef.staticCapTokens =
|
|
1223
|
-
spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
|
|
1224
|
-
? spec.limits.maxOutputTokens
|
|
1225
|
-
: model.maxTokens;
|
|
1226
|
-
}
|
|
1227
|
-
const cutKills = callCapRef ? createCutKillRegistry() : undefined;
|
|
1228
1307
|
const callIssuedAtRef = {};
|
|
1229
|
-
const execClamp = callCapRef
|
|
1230
|
-
? {
|
|
1231
|
-
deadlineMs: () => softExecDeadlineMs(callCapRef),
|
|
1232
|
-
onClamp: () => {
|
|
1233
|
-
callCapRef.toolClamps = (callCapRef.toolClamps ?? 0) + 1;
|
|
1234
|
-
},
|
|
1235
|
-
...(cutKills ? { registerKillHandle: cutKills.register } : {}),
|
|
1236
|
-
}
|
|
1237
|
-
: undefined;
|
|
1238
1308
|
const memoryWriteGateRef = {};
|
|
1239
1309
|
const additionalRootsCanonical = [];
|
|
1240
1310
|
let attachmentRootCanonical;
|
|
@@ -1317,7 +1387,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1317
1387
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1318
1388
|
...(internals?.onTaskNotification !== undefined ? { taskNotification: internals.onTaskNotification } : {}),
|
|
1319
1389
|
...(internals?.detachHub !== undefined ? { detachHub: internals.detachHub } : {}),
|
|
1320
|
-
...(execClamp !== undefined ? { execClamp } : {}),
|
|
1321
1390
|
pdfModelCapabilities: pdfModelCapabilitiesOf(model),
|
|
1322
1391
|
...(deps.hands?.bashReadonlyAllow !== undefined ? { bashReadonlyAllow: deps.hands.bashReadonlyAllow } : {}),
|
|
1323
1392
|
...(deps.hands?.commitCoAuthor !== undefined ? { commitCoAuthor: deps.hands.commitCoAuthor } : {}),
|
|
@@ -1366,7 +1435,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1366
1435
|
if (backgroundTaskToolsActive || workflowToolsActive) {
|
|
1367
1436
|
toolEffects.set("TaskOutput", "read");
|
|
1368
1437
|
toolEffects.set("TaskStop", "write");
|
|
1369
|
-
tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore
|
|
1438
|
+
tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
|
|
1370
1439
|
if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
|
|
1371
1440
|
const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
|
|
1372
1441
|
const reviveSpawn = delegationForRevive !== undefined && deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined
|
|
@@ -2137,46 +2206,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2137
2206
|
...(spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
|
|
2138
2207
|
? { maxOutputTokens: spec.limits.maxOutputTokens }
|
|
2139
2208
|
: {}),
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
return undefined;
|
|
2145
|
-
const cap = computeCallCap(callCapRef, Date.now(), (thinking ?? "off") !== "off");
|
|
2146
|
-
if (cap !== undefined)
|
|
2147
|
-
callCapRef.shrinks = (callCapRef.shrinks ?? 0) + 1;
|
|
2148
|
-
return cap;
|
|
2149
|
-
},
|
|
2150
|
-
}
|
|
2151
|
-
: {}),
|
|
2152
|
-
...(callCapRef !== undefined
|
|
2153
|
-
? {
|
|
2154
|
-
callDeadlineMsPerCall: () => {
|
|
2155
|
-
callIssuedAtRef.current = Date.now();
|
|
2156
|
-
return softExecDeadlineMs(callCapRef);
|
|
2157
|
-
},
|
|
2158
|
-
stallTimeoutsPerCall: () => softExecDeadlineMs(callCapRef) !== undefined
|
|
2159
|
-
? { connectMs: WALLTIME_STALL_CONNECT_MS, firstTokenMs: WALLTIME_STALL_FIRST_TOKEN_MS, idleMs: WALLTIME_STALL_IDLE_MS }
|
|
2160
|
-
: undefined,
|
|
2161
|
-
}
|
|
2162
|
-
: {}),
|
|
2163
|
-
...(callCapRef !== undefined
|
|
2164
|
-
? {
|
|
2165
|
-
toolDeadline: {
|
|
2166
|
-
deadlineMs: () => toolCutDeadlineMs(callCapRef, Date.now()),
|
|
2167
|
-
onCut: (info) => {
|
|
2168
|
-
callCapRef.toolCuts = (callCapRef.toolCuts ?? 0) + 1;
|
|
2169
|
-
if (callCapRef.finalizeMode === true && cutKills !== undefined) {
|
|
2170
|
-
cutKills.killFor(info.toolCallId);
|
|
2171
|
-
}
|
|
2172
|
-
callCapRef.pendingCutTools = (callCapRef.pendingCutTools ?? 0) + 1;
|
|
2173
|
-
void info.settled.then(() => {
|
|
2174
|
-
callCapRef.pendingCutTools = (callCapRef.pendingCutTools ?? 0) - 1;
|
|
2175
|
-
});
|
|
2176
|
-
},
|
|
2177
|
-
},
|
|
2178
|
-
}
|
|
2179
|
-
: {}),
|
|
2209
|
+
stallTimeoutsPerCall: () => {
|
|
2210
|
+
callIssuedAtRef.current = Date.now();
|
|
2211
|
+
return { connectMs: STALL_CONNECT_MS, firstTokenMs: STALL_FIRST_TOKEN_MS, idleMs: STALL_IDLE_MS };
|
|
2212
|
+
},
|
|
2180
2213
|
...(spec.resilience !== undefined ? { resilience: spec.resilience } : {}),
|
|
2181
2214
|
loopTrace: (step) => {
|
|
2182
2215
|
if (step.kind !== "continue")
|
|
@@ -2642,11 +2675,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2642
2675
|
const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
|
|
2643
2676
|
const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
|
|
2644
2677
|
const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
|
|
2645
|
-
const
|
|
2646
|
-
(spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
|
|
2678
|
+
const durableSuspendInfraReady = (spec.checkpointStore ?? deps.checkpointStore) !== undefined &&
|
|
2647
2679
|
!(offloadStore !== undefined && isVolatileOffloadStore(offloadStore)) &&
|
|
2648
2680
|
(ownedEnv === undefined || isRemoteExecutionEnv(ownedEnv)) &&
|
|
2649
2681
|
incompleteSuspendAdapter === undefined;
|
|
2682
|
+
const resourceSuspendEligible = spec.resourceSuspend !== undefined && durableSuspendInfraReady;
|
|
2650
2683
|
if (spec.resourceSuspend !== undefined && !resourceSuspendEligible) {
|
|
2651
2684
|
const why = (spec.checkpointStore ?? deps.checkpointStore) === undefined
|
|
2652
2685
|
? "no CheckpointStore is wired"
|
|
@@ -2660,7 +2693,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2660
2693
|
sessionId,
|
|
2661
2694
|
});
|
|
2662
2695
|
}
|
|
2663
|
-
|
|
2696
|
+
const usageGovernance = (() => {
|
|
2697
|
+
if (usageWindows === undefined || usageWindows.length === 0)
|
|
2698
|
+
return undefined;
|
|
2699
|
+
const store = deps.usageWindowStore;
|
|
2700
|
+
if (store === undefined) {
|
|
2701
|
+
deps.onError?.(new Error("RunnerDeps.usageWindows is set but INACTIVE: no `usageWindowStore` is wired, so cross-task usage cannot be counted and no window can be enforced. Wire InMemoryUsageWindowStore (process-local) or FileUsageWindowStore (restart-surviving)."), { phase: "config", sessionId });
|
|
2702
|
+
return undefined;
|
|
2703
|
+
}
|
|
2704
|
+
const key = spec.principal || GLOBAL_USAGE_KEY;
|
|
2705
|
+
const windows = usageWindows;
|
|
2706
|
+
let charged = 0;
|
|
2707
|
+
return {
|
|
2708
|
+
key,
|
|
2709
|
+
async check(now) {
|
|
2710
|
+
return usageRetryAfterMs(await store.read(key, windows, now));
|
|
2711
|
+
},
|
|
2712
|
+
async commit(cumulativeTokens, now) {
|
|
2713
|
+
const delta = cumulativeTokens - charged;
|
|
2714
|
+
if (delta <= 0)
|
|
2715
|
+
return;
|
|
2716
|
+
await store.charge(key, delta, now, windows);
|
|
2717
|
+
charged = cumulativeTokens;
|
|
2718
|
+
},
|
|
2719
|
+
};
|
|
2720
|
+
})();
|
|
2721
|
+
const platformSuspendArmed = durableSuspendInfraReady && (envLifetimeSuspendAt !== undefined || usageGovernance !== undefined);
|
|
2722
|
+
if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
|
|
2664
2723
|
const adjudicate = effectivePolicy
|
|
2665
2724
|
? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot }, abortController.signal)), abortController.signal, () => ({
|
|
2666
2725
|
action: "deny",
|
|
@@ -2883,6 +2942,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2883
2942
|
ref.scope = scope;
|
|
2884
2943
|
};
|
|
2885
2944
|
const suspendLoopCapHit = (count, cap, detail) => {
|
|
2945
|
+
if (cap === undefined)
|
|
2946
|
+
return false;
|
|
2886
2947
|
if (count + 1 <= cap)
|
|
2887
2948
|
return false;
|
|
2888
2949
|
suspendLoopRef.hit = true;
|
|
@@ -2903,73 +2964,74 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2903
2964
|
const { snapshotId: _lineage, ...identity } = env.workspaceHandle();
|
|
2904
2965
|
return { ...identity, restoreMode: "park_only" };
|
|
2905
2966
|
};
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
return false;
|
|
2929
|
-
}
|
|
2930
|
-
remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
|
|
2931
|
-
}
|
|
2932
|
-
else if (parkOnlyRemoteEnv !== undefined) {
|
|
2933
|
-
if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
|
|
2934
|
-
await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
|
|
2935
|
-
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
2936
|
-
}
|
|
2937
|
-
const token = mintCheckpointToken();
|
|
2938
|
-
const gate = { kind: "resource_limit", reason };
|
|
2939
|
-
const resourceLedgerOut = debitLedger(priorLedger, sliceSpend, resourceTotal);
|
|
2940
|
-
const mintedAt = Date.now();
|
|
2941
|
-
const cp = {
|
|
2942
|
-
token,
|
|
2943
|
-
version: resourceLedgerOut.totalWalltimeSec !== undefined ? WALLTIME_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
|
|
2944
|
-
scope: rs.scope,
|
|
2945
|
-
sessionId,
|
|
2946
|
-
leafId,
|
|
2947
|
-
gate,
|
|
2948
|
-
pendingAction: { kind: "resource_limit", reason },
|
|
2949
|
-
state: serializeCheckpointState(remoteHandle),
|
|
2950
|
-
status: "pending",
|
|
2951
|
-
createdAt: mintedAt,
|
|
2952
|
-
deadline: mintedAt + (sanitizedTtlMs(rs.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
|
|
2953
|
-
suspendCount: priorSuspendCount,
|
|
2954
|
-
humanReview: humanReviewRef.count > 0
|
|
2955
|
-
? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
|
|
2956
|
-
: undefined,
|
|
2957
|
-
resourceLedger: resourceLedgerOut,
|
|
2958
|
-
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
2959
|
-
};
|
|
2960
|
-
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
2967
|
+
const mintResourceSuspend = async (reason, sliceSpend, hint) => {
|
|
2968
|
+
const rs = spec.resourceSuspend;
|
|
2969
|
+
if (!checkpointStore)
|
|
2970
|
+
return false;
|
|
2971
|
+
const scope = rs?.scope ?? checkpointScopeOf(spec);
|
|
2972
|
+
if (abortController.signal.aborted)
|
|
2973
|
+
return false;
|
|
2974
|
+
if (suspendLoopCapHit(priorLedger?.sliceCount ?? 0, maxSlices, " (resource slice limit)"))
|
|
2975
|
+
return false;
|
|
2976
|
+
const leafId = await session.getLeafId();
|
|
2977
|
+
if (!leafId)
|
|
2978
|
+
return false;
|
|
2979
|
+
if (abortController.signal.aborted)
|
|
2980
|
+
return false;
|
|
2981
|
+
let remoteHandle;
|
|
2982
|
+
if (suspendableEnv !== undefined) {
|
|
2983
|
+
if (hasBackgroundShell(suspendableEnv))
|
|
2984
|
+
await sweepBackgroundShells(suspendableEnv, defaultTaskRegistry);
|
|
2985
|
+
const snap = await suspendableEnv.suspendVM({ abortSignal: abortController.signal });
|
|
2986
|
+
if (!snap.ok) {
|
|
2987
|
+
remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
|
|
2988
|
+
deps.onError?.(new Error(`resource suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }), { phase: "config", sessionId });
|
|
2961
2989
|
return false;
|
|
2962
|
-
publishCommittedSuspend(token, gate, rs.scope, remoteHandle);
|
|
2963
|
-
try {
|
|
2964
|
-
await sessions.pin?.(sessionId);
|
|
2965
2990
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2991
|
+
remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
|
|
2992
|
+
}
|
|
2993
|
+
else if (parkOnlyRemoteEnv !== undefined) {
|
|
2994
|
+
if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
|
|
2995
|
+
await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
|
|
2996
|
+
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
2997
|
+
}
|
|
2998
|
+
const token = mintCheckpointToken();
|
|
2999
|
+
const gate = { kind: "resource_limit", reason, ...(hint !== undefined ? { resumeAfterMs: hint.resumeAfterMs } : {}) };
|
|
3000
|
+
const resourceLedgerOut = debitLedger(priorLedger, sliceSpend, resourceTotal);
|
|
3001
|
+
const mintedAt = Date.now();
|
|
3002
|
+
const cp = {
|
|
3003
|
+
token,
|
|
3004
|
+
version: resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
|
|
3005
|
+
scope,
|
|
3006
|
+
sessionId,
|
|
3007
|
+
leafId,
|
|
3008
|
+
gate,
|
|
3009
|
+
pendingAction: { kind: "resource_limit", reason },
|
|
3010
|
+
state: serializeCheckpointState(remoteHandle),
|
|
3011
|
+
status: "pending",
|
|
3012
|
+
createdAt: mintedAt,
|
|
3013
|
+
deadline: mintedAt + Math.max(sanitizedTtlMs(rs?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS, hint !== undefined ? hint.resumeAfterMs + USAGE_WINDOW_REAP_MARGIN_MS : 0),
|
|
3014
|
+
suspendCount: priorSuspendCount,
|
|
3015
|
+
humanReview: humanReviewRef.count > 0
|
|
3016
|
+
? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
|
|
3017
|
+
: undefined,
|
|
3018
|
+
resourceLedger: resourceLedgerOut,
|
|
3019
|
+
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3020
|
+
};
|
|
3021
|
+
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
3022
|
+
return false;
|
|
3023
|
+
publishCommittedSuspend(token, gate, scope, remoteHandle);
|
|
3024
|
+
try {
|
|
3025
|
+
await sessions.pin?.(sessionId);
|
|
2971
3026
|
}
|
|
2972
|
-
|
|
3027
|
+
catch (pinErr) {
|
|
3028
|
+
deps.onError?.(pinErr, { phase: "config", sessionId });
|
|
3029
|
+
}
|
|
3030
|
+
harness.requestStopAfterTurn();
|
|
3031
|
+
return true;
|
|
3032
|
+
};
|
|
3033
|
+
suspendForResource = resourceSuspendEligible ? mintResourceSuspend : undefined;
|
|
3034
|
+
suspendForPlatformLimit = durableSuspendInfraReady ? mintResourceSuspend : undefined;
|
|
2973
3035
|
suspendForReview = checkpointStore
|
|
2974
3036
|
? async (reason) => {
|
|
2975
3037
|
if (!checkpointStore)
|
|
@@ -3021,7 +3083,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3021
3083
|
const mintedAt = Date.now();
|
|
3022
3084
|
const cp = {
|
|
3023
3085
|
token,
|
|
3024
|
-
version: reviewLedger.
|
|
3086
|
+
version: reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
3025
3087
|
scope,
|
|
3026
3088
|
sessionId,
|
|
3027
3089
|
leafId,
|
|
@@ -3146,7 +3208,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3146
3208
|
const mintedAt = Date.now();
|
|
3147
3209
|
cp = {
|
|
3148
3210
|
token,
|
|
3149
|
-
version: approvalLedger.
|
|
3211
|
+
version: approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
3150
3212
|
scope,
|
|
3151
3213
|
sessionId,
|
|
3152
3214
|
leafId,
|
|
@@ -3537,7 +3599,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3537
3599
|
: undefined;
|
|
3538
3600
|
overheadState.promptChars = systemPrompt.length;
|
|
3539
3601
|
const preparedHolder = {};
|
|
3540
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(
|
|
3602
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3541
3603
|
const prepared = buildPrepared();
|
|
3542
3604
|
preparedHolder.current = prepared;
|
|
3543
3605
|
return prepared;
|
|
@@ -5,8 +5,6 @@ import { type TaskOutcome } from "../task-outcome.js";
|
|
|
5
5
|
import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
|
|
6
6
|
import type { SessionStore } from "../session.js";
|
|
7
7
|
import type { AgentDefinition, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
|
|
8
|
-
import { DEFAULT_MAX_TURNS } from "../../config/defaults.js";
|
|
9
|
-
export { DEFAULT_MAX_TURNS };
|
|
10
8
|
export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
|
|
11
9
|
interface ResumeRun {
|
|
12
10
|
cp: Checkpoint;
|