@sema-agent/core 5.8.0 → 5.10.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 +82 -0
- package/dist/agents/cascade.js +24 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/send-message-tool.js +6 -0
- package/dist/agents/subagent.d.ts +33 -0
- package/dist/agents/subagent.js +125 -33
- package/dist/agents/teacher.js +15 -3
- package/dist/agents/team.js +10 -0
- package/dist/agents/verify.js +7 -0
- package/dist/brain/anthropic.js +27 -10
- package/dist/brain/open-responses.d.ts +11 -0
- package/dist/brain/open-responses.js +736 -0
- package/dist/brain/openai.js +32 -5
- package/dist/brain/request-params.d.ts +1 -0
- package/dist/brain/request-params.js +16 -0
- package/dist/core/a2a.js +1 -1
- package/dist/core/fs-write-gate-policy.js +2 -2
- package/dist/core/lsp-diagnostics.d.ts +3 -2
- package/dist/core/lsp-diagnostics.js +20 -7
- package/dist/core/memory-recall.js +8 -3
- package/dist/core/memory.d.ts +5 -0
- package/dist/core/memory.js +6 -4
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +14 -7
- package/dist/core/runner/prepare-task.d.ts +9 -1
- package/dist/core/runner/prepare-task.js +51 -14
- package/dist/core/runner/runtask.d.ts +12 -0
- package/dist/core/runner/runtask.js +153 -42
- package/dist/core/runner/session-file-state-replay.d.ts +7 -0
- package/dist/core/runner/session-file-state-replay.js +56 -0
- package/dist/core/runner/session-rule-policy.d.ts +1 -0
- package/dist/core/runner/session-rule-policy.js +4 -3
- package/dist/core/runner/synthetic-tools.js +1 -1
- package/dist/core/runner/tool-output-projection.js +5 -4
- package/dist/core/session-reconcile.d.ts +7 -3
- package/dist/core/session-reconcile.js +3 -2
- package/dist/core/strategy-store.d.ts +1 -1
- package/dist/core/strategy-store.js +27 -4
- package/dist/core/task-registry-shared.d.ts +0 -1
- package/dist/core/tool-policy.d.ts +8 -0
- package/dist/core/tool-policy.js +11 -0
- package/dist/core/tools.js +9 -1
- package/dist/core/trace.d.ts +0 -2
- package/dist/core/types.d.ts +8 -1
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +3 -0
- package/dist/engine/harness/types.d.ts +1 -0
- package/dist/engine/llm/types.d.ts +2 -73
- package/dist/engine/loop/agent-loop.js +168 -22
- package/dist/engine/loop/types.d.ts +1 -0
- package/dist/engine/session/repo-utils.d.ts +1 -2
- package/dist/engine/session/repo-utils.js +0 -7
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-governance.js +19 -0
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +4 -1
- package/dist/orchestration/workflow.js +15 -6
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +1 -1
- package/dist/stores/file/memory-store.js +3 -7
- package/dist/tools/fs/fs-bash.js +3 -3
- package/dist/tools/fs/fs-shared.d.ts +1 -0
- package/dist/tools/fs/fs-shared.js +4 -0
- package/dist/tools/web.js +20 -20
- package/package.json +5 -3
|
@@ -4,6 +4,7 @@ import type { RunInternals } from "./prepare-task.js";
|
|
|
4
4
|
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
|
+
import { type RecoveredOrphan } from "../session-reconcile.js";
|
|
7
8
|
import type { AgentDefinition, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
|
|
8
9
|
export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
|
|
9
10
|
interface ResumeRun {
|
|
@@ -19,6 +20,17 @@ interface ResumeRun {
|
|
|
19
20
|
onEnvRestoreFailed?: (reason: ReopenReason) => Promise<void>;
|
|
20
21
|
decisionDelivered?: boolean;
|
|
21
22
|
}
|
|
23
|
+
declare function toolEndBodyFrom(result: unknown, isError: boolean): {
|
|
24
|
+
output?: unknown;
|
|
25
|
+
truncated?: boolean;
|
|
26
|
+
totalChars?: number;
|
|
27
|
+
structured?: unknown;
|
|
28
|
+
errorCode?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function reconciledToolEndBody(orphan: Pick<RecoveredOrphan, "text" | "errorKind">): ReturnType<typeof toolEndBodyFrom>;
|
|
31
|
+
export declare const GOVERNANCE_READ_STALLED: unique symbol;
|
|
32
|
+
export declare function awaitChargeWithSlowDisclosure<T>(charge: Promise<T>, onSlow: () => void, discloseAfterMs?: number): Promise<T>;
|
|
33
|
+
export declare function raceUntilDeadline<T>(p: Promise<T>, deadline: number): Promise<T | typeof GOVERNANCE_READ_STALLED>;
|
|
22
34
|
export declare class Runner {
|
|
23
35
|
private deps;
|
|
24
36
|
readonly sessions: SessionStore;
|
|
@@ -24,7 +24,7 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
|
24
24
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
25
25
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
26
26
|
import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated } from "./compaction-call-options.js";
|
|
27
|
-
import { prepareTask } from "./prepare-task.js";
|
|
27
|
+
import { prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
28
28
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
29
29
|
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
30
30
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
@@ -113,6 +113,19 @@ function resumeDecisionWasNegative(resume) {
|
|
|
113
113
|
}
|
|
114
114
|
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
115
115
|
"NOT executed on resume. If you still need it, issue it again now.";
|
|
116
|
+
function toolEndBodyFrom(result, isError) {
|
|
117
|
+
const o = toolOutputFrom(result);
|
|
118
|
+
const st = structuredFrom(result);
|
|
119
|
+
const code = isError ? result?.details?.code : undefined;
|
|
120
|
+
return {
|
|
121
|
+
...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
|
|
122
|
+
...(st !== undefined ? { structured: st } : {}),
|
|
123
|
+
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function reconciledToolEndBody(orphan) {
|
|
127
|
+
return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true);
|
|
128
|
+
}
|
|
116
129
|
function deepJsonEqual(a, b) {
|
|
117
130
|
if (a === b)
|
|
118
131
|
return true;
|
|
@@ -230,6 +243,42 @@ function resumeContinuation(resume) {
|
|
|
230
243
|
`conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
|
|
231
244
|
`exact point, building on the existing results, and finish the remaining work.`);
|
|
232
245
|
}
|
|
246
|
+
const ENV_DUE_GOVERNANCE_READ_BUDGET_MS = 5_000;
|
|
247
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
248
|
+
const DEADLINE_TICK = Symbol("deadline-tick");
|
|
249
|
+
export const GOVERNANCE_READ_STALLED = Symbol("governance-read-stalled");
|
|
250
|
+
const CHARGE_SETTLE_DISCLOSE_MS = 10_000;
|
|
251
|
+
export async function awaitChargeWithSlowDisclosure(charge, onSlow, discloseAfterMs = CHARGE_SETTLE_DISCLOSE_MS) {
|
|
252
|
+
let timer = setTimeout(() => {
|
|
253
|
+
timer = undefined;
|
|
254
|
+
try {
|
|
255
|
+
onSlow();
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
}
|
|
259
|
+
}, discloseAfterMs);
|
|
260
|
+
try {
|
|
261
|
+
return await charge;
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
if (timer !== undefined)
|
|
265
|
+
clearTimeout(timer);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export async function raceUntilDeadline(p, deadline) {
|
|
269
|
+
for (let firstPass = true;; firstPass = false) {
|
|
270
|
+
const remaining = deadline - Date.now();
|
|
271
|
+
if (remaining <= 0 && !firstPass)
|
|
272
|
+
return GOVERNANCE_READ_STALLED;
|
|
273
|
+
let timer;
|
|
274
|
+
const tick = new Promise((res) => {
|
|
275
|
+
timer = setTimeout(() => res(DEADLINE_TICK), Math.max(0, Math.min(remaining, MAX_TIMER_DELAY_MS)));
|
|
276
|
+
});
|
|
277
|
+
const out = await Promise.race([p, tick]).finally(() => clearTimeout(timer));
|
|
278
|
+
if (out !== DEADLINE_TICK)
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
233
282
|
function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
|
|
234
283
|
const message = moment === "entry"
|
|
235
284
|
? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the task was refused before its first model call — nothing ran and nothing was spent. There is no checkpoint to suspend into at this point, so the caller re-submits after the window frees, in ${String(retryAfterMs ?? 0)}ms.`
|
|
@@ -257,17 +306,39 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
257
306
|
let platformRetryAfterMs;
|
|
258
307
|
const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
|
|
259
308
|
if (!preemptWillSuspend && prepared.suspendRef.token === undefined && !prepared.abortController.signal.aborted) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
else if (prepared.usageGovernance !== undefined) {
|
|
309
|
+
const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
|
|
310
|
+
if (prepared.usageGovernance !== undefined) {
|
|
264
311
|
const governance = prepared.usageGovernance;
|
|
265
312
|
try {
|
|
266
313
|
const at = Date.now();
|
|
267
|
-
await governance.commit(stats.tokens, at);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
314
|
+
await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
315
|
+
const windowRead = governance.check(at);
|
|
316
|
+
let answer;
|
|
317
|
+
if (ledgerReadDeadline !== undefined) {
|
|
318
|
+
answer = await raceUntilDeadline(windowRead, ledgerReadDeadline);
|
|
319
|
+
if (answer === GOVERNANCE_READ_STALLED) {
|
|
320
|
+
void windowRead.catch((lateErr) => {
|
|
321
|
+
try {
|
|
322
|
+
runnerHooks.onError?.(lateErr instanceof Error ? lateErr : new Error(String(lateErr)), { phase: "config", sessionId: prepared.sessionId });
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
try {
|
|
328
|
+
runnerHooks.onError?.(new Error("the usage ledger did not answer before the execution environment's suspend deadline — stopping for the environment, the cause this run can still prove. The window state for this boundary is UNKNOWN and was not enforced."), { phase: "config", sessionId: prepared.sessionId });
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
answer = await windowRead;
|
|
336
|
+
}
|
|
337
|
+
if (answer !== GOVERNANCE_READ_STALLED) {
|
|
338
|
+
platformRetryAfterMs = answer;
|
|
339
|
+
if (platformRetryAfterMs !== undefined)
|
|
340
|
+
platformCause = "usage_window";
|
|
341
|
+
}
|
|
271
342
|
}
|
|
272
343
|
catch (govErr) {
|
|
273
344
|
rs.limits.platformTerminal = govErr instanceof Error ? govErr : new Error(String(govErr));
|
|
@@ -276,6 +347,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
276
347
|
return undefined;
|
|
277
348
|
}
|
|
278
349
|
}
|
|
350
|
+
if (platformCause === undefined && prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
|
|
351
|
+
platformCause = "env_lifetime";
|
|
352
|
+
}
|
|
279
353
|
}
|
|
280
354
|
if (platformCause !== undefined) {
|
|
281
355
|
const platformSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
|
|
@@ -380,7 +454,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
380
454
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
381
455
|
}
|
|
382
456
|
if (prepared.lspDiagnostics && !prepared.lspDiagnostics.registry.isEmpty() && !prepared.abortController.signal.aborted) {
|
|
383
|
-
const files = prepared.lspDiagnostics.registry.drain();
|
|
457
|
+
const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
|
|
384
458
|
if (files.length > 0) {
|
|
385
459
|
queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
|
|
386
460
|
const block = formatDiagnosticsBlock(files);
|
|
@@ -913,7 +987,8 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
913
987
|
: {}),
|
|
914
988
|
ts: callEndAt,
|
|
915
989
|
}));
|
|
916
|
-
|
|
990
|
+
const cacheRowUnknown = m.usageMissing === true || m.stopReason === "aborted";
|
|
991
|
+
if (prepared.cacheBreakDetector && prepared.cacheFingerprint && !cacheRowUnknown) {
|
|
917
992
|
const finding = prepared.cacheBreakDetector.observe({
|
|
918
993
|
turn: stats.turns + 1,
|
|
919
994
|
systemPrompt: prepared.cacheFingerprint.systemPrompt,
|
|
@@ -1049,16 +1124,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1049
1124
|
toolName: event.toolName,
|
|
1050
1125
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1051
1126
|
isError: event.isError,
|
|
1052
|
-
...(
|
|
1053
|
-
const o = toolOutputFrom(event.result);
|
|
1054
|
-
return o !== undefined
|
|
1055
|
-
? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) }
|
|
1056
|
-
: {};
|
|
1057
|
-
})(),
|
|
1058
|
-
...(() => {
|
|
1059
|
-
const st = structuredFrom(event.result);
|
|
1060
|
-
return st !== undefined ? { structured: st } : {};
|
|
1061
|
-
})(),
|
|
1127
|
+
...toolEndBodyFrom(event.result, event.isError),
|
|
1062
1128
|
...ident(),
|
|
1063
1129
|
});
|
|
1064
1130
|
announceWorkspaceMove();
|
|
@@ -1312,6 +1378,10 @@ export class Runner {
|
|
|
1312
1378
|
finally {
|
|
1313
1379
|
releaseLock?.();
|
|
1314
1380
|
publishReady(handle);
|
|
1381
|
+
manualCompactRef.closed = true;
|
|
1382
|
+
if (manualCompactRef.waiters.length > 0)
|
|
1383
|
+
manualCompactRef.emitMooted?.("task_ending");
|
|
1384
|
+
manualCompactRef.emitMooted = undefined;
|
|
1315
1385
|
drainManualCompactWaiters("mooted");
|
|
1316
1386
|
}
|
|
1317
1387
|
})();
|
|
@@ -1500,6 +1570,8 @@ export class Runner {
|
|
|
1500
1570
|
throw steeringError("the task is not running");
|
|
1501
1571
|
if (opts?.signal?.aborted)
|
|
1502
1572
|
return "mooted";
|
|
1573
|
+
if (manualCompactRef.closed)
|
|
1574
|
+
return "mooted";
|
|
1503
1575
|
return new Promise((resolve) => {
|
|
1504
1576
|
const signal = opts?.signal;
|
|
1505
1577
|
const entry = {
|
|
@@ -1515,6 +1587,7 @@ export class Runner {
|
|
|
1515
1587
|
if (manualCompactRef.waiters.length === 0) {
|
|
1516
1588
|
manualCompactRef.requested = false;
|
|
1517
1589
|
}
|
|
1590
|
+
manualCompactRef.emitMooted?.("cancelled");
|
|
1518
1591
|
entry.resolve("mooted");
|
|
1519
1592
|
}
|
|
1520
1593
|
};
|
|
@@ -1648,6 +1721,13 @@ export class Runner {
|
|
|
1648
1721
|
}, this);
|
|
1649
1722
|
notificationHarness = prepared.harness;
|
|
1650
1723
|
notificationSessionId = prepared.sessionId;
|
|
1724
|
+
const runSourceTaskId = spec.taskId ?? prepared.sessionId;
|
|
1725
|
+
const parentToolCallId = internals?.parentToolCallId;
|
|
1726
|
+
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: runSourceTaskId } : { eventId: uuidv7() };
|
|
1727
|
+
notificationIdent = ident;
|
|
1728
|
+
manualCompactRef.emitMooted = (reason) => {
|
|
1729
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason, ...ident() });
|
|
1730
|
+
};
|
|
1651
1731
|
prepared.harness.onUndrainedEngineNotes = (payloads) => {
|
|
1652
1732
|
if (notificationSessionId === undefined)
|
|
1653
1733
|
return;
|
|
@@ -1666,7 +1746,7 @@ export class Runner {
|
|
|
1666
1746
|
if (pendingIdle !== undefined) {
|
|
1667
1747
|
for (const payload of discloseDroppedPending(pendingIdle)) {
|
|
1668
1748
|
deliveredAtTurnOpen.add(taskNotificationDedupKey(payload));
|
|
1669
|
-
queue.push({ type: "task_notification", notification: payload });
|
|
1749
|
+
queue.push({ type: "task_notification", notification: payload, ...ident() });
|
|
1670
1750
|
void prepared.harness.nextTurn(renderTaskNotificationXml(payload), { provenance: "engine-note", enginePayload: payload }).catch(() => {
|
|
1671
1751
|
this.pendingSessionNotifications.pend(prepared.sessionId, payload);
|
|
1672
1752
|
});
|
|
@@ -1715,7 +1795,7 @@ export class Runner {
|
|
|
1715
1795
|
rs.limits.outputRetryCap = resolveOutputRetries(spec.outputRetries);
|
|
1716
1796
|
rs.limits.effectiveMaxTurns = resolveMaxTurns(spec.limits);
|
|
1717
1797
|
rs.telemetry.tracer = spec.tracer ?? this.deps.tracer;
|
|
1718
|
-
rs.telemetry.taskId =
|
|
1798
|
+
rs.telemetry.taskId = runSourceTaskId;
|
|
1719
1799
|
if (taskIdRef) {
|
|
1720
1800
|
taskIdRef.current = rs.telemetry.taskId;
|
|
1721
1801
|
taskIdRef.sessionId = prepared.sessionId;
|
|
@@ -2063,9 +2143,6 @@ export class Runner {
|
|
|
2063
2143
|
const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
|
|
2064
2144
|
rs.counters.walltimeSyncBackstopFired = false;
|
|
2065
2145
|
const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
|
|
2066
|
-
const parentToolCallId = internals?.parentToolCallId;
|
|
2067
|
-
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: rs.telemetry.taskId } : { eventId: uuidv7() };
|
|
2068
|
-
notificationIdent = ident;
|
|
2069
2146
|
const pushContent = (e) => {
|
|
2070
2147
|
queue.push(e);
|
|
2071
2148
|
if (parentToolCallId !== undefined && internals?.onForwardEvent) {
|
|
@@ -2113,6 +2190,10 @@ export class Runner {
|
|
|
2113
2190
|
};
|
|
2114
2191
|
const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, fn));
|
|
2115
2192
|
const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
|
|
2193
|
+
for (const orphan of prepared.wakeRecovered) {
|
|
2194
|
+
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
2195
|
+
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
|
|
2196
|
+
}
|
|
2116
2197
|
const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
|
|
2117
2198
|
const batchArgs = postToolBatchHook ? new Map() : undefined;
|
|
2118
2199
|
rs.turn.toolBatch = [];
|
|
@@ -2446,7 +2527,7 @@ export class Runner {
|
|
|
2446
2527
|
if (resume) {
|
|
2447
2528
|
const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
|
|
2448
2529
|
if (!walltimeExhaustedResume && resume.outcome.gate !== "wake") {
|
|
2449
|
-
await this.applyResumeDecision(prepared, resume, (e) =>
|
|
2530
|
+
await this.applyResumeDecision(prepared, resume, (e) => pushContent({ ...e, ...ident() }), emitCommitted, (toolName, details) => {
|
|
2450
2531
|
if (rs.attach.attachState === undefined)
|
|
2451
2532
|
return;
|
|
2452
2533
|
const family = writeFamilyOf(toolName);
|
|
@@ -2638,6 +2719,7 @@ export class Runner {
|
|
|
2638
2719
|
}
|
|
2639
2720
|
finally {
|
|
2640
2721
|
timeout.clear();
|
|
2722
|
+
prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
|
|
2641
2723
|
prepared.abortController.abort();
|
|
2642
2724
|
prepared.releaseSignal();
|
|
2643
2725
|
try {
|
|
@@ -2658,7 +2740,7 @@ export class Runner {
|
|
|
2658
2740
|
}
|
|
2659
2741
|
if (prepared.usageGovernance !== undefined) {
|
|
2660
2742
|
try {
|
|
2661
|
-
await prepared.usageGovernance.commit(stats.tokens, Date.now());
|
|
2743
|
+
await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, Date.now()), () => this.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
2662
2744
|
}
|
|
2663
2745
|
catch (flushErr) {
|
|
2664
2746
|
this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
|
|
@@ -2673,7 +2755,7 @@ export class Runner {
|
|
|
2673
2755
|
try {
|
|
2674
2756
|
const report = await reconcileInterruptedSession(prepared.session, prepared.toolEffects, undefined, startedToolCallIds);
|
|
2675
2757
|
for (const orphan of report.recovered) {
|
|
2676
|
-
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...ident() });
|
|
2758
|
+
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
2677
2759
|
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
|
|
2678
2760
|
}
|
|
2679
2761
|
}
|
|
@@ -2779,6 +2861,7 @@ export class Runner {
|
|
|
2779
2861
|
unpricedSpend: rs.telemetry.unpricedSpend,
|
|
2780
2862
|
rewindNotes: prepared.rewindNotes,
|
|
2781
2863
|
remoteEnvFailures: prepared.remoteEnvFailures,
|
|
2864
|
+
retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
|
|
2782
2865
|
abortedForTimeout: timeout.fired,
|
|
2783
2866
|
abortedForTurns: rs.limits.turnsExceeded,
|
|
2784
2867
|
abortedLive,
|
|
@@ -2870,7 +2953,7 @@ export class Runner {
|
|
|
2870
2953
|
const committedToken = prepared.suspendRef.token ?? prepared.reviewRef.token;
|
|
2871
2954
|
const committedScope = prepared.suspendRef.scope ?? prepared.reviewRef.scope;
|
|
2872
2955
|
if (committedToken !== undefined) {
|
|
2873
|
-
const store = spec
|
|
2956
|
+
const store = resolveCheckpointStore(spec, this.deps);
|
|
2874
2957
|
if (store && committedScope !== undefined) {
|
|
2875
2958
|
onSuspend({
|
|
2876
2959
|
env: prepared.ownedEnv,
|
|
@@ -2926,6 +3009,11 @@ export class Runner {
|
|
|
2926
3009
|
catch {
|
|
2927
3010
|
}
|
|
2928
3011
|
}
|
|
3012
|
+
if (manualCompactRef.waiters.length > 0) {
|
|
3013
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
|
|
3014
|
+
drainManualCompact("mooted");
|
|
3015
|
+
}
|
|
3016
|
+
manualCompactRef.emitMooted = undefined;
|
|
2929
3017
|
queue.push({ type: "done", result });
|
|
2930
3018
|
queue.close();
|
|
2931
3019
|
if (spec.rewindFiles && result.status === "completed") {
|
|
@@ -3114,7 +3202,7 @@ export class Runner {
|
|
|
3114
3202
|
return stream.result();
|
|
3115
3203
|
}
|
|
3116
3204
|
async resumeStream(token, outcome, taskConfig, internals) {
|
|
3117
|
-
const store = taskConfig
|
|
3205
|
+
const store = resolveCheckpointStore(taskConfig, this.deps);
|
|
3118
3206
|
if (!store) {
|
|
3119
3207
|
throw new CheckpointError("checkpoint.not_found", "no CheckpointStore wired — cannot resume (set RunnerDeps.checkpointStore or taskConfig.checkpointStore)");
|
|
3120
3208
|
}
|
|
@@ -3349,13 +3437,16 @@ export class Runner {
|
|
|
3349
3437
|
const completed = new Set(pendingAction.completedCallIds);
|
|
3350
3438
|
const deferredIds = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !completed.has(id));
|
|
3351
3439
|
const names = new Map();
|
|
3440
|
+
const deferredArgs = new Map();
|
|
3352
3441
|
if (deferredIds.length > 0) {
|
|
3353
3442
|
const { messages } = await prepared.session.buildContext();
|
|
3354
3443
|
for (const m of messages) {
|
|
3355
3444
|
if (m.role === "assistant") {
|
|
3356
3445
|
for (const c of m.content) {
|
|
3357
|
-
if (c.type === "toolCall")
|
|
3446
|
+
if (c.type === "toolCall") {
|
|
3358
3447
|
names.set(c.id, c.name);
|
|
3448
|
+
deferredArgs.set(c.id, c.arguments);
|
|
3449
|
+
}
|
|
3359
3450
|
}
|
|
3360
3451
|
}
|
|
3361
3452
|
}
|
|
@@ -3366,7 +3457,12 @@ export class Runner {
|
|
|
3366
3457
|
}
|
|
3367
3458
|
else if (!completed.has(id)) {
|
|
3368
3459
|
const name = names.get(id) ?? "unknown";
|
|
3369
|
-
|
|
3460
|
+
const displayLabel = (() => {
|
|
3461
|
+
const l = prepared.tools.find((t) => t.name === name)?.label;
|
|
3462
|
+
return l !== undefined && l !== name ? { label: l } : {};
|
|
3463
|
+
})();
|
|
3464
|
+
emit({ type: "tool_start", toolCallId: id, toolName: name, ...displayLabel, args: deferredArgs.get(id) ?? {} });
|
|
3465
|
+
emit({ type: "tool_end", toolCallId: id, toolName: name, ...displayLabel, isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE) }, true) });
|
|
3370
3466
|
const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE), true));
|
|
3371
3467
|
emitCommitted(eid, "toolResult", id);
|
|
3372
3468
|
}
|
|
@@ -3380,10 +3476,10 @@ export class Runner {
|
|
|
3380
3476
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput ?? pendingAction.args);
|
|
3381
3477
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
3382
3478
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
3383
|
-
const emitEnd = (isError) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError });
|
|
3479
|
+
const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError) });
|
|
3384
3480
|
if (outcome.decision === "deny") {
|
|
3385
|
-
emitEnd(true);
|
|
3386
3481
|
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason) : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
3482
|
+
emitEnd(true, { content: formatHookFeedback(reason) });
|
|
3387
3483
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
|
|
3388
3484
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3389
3485
|
return;
|
|
@@ -3391,8 +3487,9 @@ export class Runner {
|
|
|
3391
3487
|
if (outcome.decision === "allow" && outcome.updatedInput !== undefined && prepared.basePolicyForResumeEdit) {
|
|
3392
3488
|
const rechecked = refuseOutOfContractDecision(await prepared.basePolicyForResumeEdit.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
|
|
3393
3489
|
if (rechecked.action === "deny") {
|
|
3394
|
-
|
|
3395
|
-
|
|
3490
|
+
const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is denied by the deployment's tool policy and was not executed${rechecked.message ? `: ${rechecked.message}` : ""}.`);
|
|
3491
|
+
emitEnd(true, { content: editedDenial });
|
|
3492
|
+
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
|
|
3396
3493
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3397
3494
|
return;
|
|
3398
3495
|
}
|
|
@@ -3400,8 +3497,9 @@ export class Runner {
|
|
|
3400
3497
|
if (prepared.denyNarrowingPolicy) {
|
|
3401
3498
|
const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
|
|
3402
3499
|
if (narrowed.action === "deny") {
|
|
3403
|
-
|
|
3404
|
-
|
|
3500
|
+
const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`);
|
|
3501
|
+
emitEnd(true, { content: narrowedDenial });
|
|
3502
|
+
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
|
|
3405
3503
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3406
3504
|
return;
|
|
3407
3505
|
}
|
|
@@ -3419,13 +3517,26 @@ export class Runner {
|
|
|
3419
3517
|
res = await tool.execute(pendingAction.toolCallId, args, prepared.abortController.signal);
|
|
3420
3518
|
}
|
|
3421
3519
|
catch (err) {
|
|
3422
|
-
|
|
3423
|
-
const
|
|
3520
|
+
const execError = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
3521
|
+
const marks = (() => {
|
|
3522
|
+
if (err === null || typeof err !== "object")
|
|
3523
|
+
return undefined;
|
|
3524
|
+
const src = err;
|
|
3525
|
+
let d;
|
|
3526
|
+
if (src.details !== null && typeof src.details === "object" && !Array.isArray(src.details)) {
|
|
3527
|
+
d = { ...src.details };
|
|
3528
|
+
}
|
|
3529
|
+
if (typeof src.errorKind === "string")
|
|
3530
|
+
d = { ...(d ?? {}), errorKind: src.errorKind };
|
|
3531
|
+
return d;
|
|
3532
|
+
})();
|
|
3533
|
+
emitEnd(true, { content: execError, ...(marks !== undefined ? { details: marks } : {}) });
|
|
3534
|
+
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
|
|
3424
3535
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3425
3536
|
return;
|
|
3426
3537
|
}
|
|
3427
3538
|
const executedIsError = res.isError === true;
|
|
3428
|
-
emitEnd(executedIsError);
|
|
3539
|
+
emitEnd(executedIsError, res);
|
|
3429
3540
|
onResolvedToolSuccess?.(pendingAction.toolName, executedIsError ? undefined : res.details);
|
|
3430
3541
|
const eid = await prepared.session.appendMessage({
|
|
3431
3542
|
role: "toolResult",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentMessage } from "../../internal/harness.js";
|
|
2
|
+
export interface TranscriptFileRecord {
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
at: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function wholeFileRecordsFromTranscript(messages: readonly AgentMessage[]): TranscriptFileRecord[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
2
|
+
const READ_TOOL = "Read";
|
|
3
|
+
const WRITE_TOOL = "Write";
|
|
4
|
+
const RETRACTING_RESULTS = [
|
|
5
|
+
{ toolName: "Edit", type: "edit", pathField: "filePath" },
|
|
6
|
+
{ toolName: "NotebookEdit", type: "notebook-edit", pathField: "notebookPath" },
|
|
7
|
+
];
|
|
8
|
+
function cardOf(details) {
|
|
9
|
+
if (typeof details !== "object" || details === null)
|
|
10
|
+
return undefined;
|
|
11
|
+
const rest = details;
|
|
12
|
+
return typeof rest.type === "string" ? { type: rest.type, rest } : undefined;
|
|
13
|
+
}
|
|
14
|
+
function wholeFileFromReadCard(rest) {
|
|
15
|
+
const file = rest.file;
|
|
16
|
+
if (typeof file !== "object" || file === null)
|
|
17
|
+
return undefined;
|
|
18
|
+
const f = file;
|
|
19
|
+
if (typeof f.content !== "string" || f.truncatedByTokenCap === true)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (f.startLine !== 1 || typeof f.numLines !== "number" || f.numLines !== f.totalLines)
|
|
22
|
+
return undefined;
|
|
23
|
+
return f.content;
|
|
24
|
+
}
|
|
25
|
+
export function wholeFileRecordsFromTranscript(messages) {
|
|
26
|
+
const byPath = new Map();
|
|
27
|
+
for (const m of messages) {
|
|
28
|
+
if (m.role !== "toolResult" || m.isError)
|
|
29
|
+
continue;
|
|
30
|
+
const card = cardOf(m.details);
|
|
31
|
+
if (card === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
const { type, rest } = card;
|
|
34
|
+
const retracts = RETRACTING_RESULTS.find((r) => r.toolName === m.toolName && r.type === type);
|
|
35
|
+
if (retracts !== undefined) {
|
|
36
|
+
const changed = rest[retracts.pathField];
|
|
37
|
+
if (typeof changed === "string")
|
|
38
|
+
byPath.delete(changed);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const isRead = m.toolName === READ_TOOL && type === "text";
|
|
42
|
+
const isWrite = m.toolName === WRITE_TOOL && (type === "create" || type === "update");
|
|
43
|
+
if (!isRead && !isWrite)
|
|
44
|
+
continue;
|
|
45
|
+
const nested = rest.file;
|
|
46
|
+
const holder = isRead ? (typeof nested === "object" && nested !== null ? nested : undefined) : rest;
|
|
47
|
+
const filePath = holder?.filePath;
|
|
48
|
+
if (typeof filePath !== "string" || !isAbsolutePathForm(filePath))
|
|
49
|
+
continue;
|
|
50
|
+
const content = isRead ? wholeFileFromReadCard(rest) : typeof rest.content === "string" ? rest.content : undefined;
|
|
51
|
+
if (content === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
byPath.set(filePath, { path: filePath, content, at: m.timestamp });
|
|
54
|
+
}
|
|
55
|
+
return [...byPath.values()];
|
|
56
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { ToolEffect } from "../types.js";
|
|
|
3
3
|
import { type NamedToolPolicy } from "../tool-policy.js";
|
|
4
4
|
import type { SessionPermissionRules } from "../session-policy-store.js";
|
|
5
5
|
export declare const PATH_WRITE_TOOLS: ReadonlySet<string>;
|
|
6
|
+
export declare const PATH_CONFINABLE_WRITE_TOOLS: ReadonlySet<string>;
|
|
6
7
|
export declare function isWithin(root: string, p: string): boolean;
|
|
7
8
|
export declare function createSessionRulePolicy(rules: SessionPermissionRules, opts: {
|
|
8
9
|
env: ExecutionEnv;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { canonicalizeTarget,
|
|
1
|
+
import { canonicalizeTarget, writeTargetPath } from "../../tools/fs/safety.js";
|
|
2
2
|
import { isWinFormPath } from "../../tools/fs/safety.js";
|
|
3
3
|
import { createCoarseCommandNamePolicy } from "../tool-policy.js";
|
|
4
4
|
export const PATH_WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit"]);
|
|
5
|
+
export const PATH_CONFINABLE_WRITE_TOOLS = new Set([...PATH_WRITE_TOOLS, "NotebookEdit"]);
|
|
5
6
|
export function isWithin(root, p) {
|
|
6
7
|
if (!root)
|
|
7
8
|
return false;
|
|
@@ -46,14 +47,14 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
46
47
|
return d;
|
|
47
48
|
}
|
|
48
49
|
if (allowDirs) {
|
|
49
|
-
if (!
|
|
50
|
+
if (!PATH_CONFINABLE_WRITE_TOOLS.has(toolName)) {
|
|
50
51
|
const eff = toolEffects?.get(toolName) ?? "write";
|
|
51
52
|
if (eff !== "read") {
|
|
52
53
|
return deny(`write-capable tool "${req.toolName}" denied: session rule confines writes to allowDirs but this tool cannot be path-confined`);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
else {
|
|
56
|
-
const path =
|
|
57
|
+
const path = writeTargetPath(toolName, req.args);
|
|
57
58
|
if (typeof path !== "string" || path.length === 0) {
|
|
58
59
|
return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the call has no resolvable path`);
|
|
59
60
|
}
|
|
@@ -78,7 +78,7 @@ export function createReportFindingsTool() {
|
|
|
78
78
|
const count = findings.length;
|
|
79
79
|
return {
|
|
80
80
|
content: count === 0 ? "No findings reported." : `${count} finding${count === 1 ? "" : "s"} reported.`,
|
|
81
|
-
details: { count, ...(level !== undefined ? { level } : {}), findings },
|
|
81
|
+
details: { type: "report-findings", count, ...(level !== undefined ? { level } : {}), findings },
|
|
82
82
|
};
|
|
83
83
|
},
|
|
84
84
|
});
|
|
@@ -57,11 +57,12 @@ export const toolOutputFrom = (result) => {
|
|
|
57
57
|
return { output: raw, truncated: true, totalChars };
|
|
58
58
|
};
|
|
59
59
|
const CC_DETAIL_TYPES = new Set([
|
|
60
|
-
"edit", "
|
|
61
|
-
"agent", "task", "task-list", "task-output", "
|
|
60
|
+
"edit", "create", "update", "bash", "notebook-edit", "notebook", "file_unchanged", "worktree", "text", "grep", "glob", "mcp",
|
|
61
|
+
"agent", "task", "task-list", "task-output", "workflow-run",
|
|
62
62
|
"web-fetch", "web-search", "todo", "cron-create", "cron-delete", "cron-list", "image",
|
|
63
|
-
"task-stop", "tool-search", "
|
|
64
|
-
"monitor-start", "path_not_in_root",
|
|
63
|
+
"task-stop", "tool-search", "repo-map", "fork", "enter-plan-mode", "exit-plan-mode",
|
|
64
|
+
"monitor-start", "path_not_in_root", "readonly_out_of_root",
|
|
65
|
+
"report-findings", "schedule-wakeup", "send-message", "agent-transcript", "a2a", "document",
|
|
65
66
|
]);
|
|
66
67
|
export const structuredFrom = (result) => {
|
|
67
68
|
const details = result !== null && typeof result === "object" ? result.details : undefined;
|
|
@@ -6,10 +6,14 @@ export interface OrphanToolCall {
|
|
|
6
6
|
toolName: string;
|
|
7
7
|
kind?: "result";
|
|
8
8
|
}
|
|
9
|
+
export type ReconciledErrorKind = "interrupted_never_started" | "interrupted_outcome_unknown";
|
|
10
|
+
export type RecoveredOrphan = OrphanToolCall & {
|
|
11
|
+
entryId: string;
|
|
12
|
+
text: string;
|
|
13
|
+
errorKind: ReconciledErrorKind;
|
|
14
|
+
};
|
|
9
15
|
export interface ReconcileReport {
|
|
10
|
-
recovered:
|
|
11
|
-
entryId: string;
|
|
12
|
-
}>;
|
|
16
|
+
recovered: RecoveredOrphan[];
|
|
13
17
|
}
|
|
14
18
|
export declare function findOrphanToolCalls(messages: AgentMessage[], suspendedBatch?: ReadonlySet<string>): OrphanToolCall[];
|
|
15
19
|
export declare function reconcileInterruptedSession(session: Session, toolEffects?: Map<string, ToolEffect>, suspendedBatch?: ReadonlySet<string>, startedToolCallIds?: ReadonlySet<string>): Promise<ReconcileReport>;
|
|
@@ -75,16 +75,17 @@ export async function reconcileInterruptedSession(session, toolEffects, suspende
|
|
|
75
75
|
const effectText = effect === "read" ? INTERRUPTED_SAFE : effect === "idempotent" ? INTERRUPTED_IDEMPOTENT : INTERRUPTED_UNKNOWN;
|
|
76
76
|
const neverStarted = startedToolCallIds !== undefined && !startedToolCallIds.has(orphan.toolCallId);
|
|
77
77
|
const text = neverStarted ? INTERRUPTED_NEVER_STARTED : effectText;
|
|
78
|
+
const errorKind = neverStarted ? "interrupted_never_started" : "interrupted_outcome_unknown";
|
|
78
79
|
const entryId = await session.appendMessage({
|
|
79
80
|
role: "toolResult",
|
|
80
81
|
toolCallId: orphan.toolCallId,
|
|
81
82
|
toolName: orphan.toolName,
|
|
82
83
|
content: [{ type: "text", text }],
|
|
83
|
-
details: { errorKind
|
|
84
|
+
details: { errorKind },
|
|
84
85
|
isError: true,
|
|
85
86
|
timestamp: Date.now(),
|
|
86
87
|
});
|
|
87
|
-
recovered.push({ ...orphan, entryId });
|
|
88
|
+
recovered.push({ ...orphan, entryId, text, errorKind });
|
|
88
89
|
}
|
|
89
90
|
return { recovered };
|
|
90
91
|
}
|
|
@@ -14,8 +14,8 @@ export interface StrategyStore {
|
|
|
14
14
|
prune?(scope: string, maxSize: number): Promise<void> | void;
|
|
15
15
|
}
|
|
16
16
|
export declare class InMemoryStrategyStore implements StrategyStore {
|
|
17
|
-
private readonly maxPerScope;
|
|
18
17
|
private byScope;
|
|
18
|
+
private readonly maxPerScope;
|
|
19
19
|
constructor(maxPerScope?: number);
|
|
20
20
|
save(s: StoredStrategy): void;
|
|
21
21
|
find(scope: string, query: string, limit: number): StoredStrategy[];
|