@sema-agent/core 5.34.0 → 5.36.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 +104 -0
- package/dist/agents/subagent.js +29 -2
- package/dist/core/auto-compaction.d.ts +23 -0
- package/dist/core/auto-compaction.js +8 -0
- package/dist/core/checkpoint-store.d.ts +49 -4
- package/dist/core/context-guard.d.ts +41 -0
- package/dist/core/context-guard.js +76 -0
- package/dist/core/hooks.d.ts +98 -3
- package/dist/core/hooks.js +146 -8
- package/dist/core/memory-engine/engine.js +1 -1
- package/dist/core/park-selfcheck.d.ts +161 -0
- package/dist/core/park-selfcheck.js +251 -0
- package/dist/core/runner/assemble-result.d.ts +3 -0
- package/dist/core/runner/assemble-result.js +3 -0
- package/dist/core/runner/git-status-frame.d.ts +219 -0
- package/dist/core/runner/git-status-frame.js +212 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +6 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
- package/dist/core/runner/prepare-task.d.ts +28 -4
- package/dist/core/runner/prepare-task.js +86 -52
- package/dist/core/runner/runtask.d.ts +6 -1
- package/dist/core/runner/runtask.js +330 -19
- package/dist/core/task-registry-agent.d.ts +15 -0
- package/dist/core/task-registry-agent.js +9 -0
- package/dist/core/task-registry.d.ts +3 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-policy.d.ts +125 -0
- package/dist/core/tool-policy.js +35 -2
- package/dist/core/types.d.ts +98 -9
- package/dist/engine/harness/types.d.ts +65 -1
- package/dist/engine/harness/types.js +20 -0
- package/dist/engine/session/import-validate.js +10 -1
- package/dist/engine/session/session.d.ts +37 -1
- package/dist/engine/session/session.js +56 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/internal/harness-types.d.ts +1 -0
- package/dist/internal/harness.d.ts +2 -0
- package/dist/internal/harness.js +2 -0
- package/dist/orchestration/workflow.d.ts +1 -1
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompts/default.d.ts +20 -7
- package/dist/prompts/default.js +2 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -1
|
@@ -2,7 +2,9 @@ import { persistedReadDenyEntryProblem } from "../../tools/fs/read-deny.js";
|
|
|
2
2
|
import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
|
|
3
3
|
import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
|
|
4
4
|
import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
5
|
-
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
5
|
+
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
6
|
+
import { GIT_STATUS_ECHO_PREVIEW, branchCarriesVisiblePositiveGitFrame, newestEngineGitFrame, stripGitStatusUnits } from "./git-status-frame.js";
|
|
7
|
+
import { gitFrameContextVisible, normalizeGitAnnouncement } from "../../internal/harness.js";
|
|
6
8
|
import { engineVersion } from "../version.js";
|
|
7
9
|
import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
|
|
8
10
|
import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
|
|
@@ -41,7 +43,7 @@ import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntr
|
|
|
41
43
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
42
44
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
43
45
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
44
|
-
import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.js";
|
|
46
|
+
import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
|
|
45
47
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
46
48
|
import { discloseDroppedPending, isDelegatedAgentTerminal, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
47
49
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
@@ -68,6 +70,13 @@ function nextHumanInputSeq(key) {
|
|
|
68
70
|
}
|
|
69
71
|
return ++box.n;
|
|
70
72
|
}
|
|
73
|
+
function sameAcceptedSteerInput(a, b) {
|
|
74
|
+
return (a.payload === b.payload &&
|
|
75
|
+
a.trusted === b.trusted &&
|
|
76
|
+
a.actor?.id === b.actor?.id &&
|
|
77
|
+
a.actor?.hostAsserted === b.actor?.hostAsserted &&
|
|
78
|
+
a.actor?.issuer === b.actor?.issuer);
|
|
79
|
+
}
|
|
71
80
|
const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
|
|
72
81
|
const STOP_HOOK_BLOCK_CAP = 8;
|
|
73
82
|
const COMPACTION_REGROWTH_FACTOR = 1.5;
|
|
@@ -130,7 +139,7 @@ function resumeDecisionWasNegative(resume) {
|
|
|
130
139
|
}
|
|
131
140
|
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
132
141
|
"NOT executed on resume. If you still need it, issue it again now.";
|
|
133
|
-
function toolEndBodyFrom(result, isError, settledBy) {
|
|
142
|
+
function toolEndBodyFrom(result, isError, settledBy, approver) {
|
|
134
143
|
const o = toolOutputFrom(result);
|
|
135
144
|
const st = structuredFrom(result);
|
|
136
145
|
const det = isError ? result?.details : undefined;
|
|
@@ -142,6 +151,7 @@ function toolEndBodyFrom(result, isError, settledBy) {
|
|
|
142
151
|
...(st !== undefined ? { structured: st } : {}),
|
|
143
152
|
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
144
153
|
...(settledBy !== undefined ? { settledBy } : {}),
|
|
154
|
+
...(approver !== undefined ? { approver } : {}),
|
|
145
155
|
};
|
|
146
156
|
}
|
|
147
157
|
export function reconciledToolEndBody(orphan) {
|
|
@@ -860,6 +870,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
860
870
|
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
861
871
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
862
872
|
...runnerHooks.seamCCompactionOptions(prepared),
|
|
873
|
+
...gitRestateOption(prepared),
|
|
863
874
|
...windowSafetyOptions(event.model),
|
|
864
875
|
...runnerHooks.compactionHookOptions(spec, prepared.sessionId, passTrigger),
|
|
865
876
|
});
|
|
@@ -988,6 +999,96 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
988
999
|
};
|
|
989
1000
|
return onTurnBoundary;
|
|
990
1001
|
}
|
|
1002
|
+
function wrapGitFrame(body) {
|
|
1003
|
+
return `<system-reminder>\n${sanitizeUntrustedText(body)}\n</system-reminder>`;
|
|
1004
|
+
}
|
|
1005
|
+
async function resolveGitLegDelivery(prepared, cpMirror, report) {
|
|
1006
|
+
const ref = prepared.gitStatusRef;
|
|
1007
|
+
const frame = ref.frame;
|
|
1008
|
+
if (frame === undefined)
|
|
1009
|
+
return undefined;
|
|
1010
|
+
let prior;
|
|
1011
|
+
let pending = false;
|
|
1012
|
+
try {
|
|
1013
|
+
const branchReadout = await prepared.session.getGitAnnouncement?.();
|
|
1014
|
+
if (branchReadout !== undefined) {
|
|
1015
|
+
if (branchReadout.status === "pending") {
|
|
1016
|
+
pending = true;
|
|
1017
|
+
prior = { kind: branchReadout.kind, hash: branchReadout.hash };
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
prior = { kind: branchReadout.kind, hash: branchReadout.hash, ...(branchReadout.entryId !== undefined ? { entryId: branchReadout.entryId } : {}) };
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
const shaped = normalizeGitAnnouncement(cpMirror);
|
|
1025
|
+
if (shaped !== undefined) {
|
|
1026
|
+
if (shaped.pending === true || shaped.entryId === undefined) {
|
|
1027
|
+
pending = true;
|
|
1028
|
+
prior = { kind: shaped.kind, hash: shaped.hash };
|
|
1029
|
+
}
|
|
1030
|
+
else if (gitFrameContextVisible(await prepared.session.getBranch(), shaped.entryId)) {
|
|
1031
|
+
prior = { kind: shaped.kind, hash: shaped.hash, entryId: shaped.entryId };
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
pending = true;
|
|
1035
|
+
prior = { kind: shaped.kind, hash: shaped.hash };
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
catch (err) {
|
|
1041
|
+
pending = true;
|
|
1042
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
1043
|
+
}
|
|
1044
|
+
if (prior !== undefined && !pending && prior.entryId !== undefined) {
|
|
1045
|
+
try {
|
|
1046
|
+
const newest = newestEngineGitFrame(await prepared.session.getBranch());
|
|
1047
|
+
if (newest === undefined || newest.entryId !== prior.entryId)
|
|
1048
|
+
pending = true;
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
pending = true;
|
|
1052
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (prior !== undefined)
|
|
1056
|
+
ref.announced = pending ? { ...prior, pending: true } : { ...prior };
|
|
1057
|
+
const negative = frame.kind === "unavailable" || frame.kind === "non-repo";
|
|
1058
|
+
if (negative) {
|
|
1059
|
+
if (prior === undefined && !pending) {
|
|
1060
|
+
let mustDisown;
|
|
1061
|
+
try {
|
|
1062
|
+
mustDisown = branchCarriesVisiblePositiveGitFrame(await prepared.session.getBranch());
|
|
1063
|
+
}
|
|
1064
|
+
catch (err) {
|
|
1065
|
+
mustDisown = true;
|
|
1066
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
1067
|
+
}
|
|
1068
|
+
return mustDisown ? frame.body : undefined;
|
|
1069
|
+
}
|
|
1070
|
+
if (prior !== undefined && !pending && prior.kind === frame.kind && prior.hash === frame.hash)
|
|
1071
|
+
return undefined;
|
|
1072
|
+
return frame.body;
|
|
1073
|
+
}
|
|
1074
|
+
if (!pending && prior !== undefined && prior.kind === frame.kind && prior.hash === frame.hash) {
|
|
1075
|
+
ref.protectedText = wrapGitFrame(frame.body);
|
|
1076
|
+
if (frame.kind === "full" && frame.shrunk !== undefined) {
|
|
1077
|
+
ref.wrappedShrink = { find: ref.protectedText, replace: wrapGitFrame(frame.shrunk.body) };
|
|
1078
|
+
}
|
|
1079
|
+
return undefined;
|
|
1080
|
+
}
|
|
1081
|
+
return frame.body;
|
|
1082
|
+
}
|
|
1083
|
+
function gitRestateOption(prepared) {
|
|
1084
|
+
const ref = prepared.gitStatusRef;
|
|
1085
|
+
if (ref.frame === undefined || ref.announced === undefined || ref.reassert === undefined)
|
|
1086
|
+
return {};
|
|
1087
|
+
const use = ref.overBudgetShrunk && ref.frame.shrunk !== undefined
|
|
1088
|
+
? { kind: "degraded", hash: ref.frame.shrunk.hash }
|
|
1089
|
+
: { kind: ref.frame.kind, hash: ref.frame.hash };
|
|
1090
|
+
return { gitRestate: { pending: use, land: ref.reassert } };
|
|
1091
|
+
}
|
|
991
1092
|
function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
992
1093
|
const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs } = deps;
|
|
993
1094
|
const internalsNotifier = createSafeNotifier({
|
|
@@ -1032,6 +1133,19 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1032
1133
|
if (event.entryId !== undefined && (m.role === "user" || m.role === "assistant" || m.role === "toolResult")) {
|
|
1033
1134
|
emitCommitted(event.entryId, m.role, m.role === "toolResult" ? m.toolCallId : undefined);
|
|
1034
1135
|
}
|
|
1136
|
+
if (event.entryId !== undefined && m.role === "user" && prepared.gitStatusRef.pendingReceipt !== undefined) {
|
|
1137
|
+
const pr = prepared.gitStatusRef.pendingReceipt;
|
|
1138
|
+
const c = m.content;
|
|
1139
|
+
const text = typeof c === "string"
|
|
1140
|
+
? c
|
|
1141
|
+
: Array.isArray(c) && c.length >= 1 && c[0].type === "text"
|
|
1142
|
+
? c[0].text
|
|
1143
|
+
: undefined;
|
|
1144
|
+
if (text === pr.text) {
|
|
1145
|
+
delete prepared.gitStatusRef.pendingReceipt;
|
|
1146
|
+
pr.commit(event.entryId);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1035
1149
|
if (rs.degrade.degraded === undefined && m.role === "assistant") {
|
|
1036
1150
|
const deg = readDegradation(m);
|
|
1037
1151
|
if (deg)
|
|
@@ -1213,16 +1327,16 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1213
1327
|
prepared.lspDiagnostics.nudge(p);
|
|
1214
1328
|
}
|
|
1215
1329
|
}
|
|
1216
|
-
const
|
|
1217
|
-
if (
|
|
1218
|
-
prepared.
|
|
1330
|
+
const settlement = prepared.approvalSettlement.get(event.toolCallId);
|
|
1331
|
+
if (settlement !== undefined)
|
|
1332
|
+
prepared.approvalSettlement.delete(event.toolCallId);
|
|
1219
1333
|
pushContent({
|
|
1220
1334
|
type: "tool_end",
|
|
1221
1335
|
toolCallId: event.toolCallId,
|
|
1222
1336
|
toolName: event.toolName,
|
|
1223
1337
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1224
1338
|
isError: event.isError,
|
|
1225
|
-
...toolEndBodyFrom(event.result, event.isError, settledBy),
|
|
1339
|
+
...toolEndBodyFrom(event.result, event.isError, settlement?.settledBy, settlement?.approver),
|
|
1226
1340
|
...ident(),
|
|
1227
1341
|
});
|
|
1228
1342
|
announceWorkspaceMove();
|
|
@@ -1253,6 +1367,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1253
1367
|
type: "task_progress",
|
|
1254
1368
|
taskId: rs.telemetry.taskId,
|
|
1255
1369
|
...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
|
|
1370
|
+
...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
|
|
1256
1371
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
1257
1372
|
...(subagentName ? { name: subagentName } : {}),
|
|
1258
1373
|
usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
|
|
@@ -1444,6 +1559,7 @@ export class Runner {
|
|
|
1444
1559
|
};
|
|
1445
1560
|
let reapHandle;
|
|
1446
1561
|
let steerChain = Promise.resolve();
|
|
1562
|
+
const acceptedSteerInputs = new Map();
|
|
1447
1563
|
const notifyRef = {};
|
|
1448
1564
|
const manualCompactRef = { requested: false, waiters: [] };
|
|
1449
1565
|
const drainManualCompactWaiters = (outcome) => {
|
|
@@ -1565,6 +1681,7 @@ export class Runner {
|
|
|
1565
1681
|
queue.push({ type: "done", result: resultValue });
|
|
1566
1682
|
queue.close();
|
|
1567
1683
|
});
|
|
1684
|
+
void settled.then(() => acceptedSteerInputs.clear(), () => acceptedSteerInputs.clear());
|
|
1568
1685
|
const steeringError = (msg, code = "steering.not_running") => {
|
|
1569
1686
|
const e = new Error(`cannot steer: ${msg}`);
|
|
1570
1687
|
e.code = code;
|
|
@@ -1613,22 +1730,40 @@ export class Runner {
|
|
|
1613
1730
|
return suggestionsDone.catch(() => []);
|
|
1614
1731
|
},
|
|
1615
1732
|
steer: async (text, options) => {
|
|
1616
|
-
|
|
1733
|
+
const trusted = options?.trusted ? true : false;
|
|
1734
|
+
if (trusted && sanitizeUntrustedText(text) !== text) {
|
|
1617
1735
|
throw steeringError("trusted steering text must not contain a </system-reminder> tag", "steering.invalid_content");
|
|
1618
1736
|
}
|
|
1619
|
-
const
|
|
1737
|
+
const inputId = options?.inputId;
|
|
1738
|
+
if (inputId !== undefined) {
|
|
1739
|
+
if (typeof inputId !== "string") {
|
|
1740
|
+
throw steeringError("inputId must be a string when supplied", "steering.invalid_content");
|
|
1741
|
+
}
|
|
1742
|
+
if (inputId === "" || inputId.length > MAX_STEER_INPUT_ID_CHARS) {
|
|
1743
|
+
throw steeringError(`inputId must be a non-empty string of at most ${MAX_STEER_INPUT_ID_CHARS} characters`, "steering.invalid_content");
|
|
1744
|
+
}
|
|
1745
|
+
if (inputId === LEGACY_PENDING_STEER_INPUT_ID) {
|
|
1746
|
+
throw steeringError(`inputId "${LEGACY_PENDING_STEER_INPUT_ID}" is reserved for a pre-queue parked steer and cannot be supplied by a caller`, "steering.invalid_content");
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
const actorIn = options?.actor;
|
|
1750
|
+
const actor = actorIn === undefined ? undefined : snapshotActorAssertion(actorIn);
|
|
1620
1751
|
const projected = projectHumanInput({ text, actor, source: "steer" });
|
|
1621
|
-
const payload =
|
|
1752
|
+
const payload = trusted ? formatHookFeedback(projected) : projected;
|
|
1622
1753
|
const mintsAFrame = payload.trim().length !== 0;
|
|
1623
|
-
const
|
|
1754
|
+
const replay = { payload, trusted, ...(actor !== undefined ? { actor } : {}) };
|
|
1755
|
+
const noteAccepted = (h) => {
|
|
1624
1756
|
if (!mintsAFrame)
|
|
1625
1757
|
return;
|
|
1758
|
+
if (typeof inputId === "string")
|
|
1759
|
+
acceptedSteerInputs.set(inputId, replay);
|
|
1626
1760
|
queue.push({
|
|
1627
1761
|
...buildHumanInputEvent({
|
|
1628
1762
|
carrier: "steer",
|
|
1629
1763
|
source: "steer",
|
|
1630
1764
|
delivery: "queued",
|
|
1631
1765
|
sessionSeq: nextHumanInputSeq(h.harness),
|
|
1766
|
+
...(typeof inputId === "string" ? { inputId } : {}),
|
|
1632
1767
|
...(actor !== undefined ? { actor } : {}),
|
|
1633
1768
|
...(actor?.issuer !== undefined ? { issuer: actor.issuer } : {}),
|
|
1634
1769
|
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
@@ -1645,9 +1780,21 @@ export class Runner {
|
|
|
1645
1780
|
const h = handle ?? (await orTimeout(ready));
|
|
1646
1781
|
if (!h)
|
|
1647
1782
|
throw steeringError("the task is not running");
|
|
1783
|
+
if (typeof inputId === "string") {
|
|
1784
|
+
const prior = acceptedSteerInputs.get(inputId);
|
|
1785
|
+
if (prior !== undefined) {
|
|
1786
|
+
if (resultValue !== undefined || h.loop.ended)
|
|
1787
|
+
throw steeringError("the task is no longer running");
|
|
1788
|
+
if (!sameAcceptedSteerInput(prior, replay)) {
|
|
1789
|
+
throw steeringError("a different steering instruction was already accepted under this inputId — re-issue this one with a fresh inputId " +
|
|
1790
|
+
"(an identical payload would have been an idempotent retry)", "steering.duplicate_input_id");
|
|
1791
|
+
}
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1648
1795
|
try {
|
|
1649
1796
|
await h.harness.steer(payload, { provenance: "engine-note", ...(actor !== undefined ? { actor } : {}) });
|
|
1650
|
-
|
|
1797
|
+
noteAccepted(h);
|
|
1651
1798
|
return;
|
|
1652
1799
|
}
|
|
1653
1800
|
catch (e) {
|
|
@@ -1658,7 +1805,7 @@ export class Runner {
|
|
|
1658
1805
|
while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
|
|
1659
1806
|
try {
|
|
1660
1807
|
await h.harness.steer(payload, { provenance: "engine-note", ...(actor !== undefined ? { actor } : {}) });
|
|
1661
|
-
|
|
1808
|
+
noteAccepted(h);
|
|
1662
1809
|
return;
|
|
1663
1810
|
}
|
|
1664
1811
|
catch (e2) {
|
|
@@ -2135,12 +2282,12 @@ export class Runner {
|
|
|
2135
2282
|
return [];
|
|
2136
2283
|
const full = textOf(e.message);
|
|
2137
2284
|
if (m.engineMinted === true)
|
|
2138
|
-
return [full];
|
|
2285
|
+
return [stripGitStatusUnits(full)];
|
|
2139
2286
|
if (Array.isArray(m.engineSegments) && m.engineSegments.length > 0) {
|
|
2140
|
-
return m.engineSegments.map((s) => full.slice(Math.max(0, s.start), Math.max(0, s.end)));
|
|
2287
|
+
return m.engineSegments.map((s) => stripGitStatusUnits(full.slice(Math.max(0, s.start), Math.max(0, s.end))));
|
|
2141
2288
|
}
|
|
2142
2289
|
if (typeof m.enginePrefixChars === "number" && m.enginePrefixChars > 0)
|
|
2143
|
-
return [full.slice(0, m.enginePrefixChars)];
|
|
2290
|
+
return [stripGitStatusUnits(full.slice(0, m.enginePrefixChars))];
|
|
2144
2291
|
return [];
|
|
2145
2292
|
});
|
|
2146
2293
|
}
|
|
@@ -2652,6 +2799,7 @@ export class Runner {
|
|
|
2652
2799
|
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
2653
2800
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
2654
2801
|
...this.seamCCompactionOptions(prepared),
|
|
2802
|
+
...gitRestateOption(prepared),
|
|
2655
2803
|
...windowSafetyOptions(prepared.harness.getModel()),
|
|
2656
2804
|
...this.compactionHookOptions(spec, prepared.sessionId, "forced"),
|
|
2657
2805
|
});
|
|
@@ -2722,6 +2870,80 @@ export class Runner {
|
|
|
2722
2870
|
recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
|
|
2723
2871
|
},
|
|
2724
2872
|
});
|
|
2873
|
+
prepared.gitStatusRef.reassert = async () => {
|
|
2874
|
+
const ref = prepared.gitStatusRef;
|
|
2875
|
+
const frame = ref.frame;
|
|
2876
|
+
if (frame === undefined)
|
|
2877
|
+
return;
|
|
2878
|
+
delete ref.mirrorOwed;
|
|
2879
|
+
const use = ref.overBudgetShrunk && frame.shrunk !== undefined
|
|
2880
|
+
? { kind: "degraded", body: frame.shrunk.body, hash: frame.shrunk.hash }
|
|
2881
|
+
: { kind: frame.kind, body: frame.body, hash: frame.hash };
|
|
2882
|
+
const wrapped = wrapGitFrame(use.body);
|
|
2883
|
+
try {
|
|
2884
|
+
const entryId = await prepared.session.appendMessage({
|
|
2885
|
+
role: "user",
|
|
2886
|
+
content: [{ type: "text", text: wrapped }],
|
|
2887
|
+
timestamp: Date.now(),
|
|
2888
|
+
engineMinted: true,
|
|
2889
|
+
});
|
|
2890
|
+
ref.announced = { kind: use.kind, hash: use.hash, entryId };
|
|
2891
|
+
ref.protectedText = wrapped;
|
|
2892
|
+
if (use.kind === "full" && frame.shrunk !== undefined) {
|
|
2893
|
+
ref.wrappedShrink = { find: wrapped, replace: wrapGitFrame(frame.shrunk.body) };
|
|
2894
|
+
}
|
|
2895
|
+
else {
|
|
2896
|
+
delete ref.wrappedShrink;
|
|
2897
|
+
}
|
|
2898
|
+
queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[use.kind], ...ident() });
|
|
2899
|
+
rs.attach.attachmentsInjected += 1;
|
|
2900
|
+
try {
|
|
2901
|
+
await prepared.session.appendGitAnnouncement?.({ kind: use.kind, hash: use.hash, entryId });
|
|
2902
|
+
}
|
|
2903
|
+
catch (mirrorErr) {
|
|
2904
|
+
try {
|
|
2905
|
+
this.deps.onError?.(new Error(`git announcement mirror write failed (frame delivered; next leg re-announces): ${mirrorErr instanceof Error ? mirrorErr.message : String(mirrorErr)}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
2906
|
+
}
|
|
2907
|
+
catch {
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
catch (err) {
|
|
2912
|
+
ref.announced = { kind: use.kind, hash: use.hash, pending: true };
|
|
2913
|
+
try {
|
|
2914
|
+
this.deps.onError?.(new Error(`git status frame re-assert append failed (pending; retried at the next boundary): ${err instanceof Error ? err.message : String(err)}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
2915
|
+
}
|
|
2916
|
+
catch {
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
const flushGitMirror = async () => {
|
|
2921
|
+
const owed = prepared.gitStatusRef.mirrorOwed;
|
|
2922
|
+
if (owed === undefined)
|
|
2923
|
+
return;
|
|
2924
|
+
if (prepared.gitStatusRef.overBudgetShrunk && owed.kind === "full") {
|
|
2925
|
+
delete prepared.gitStatusRef.mirrorOwed;
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
delete prepared.gitStatusRef.mirrorOwed;
|
|
2929
|
+
try {
|
|
2930
|
+
await prepared.session.appendGitAnnouncement?.(owed);
|
|
2931
|
+
}
|
|
2932
|
+
catch (mirrorErr) {
|
|
2933
|
+
prepared.gitStatusRef.mirrorOwed = owed;
|
|
2934
|
+
try {
|
|
2935
|
+
this.deps.onError?.(new Error(`git announcement mirror write failed (frame delivered; retried at the next serialization point): ${mirrorErr instanceof Error ? mirrorErr.message : String(mirrorErr)}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
2936
|
+
}
|
|
2937
|
+
catch {
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
const unsubGitRetry = prepared.harness.on("turn_boundary", async () => {
|
|
2942
|
+
await flushGitMirror();
|
|
2943
|
+
if (prepared.gitStatusRef.announced?.pending === true)
|
|
2944
|
+
await prepared.gitStatusRef.reassert?.();
|
|
2945
|
+
return undefined;
|
|
2946
|
+
});
|
|
2725
2947
|
const unsubBoundary = prepared.harness.on("turn_boundary", onTurnBoundary);
|
|
2726
2948
|
const unsub = prepared.harness.subscribe((event) => {
|
|
2727
2949
|
switch (event.type) {
|
|
@@ -2799,6 +3021,25 @@ export class Runner {
|
|
|
2799
3021
|
continuation += "\n\n" + formatHookFeedback(renderOrphanedBackgroundTasks(orphans));
|
|
2800
3022
|
}
|
|
2801
3023
|
}
|
|
3024
|
+
let gitResumeDelivered;
|
|
3025
|
+
{
|
|
3026
|
+
const gitBody = await resolveGitLegDelivery(prepared, resume.cp.state.gitAnnouncement, (err) => {
|
|
3027
|
+
try {
|
|
3028
|
+
this.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
3029
|
+
}
|
|
3030
|
+
catch {
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
const gitFrame = prepared.gitStatusRef.frame;
|
|
3034
|
+
if (gitBody !== undefined && gitFrame !== undefined) {
|
|
3035
|
+
const wrappedGit = wrapGitFrame(gitBody);
|
|
3036
|
+
continuation += "\n\n" + wrappedGit;
|
|
3037
|
+
gitResumeDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, wrapped: wrappedGit };
|
|
3038
|
+
if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
|
|
3039
|
+
prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body) };
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2802
3043
|
const engineSegments = continuation.length > 0 ? [{ start: 0, end: continuation.length }] : [];
|
|
2803
3044
|
const resumeFrames = [
|
|
2804
3045
|
...readPendingSteerQueue(resume.cp.state).map((entry) => ({ entry, source: "steer" })),
|
|
@@ -2828,7 +3069,23 @@ export class Runner {
|
|
|
2828
3069
|
}
|
|
2829
3070
|
if (resume !== undefined)
|
|
2830
3071
|
resume.decisionDelivered = true;
|
|
3072
|
+
if (gitResumeDelivered !== undefined) {
|
|
3073
|
+
const delivered = gitResumeDelivered;
|
|
3074
|
+
prepared.gitStatusRef.protectedText = delivered.wrapped;
|
|
3075
|
+
prepared.gitStatusRef.pendingReceipt = {
|
|
3076
|
+
text: continuation,
|
|
3077
|
+
commit: (entryId) => {
|
|
3078
|
+
prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3079
|
+
queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
|
|
3080
|
+
rs.attach.attachmentsInjected += 1;
|
|
3081
|
+
prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3082
|
+
},
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
2831
3085
|
final = await withBrainSinks(() => prepared.harness.prompt(continuation, { engineSegments }));
|
|
3086
|
+
await flushGitMirror();
|
|
3087
|
+
if (prepared.gitStatusRef.announced?.pending === true)
|
|
3088
|
+
await prepared.gitStatusRef.reassert?.();
|
|
2832
3089
|
}
|
|
2833
3090
|
}
|
|
2834
3091
|
else {
|
|
@@ -2860,6 +3117,27 @@ export class Runner {
|
|
|
2860
3117
|
promptBlocked = true;
|
|
2861
3118
|
}
|
|
2862
3119
|
}
|
|
3120
|
+
let gitLegDelivered;
|
|
3121
|
+
if (!promptBlocked) {
|
|
3122
|
+
const gitBody = await resolveGitLegDelivery(prepared, undefined, (err) => {
|
|
3123
|
+
try {
|
|
3124
|
+
this.deps.onError?.(new Error(`git announcement ladder unreadable (conservative re-announce): ${err.message}`), { phase: "degraded", sessionId: prepared.sessionId });
|
|
3125
|
+
}
|
|
3126
|
+
catch {
|
|
3127
|
+
}
|
|
3128
|
+
});
|
|
3129
|
+
const gitFrame = prepared.gitStatusRef.frame;
|
|
3130
|
+
if (gitBody !== undefined && gitFrame !== undefined) {
|
|
3131
|
+
const wrappedGit = wrapGitFrame(gitBody);
|
|
3132
|
+
const standalone = spec.images !== undefined && spec.images.length > 0;
|
|
3133
|
+
if (!standalone)
|
|
3134
|
+
effectiveObjective = `${wrappedGit}\n${effectiveObjective}`;
|
|
3135
|
+
gitLegDelivered = { kind: gitFrame.kind, hash: gitFrame.hash, standalone, wrapped: wrappedGit };
|
|
3136
|
+
if (gitFrame.kind === "full" && gitFrame.shrunk !== undefined) {
|
|
3137
|
+
prepared.gitStatusRef.wrappedShrink = { find: wrappedGit, replace: wrapGitFrame(gitFrame.shrunk.body) };
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
2863
3141
|
const firstFrames = [];
|
|
2864
3142
|
if (rs.attach.attachState !== undefined) {
|
|
2865
3143
|
const attach = rs.attach.attachState;
|
|
@@ -2897,17 +3175,18 @@ export class Runner {
|
|
|
2897
3175
|
effectiveObjective = `${firstFrames.map((f) => `<system-reminder>\n${sanitizeUntrustedText(f.body)}\n</system-reminder>`).join("\n")}\n${effectiveObjective}`;
|
|
2898
3176
|
}
|
|
2899
3177
|
}
|
|
3178
|
+
const gitQueuedChars = gitLegDelivered !== undefined && gitLegDelivered.standalone ? gitLegDelivered.wrapped.length : 0;
|
|
2900
3179
|
const precallMicroUsd = rs.budget.maxCostMicroUsd === undefined
|
|
2901
3180
|
? 0
|
|
2902
3181
|
: computeCostMicroUsd({
|
|
2903
|
-
totalInputTokens: Math.ceil(effectiveObjective.length / 4),
|
|
3182
|
+
totalInputTokens: Math.ceil((effectiveObjective.length + gitQueuedChars) / 4),
|
|
2904
3183
|
cacheReadTokens: 0,
|
|
2905
3184
|
cacheWriteTokens: 0,
|
|
2906
3185
|
cacheWriteTokensLong: 0,
|
|
2907
3186
|
outputTokens: prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS,
|
|
2908
3187
|
}, rs.telemetry.pricing);
|
|
2909
3188
|
const precallCeilingMicroUsd = prepared.suspendForResource !== undefined ? rs.budget.remainingMicroUsd : rs.budget.maxCostMicroUsd;
|
|
2910
|
-
const precallTokens = Math.ceil(effectiveObjective.length / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
|
|
3189
|
+
const precallTokens = Math.ceil((effectiveObjective.length + gitQueuedChars) / 4) + (prepared.model.maxTokens ?? DEFAULT_PRECALL_OUTPUT_TOKENS);
|
|
2911
3190
|
const precallCeilingTokens = prepared.suspendForResource !== undefined ? rs.budget.remainingTokens : rs.budget.maxTokensWindow;
|
|
2912
3191
|
const entryUsageRetryAfterMs = prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
|
|
2913
3192
|
if (promptBlocked) {
|
|
@@ -2939,6 +3218,21 @@ export class Runner {
|
|
|
2939
3218
|
const images = spec.images
|
|
2940
3219
|
? await Promise.all(spec.images.map((img) => toImageContent(img, this.deps.allowImageUrl)))
|
|
2941
3220
|
: undefined;
|
|
3221
|
+
if (gitLegDelivered !== undefined) {
|
|
3222
|
+
const delivered = gitLegDelivered;
|
|
3223
|
+
if (delivered.standalone)
|
|
3224
|
+
await prepared.harness.nextTurn(delivered.wrapped, { engineMinted: true });
|
|
3225
|
+
prepared.gitStatusRef.protectedText = delivered.wrapped;
|
|
3226
|
+
prepared.gitStatusRef.pendingReceipt = {
|
|
3227
|
+
text: delivered.standalone ? delivered.wrapped : effectiveObjective,
|
|
3228
|
+
commit: (entryId) => {
|
|
3229
|
+
prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3230
|
+
queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
|
|
3231
|
+
rs.attach.attachmentsInjected += 1;
|
|
3232
|
+
prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
3233
|
+
},
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
2942
3236
|
for (const f of firstFrames) {
|
|
2943
3237
|
f.commit();
|
|
2944
3238
|
queue.push({ type: "steering_injected", source: f.source, preview: f.body.slice(0, 220), ...ident() });
|
|
@@ -2964,6 +3258,9 @@ export class Runner {
|
|
|
2964
3258
|
...(enginePrefixChars > 0 ? { enginePrefixChars } : {}),
|
|
2965
3259
|
...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
|
|
2966
3260
|
}));
|
|
3261
|
+
await flushGitMirror();
|
|
3262
|
+
if (prepared.gitStatusRef.announced?.pending === true)
|
|
3263
|
+
await prepared.gitStatusRef.reassert?.();
|
|
2967
3264
|
}
|
|
2968
3265
|
}
|
|
2969
3266
|
loopLatch.ended = true;
|
|
@@ -2993,6 +3290,7 @@ export class Runner {
|
|
|
2993
3290
|
}
|
|
2994
3291
|
notificationLaneLive = false;
|
|
2995
3292
|
unsubscribeTaskNotifications();
|
|
3293
|
+
unsubGitRetry();
|
|
2996
3294
|
unsubBoundary();
|
|
2997
3295
|
unsub();
|
|
2998
3296
|
}
|
|
@@ -3136,6 +3434,7 @@ export class Runner {
|
|
|
3136
3434
|
budgetAxis: rs.limits.budgetAxis,
|
|
3137
3435
|
blockedReason: prepared.blockedRef.reason,
|
|
3138
3436
|
conflict: prepared.conflictRef.hit,
|
|
3437
|
+
gitCoreOverBudget: prepared.gitStatusRef.terminalCode === "irreducible_core_over_budget",
|
|
3139
3438
|
outputInvalid: rs.degrade.outputInvalid,
|
|
3140
3439
|
suspendLoop: prepared.suspendLoopRef.hit,
|
|
3141
3440
|
suspendRef: prepared.suspendRef.token !== undefined && prepared.suspendRef.gate !== undefined
|
|
@@ -3264,6 +3563,7 @@ export class Runner {
|
|
|
3264
3563
|
type: "task_progress",
|
|
3265
3564
|
taskId: rs.telemetry.taskId,
|
|
3266
3565
|
...(internals?.delegationTaskType !== undefined ? { taskType: internals.delegationTaskType } : {}),
|
|
3566
|
+
...(internals?.cycleSeq !== undefined ? { seq: internals.cycleSeq } : {}),
|
|
3267
3567
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
3268
3568
|
...(subagentName ? { name: subagentName } : {}),
|
|
3269
3569
|
usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
|
|
@@ -3664,6 +3964,15 @@ export class Runner {
|
|
|
3664
3964
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" with settledBy "${settledBy}" — a non-human settlement is a fail-closed end (nobody answered), so it cannot be the source of an approval that EXECUTES; ` +
|
|
3665
3965
|
"supply \"human\", or omit the field if the allow came from configuration rather than a person; refusing pre-CAS, the checkpoint stays pending", { field: "settledBy" });
|
|
3666
3966
|
}
|
|
3967
|
+
const approver = decide.approver;
|
|
3968
|
+
const attribution = screenApproverAttribution(approver);
|
|
3969
|
+
if (attribution.defect !== undefined) {
|
|
3970
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries an approver attribution this engine refuses: ${attribution.defect}; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
|
|
3971
|
+
}
|
|
3972
|
+
if (attribution.approver !== undefined && settledBy === undefined) {
|
|
3973
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume names an approver but no settledBy — an attribution says WHOSE settlement it was and needs the word that says what KIND of end it was beside it; ` +
|
|
3974
|
+
`supply settledBy ("human" / "timeout" / "aborted"), or omit the approver; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
|
|
3975
|
+
}
|
|
3667
3976
|
const reason = decide.reason;
|
|
3668
3977
|
assertOutcomeText(reason, "reason");
|
|
3669
3978
|
plainPolicyOutcome = {
|
|
@@ -3675,6 +3984,7 @@ export class Runner {
|
|
|
3675
3984
|
...(reason !== undefined ? { reason } : {}),
|
|
3676
3985
|
...(redeemedAnswer !== undefined ? { answer: redeemedAnswer } : {}),
|
|
3677
3986
|
...(settledBy !== undefined ? { settledBy } : {}),
|
|
3987
|
+
...(attribution.approver !== undefined ? { approver: attribution.approver } : {}),
|
|
3678
3988
|
};
|
|
3679
3989
|
if (plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason && sanitizeUntrustedText(plainPolicyOutcome.reason) !== plainPolicyOutcome.reason) {
|
|
3680
3990
|
throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason must not contain a </system-reminder> tag");
|
|
@@ -4037,7 +4347,7 @@ export class Runner {
|
|
|
4037
4347
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
|
|
4038
4348
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
4039
4349
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
4040
|
-
const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, outcome.settledBy) });
|
|
4350
|
+
const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, outcome.settledBy, outcome.approver) });
|
|
4041
4351
|
if (outcome.decision === "deny") {
|
|
4042
4352
|
const defaultDenial = outcome.settledBy === "timeout"
|
|
4043
4353
|
? `No one answered the approval request for the pending tool call "${pendingAction.toolName}" — the approval window elapsed with no answer, so it was not executed.`
|
|
@@ -4266,6 +4576,7 @@ export class Runner {
|
|
|
4266
4576
|
workingFileAttachments: buildWorkingFileAttachments(spec, prepared),
|
|
4267
4577
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
4268
4578
|
...this.seamCCompactionOptions(prepared),
|
|
4579
|
+
...gitRestateOption(prepared),
|
|
4269
4580
|
...this.compactionHookOptions(spec, prepared.sessionId, "auto"),
|
|
4270
4581
|
});
|
|
4271
4582
|
this.recordCompactionReuse(prepared, finishComp);
|
|
@@ -275,6 +275,21 @@ export declare function resolveBackgroundAgentByNameLane(core: DurableAgentCore,
|
|
|
275
275
|
* alone does NOT retain the child session, so declaring at register time acknowledged parks that
|
|
276
276
|
* a capacity/pin failure (or plain session-scope) could never drain. No-op on unknown ids. */
|
|
277
277
|
export declare function markRetainedContinuationLane(core: DurableAgentCore, id: string): void;
|
|
278
|
+
/**
|
|
279
|
+
* #258 — the stop-cycle generation of the RUN BEING SPAWNED under this row (the same counter the
|
|
280
|
+
* register/revive lanes keep on the handle: fresh spawn = 1, durable-seeded revive = the claimed
|
|
281
|
+
* row's seq, in-memory wake = the bump). The spawn lanes read it ONCE, right after registering, to
|
|
282
|
+
* thread into the child's `RunInternals.cycleSeq` — one authoritative source instead of each lane
|
|
283
|
+
* re-deriving the seed-vs-fresh arithmetic. `undefined` for an unknown id or a non-agent row.
|
|
284
|
+
*
|
|
285
|
+
* PARKED-BORN arm (falsification F1): a parked handle's counter still names the cycle that PARKED —
|
|
286
|
+
* the run this spawn lane is about to drive begins at the consume flip, which installs the bumped
|
|
287
|
+
* value (`flipped.seq = seq + 1`, the §7.2d consume write). Answering the pre-flip number made one
|
|
288
|
+
* resumed run speak two generations (spawn/ticks at n, terminal at n+1), so the parked arm answers
|
|
289
|
+
* the flip's target instead. A resume that loses the consume arbitration never runs, so the answer
|
|
290
|
+
* is never attached to a live cycle it doesn't describe.
|
|
291
|
+
*/
|
|
292
|
+
export declare function backgroundAgentCycleSeqLane(core: DurableAgentCore, id: string): number | undefined;
|
|
278
293
|
/** S2b RB-27② — REVIVE a settled background-agent row for a retained-session RESUME cycle: the
|
|
279
294
|
* row flips back to "running" with a fresh "attaching" channel and a bumped revive-cycle stamp
|
|
280
295
|
* (returned; the resume leg threads it through attach and settle so a stale cycle's late calls
|
|
@@ -867,6 +867,12 @@ export function markRetainedContinuationLane(core, id) {
|
|
|
867
867
|
if (handle && handle.type === "background_agent")
|
|
868
868
|
handle.retainedContinuation = true;
|
|
869
869
|
}
|
|
870
|
+
export function backgroundAgentCycleSeqLane(core, id) {
|
|
871
|
+
const handle = core.handles.get(id);
|
|
872
|
+
if (handle === undefined || handle.type !== "background_agent")
|
|
873
|
+
return undefined;
|
|
874
|
+
return handle.status === "parked" ? (handle.cycleSeq ?? 1) + 1 : handle.cycleSeq;
|
|
875
|
+
}
|
|
870
876
|
async function claimTerminalRowForRevive(core, store, handle, scope) {
|
|
871
877
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
872
878
|
let live;
|
|
@@ -972,6 +978,9 @@ export async function reviveBackgroundAgentLane(core, id, access, abort) {
|
|
|
972
978
|
return { ok: false, reason: "still_running" };
|
|
973
979
|
}
|
|
974
980
|
ensureDurableHeartbeatLane(core);
|
|
981
|
+
if (claim.status === "claimed" && typeof claim.row.seq === "number" && (handle.cycleSeq ?? 1) < claim.row.seq) {
|
|
982
|
+
handle.cycleSeq = claim.row.seq;
|
|
983
|
+
}
|
|
975
984
|
}
|
|
976
985
|
handle.status = "running";
|
|
977
986
|
handle.channelState = "attaching";
|
|
@@ -267,6 +267,9 @@ export declare class TaskRegistry {
|
|
|
267
267
|
suggestion?: string;
|
|
268
268
|
};
|
|
269
269
|
markRetainedContinuation(id: string): void;
|
|
270
|
+
/** #258 — the row's current stop-cycle counter, read by the spawn lanes right after registering to
|
|
271
|
+
* thread into the child's `RunInternals.cycleSeq`; see {@link backgroundAgentCycleSeqLane}. */
|
|
272
|
+
backgroundAgentCycleSeq(id: string): number | undefined;
|
|
270
273
|
/** ASYNC (revive arbitration) — the durable half is a guarded ownership claim on the row (awaited before the
|
|
271
274
|
* in-memory flip), so this leg and a cross-process claim arbitrate in one domain instead of both
|
|
272
275
|
* believing they own the cycle. See {@link reviveBackgroundAgentLane}. */
|