@sema-agent/core 2.3.0 → 2.5.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/dist/agents/send-message-tool.d.ts +4 -0
- package/dist/agents/send-message-tool.js +37 -24
- package/dist/agents/subagent.js +279 -128
- package/dist/brain/errors.d.ts +1 -0
- package/dist/brain/errors.js +14 -0
- package/dist/brain/stream-engine.js +3 -3
- package/dist/core/auto-compaction.d.ts +2 -0
- package/dist/core/auto-compaction.js +2 -1
- package/dist/core/context-edit.js +2 -1
- package/dist/core/mcp.js +4 -1
- package/dist/core/runner/prepare-task.d.ts +5 -0
- package/dist/core/runner/prepare-task.js +41 -2
- package/dist/core/runner/runtask.js +39 -9
- package/dist/core/runner/tool-disclosure.d.ts +1 -0
- package/dist/core/runner/tool-disclosure.js +16 -5
- package/dist/core/runner/tool-output-projection.js +2 -1
- package/dist/core/session-reconcile.d.ts +1 -0
- package/dist/core/session-reconcile.js +40 -20
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +37 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +195 -0
- package/dist/core/store-contracts/contract-harness.d.ts +6 -0
- package/dist/core/store-contracts/contract-harness.js +16 -0
- package/dist/core/store-contracts/contract-kit-version.d.ts +1 -0
- package/dist/core/store-contracts/contract-kit-version.js +2 -0
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-snapshot-store-contract.js +126 -0
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +6 -0
- package/dist/core/store-contracts/mailbox-store-contract.js +193 -0
- package/dist/core/store-contracts/session-repo-contract.d.ts +3 -0
- package/dist/core/store-contracts/session-repo-contract.js +36 -0
- package/dist/core/store-contracts/tool-result-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/tool-result-store-contract.js +35 -0
- package/dist/core/task-notification.d.ts +2 -0
- package/dist/core/task-registry-agent.d.ts +6 -0
- package/dist/core/task-registry-agent.js +24 -1
- package/dist/core/task-registry-monitor.js +6 -6
- package/dist/core/task-registry-shared.d.ts +9 -2
- package/dist/core/task-registry-shared.js +1 -1
- package/dist/core/task-registry.d.ts +8 -0
- package/dist/core/task-registry.js +59 -4
- package/dist/core/tool-result-store.d.ts +3 -2
- package/dist/core/tool-result-store.js +12 -4
- package/dist/core/trace.d.ts +7 -0
- package/dist/core/types.d.ts +10 -3
- package/dist/engine/compaction/compaction.d.ts +5 -0
- package/dist/engine/compaction/compaction.js +68 -2
- package/dist/engine/compaction/utils.d.ts +6 -0
- package/dist/engine/compaction/utils.js +53 -3
- package/dist/engine/harness/messages.d.ts +1 -1
- package/dist/engine/harness/messages.js +11 -3
- package/dist/engine/loop/types.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +16 -0
- package/dist/engine/session/import-validate.js +30 -1
- package/dist/engine/session/session.js +7 -5
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/builtin-workflows.d.ts +1 -1
- package/dist/orchestration/builtin-workflows.js +11 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -1
- package/dist/orchestration/workflow-governance.js +24 -4
- package/dist/orchestration/workflow-primitives.js +7 -1
- package/dist/orchestration/workflow-types.d.ts +1 -0
- package/dist/orchestration/workflow.d.ts +1 -0
- package/dist/orchestration/workflow.js +41 -3
- package/dist/tools/fs/fs-bash.d.ts +7 -1
- package/dist/tools/fs/fs-bash.js +59 -22
- package/dist/tools/fs/fs-read.js +22 -11
- package/dist/tools/fs/fs-search-tools.js +3 -3
- package/dist/tools/fs/fs-shared.d.ts +20 -7
- package/dist/tools/fs/fs-shared.js +17 -3
- package/dist/tools/fs/fs-write.js +4 -4
- package/dist/tools/fs/index.d.ts +2 -0
- package/dist/tools/fs/index.js +7 -1
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/safety.d.ts +10 -0
- package/dist/tools/fs/safety.js +15 -1
- package/dist/tools/monitor.js +18 -4
- package/dist/tools/web.js +6 -2
- package/dist/tools/worktree.js +46 -25
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAssistantMessageEventStream, } from "../internal/llm.js";
|
|
2
|
-
import { BrainError, classifyHttp } from "./errors.js";
|
|
2
|
+
import { BrainError, classifyHttp, describeNetworkError } from "./errors.js";
|
|
3
3
|
import { retryBackoffMs } from "./retry.js";
|
|
4
4
|
import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
|
|
5
5
|
import { createConnectController } from "./timeout.js";
|
|
@@ -139,7 +139,7 @@ export function runStreamingBrain(args) {
|
|
|
139
139
|
continue;
|
|
140
140
|
}
|
|
141
141
|
if (netErr)
|
|
142
|
-
throw new BrainError("network",
|
|
142
|
+
throw new BrainError("network", describeNetworkError(netErr));
|
|
143
143
|
const detail = r ? await r.text().catch(() => "") : "";
|
|
144
144
|
const status = r?.status ?? 0;
|
|
145
145
|
throw new BrainError(classifyHttp(status), `${httpLabel} HTTP ${status || "ERR"}: ${detail.slice(0, 500)}`, status);
|
|
@@ -250,7 +250,7 @@ export function runStreamingBrain(args) {
|
|
|
250
250
|
throw e;
|
|
251
251
|
failure = {
|
|
252
252
|
kind: "connection",
|
|
253
|
-
err: new BrainError("network", `mid-stream read failed: ${
|
|
253
|
+
err: new BrainError("network", `mid-stream read failed: ${describeNetworkError(e)}`),
|
|
254
254
|
};
|
|
255
255
|
break readLoop;
|
|
256
256
|
}
|
|
@@ -96,11 +96,13 @@ export interface MaybeCompactOptions {
|
|
|
96
96
|
droppedChars: number;
|
|
97
97
|
keptChars: number;
|
|
98
98
|
}) => void;
|
|
99
|
+
activeTools?: ReadonlyArray<string>;
|
|
99
100
|
}
|
|
100
101
|
export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
|
|
101
102
|
compacted: boolean;
|
|
102
103
|
blocked?: boolean;
|
|
103
104
|
disabled?: boolean;
|
|
105
|
+
noop?: true;
|
|
104
106
|
tokensBefore?: number;
|
|
105
107
|
freedTokens?: number;
|
|
106
108
|
triggerTokens?: number;
|
|
@@ -86,7 +86,7 @@ export async function maybeCompact(opts) {
|
|
|
86
86
|
throw prep.error;
|
|
87
87
|
}
|
|
88
88
|
if (!prep.value) {
|
|
89
|
-
return { contextUsage, compacted: false };
|
|
89
|
+
return { contextUsage, compacted: false, noop: true };
|
|
90
90
|
}
|
|
91
91
|
const trigger = opts.trigger ?? "auto";
|
|
92
92
|
let hookInstructions;
|
|
@@ -454,6 +454,7 @@ export async function maybeCompact(opts) {
|
|
|
454
454
|
...(details ?? {}),
|
|
455
455
|
promptEpoch: restatedEpoch,
|
|
456
456
|
...(restatedListings !== undefined ? { announcedListings: restatedListings } : {}),
|
|
457
|
+
...(opts.activeTools !== undefined && opts.activeTools.length > 0 ? { activeTools: [...opts.activeTools] } : {}),
|
|
457
458
|
}, false);
|
|
458
459
|
try {
|
|
459
460
|
opts.onApplied?.(attachedComplete, excludedReadStatePreserveKeys);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { DEFAULT_CHARS_PER_TOKEN, estimateContextTokens, estimateTokens } from "../internal/harness.js";
|
|
2
2
|
import { isToolResult } from "./message-utils.js";
|
|
3
|
+
import { offloadPagebackHint } from "./tool-result-store.js";
|
|
3
4
|
const CLEARED_MARKER = "[tool result cleared to save context]";
|
|
4
|
-
const refNote = (ref) =>
|
|
5
|
+
const refNote = (ref) => offloadPagebackHint(ref, "cleared");
|
|
5
6
|
const mediaNote = (blocks) => {
|
|
6
7
|
const byType = new Map();
|
|
7
8
|
for (const b of blocks) {
|
package/dist/core/mcp.js
CHANGED
|
@@ -1039,13 +1039,16 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
|
|
|
1039
1039
|
if (axis)
|
|
1040
1040
|
serverAxes.push(axis);
|
|
1041
1041
|
const writeEffect = (axis?.effect ?? "write") === "write";
|
|
1042
|
-
const
|
|
1042
|
+
const mcpToolMeta = t._meta;
|
|
1043
|
+
const mcpMaxResultSizeChars = resolveMcpDeclaredResultSize(mcpToolMeta);
|
|
1044
|
+
const mcpAlwaysLoad = mcpToolMeta?.["anthropic/alwaysLoad"] === true;
|
|
1043
1045
|
serverTools.push({
|
|
1044
1046
|
name: namespacedName,
|
|
1045
1047
|
description: effectiveDescription ?? `MCP tool ${remoteName} from ${spec.name}`,
|
|
1046
1048
|
label: `${spec.name}:${remoteName}`,
|
|
1047
1049
|
parameters: (effectiveInputSchema ?? { type: "object" }),
|
|
1048
1050
|
...(mcpMaxResultSizeChars !== undefined ? { mcpMaxResultSizeChars } : {}),
|
|
1051
|
+
...(mcpAlwaysLoad ? { mcpAlwaysLoad: true } : {}),
|
|
1049
1052
|
execute: async (_toolCallId, params, signal) => {
|
|
1050
1053
|
const what = `The call to tool ${inlineUntrusted(remoteName)}`;
|
|
1051
1054
|
if (health.dead)
|
|
@@ -327,6 +327,11 @@ export interface RunInternals {
|
|
|
327
327
|
requestedCwd?: string;
|
|
328
328
|
onSubagentSpawn?: (handle: import("../../agents/subagent.js").SubagentSteerHandle) => void;
|
|
329
329
|
onActivity?: (activity: ToolActivity) => void;
|
|
330
|
+
onWorkspaceResolved?: (workspace: ResolvedWorkspace) => void;
|
|
331
|
+
}
|
|
332
|
+
export interface ResolvedWorkspace {
|
|
333
|
+
cwd: string;
|
|
334
|
+
isolated: boolean;
|
|
330
335
|
}
|
|
331
336
|
export declare function batchContextAt(messages: AgentMessage[], currentId: string): {
|
|
332
337
|
batchToolCallIds: string[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { resolve as resolveFsPath } from "node:path";
|
|
4
|
-
import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, summaryOutputBudgetTokens } from "../../internal/harness.js";
|
|
4
|
+
import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, readCompactionActiveTools, summaryOutputBudgetTokens } from "../../internal/harness.js";
|
|
5
5
|
const PROMPT_HASH_SALT = randomBytes(16);
|
|
6
6
|
import { sanitizeCompactionSettings } from "../auto-compaction.js";
|
|
7
7
|
import { createAutoModeDecider } from "../auto-mode.js";
|
|
@@ -177,6 +177,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
177
177
|
const toolFaceSnapshot = {
|
|
178
178
|
exclude: spec.excludeTools ? Object.freeze([...spec.excludeTools]) : undefined,
|
|
179
179
|
defer: spec.deferTools ? Object.freeze([...spec.deferTools]) : undefined,
|
|
180
|
+
alwaysLoad: spec.alwaysLoadTools ? Object.freeze([...spec.alwaysLoadTools]) : undefined,
|
|
180
181
|
};
|
|
181
182
|
const promptProfile = resolveModelPromptTraits({ id: "" }, spec, internals).promptProfile;
|
|
182
183
|
if (spec.resumeAt !== undefined) {
|
|
@@ -435,10 +436,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
435
436
|
const offloadStore = rawOffloadStore instanceof RunnerSharedToolResultStore
|
|
436
437
|
? new ScopedToolResultStore(rawOffloadStore, taskScope)
|
|
437
438
|
: rawOffloadStore;
|
|
439
|
+
const offloadReachableToolsRef = {};
|
|
438
440
|
const maybeOffload = (tool, perTool) => {
|
|
439
441
|
if (!offloadStore || perTool?.offload === false)
|
|
440
442
|
return tool;
|
|
441
|
-
return withToolResultOffload(tool, offloadStore, perTool?.offloadThresholdChars ?? offloadThreshold, sessionId);
|
|
443
|
+
return withToolResultOffload(tool, offloadStore, perTool?.offloadThresholdChars ?? offloadThreshold, sessionId, () => offloadReachableToolsRef.current?.());
|
|
442
444
|
};
|
|
443
445
|
const firstPartyOffload = (tool) => {
|
|
444
446
|
const policy = firstPartyOffloadPolicy(tool.name);
|
|
@@ -607,6 +609,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
607
609
|
}
|
|
608
610
|
}
|
|
609
611
|
}
|
|
612
|
+
if (internals?.onWorkspaceResolved !== undefined) {
|
|
613
|
+
try {
|
|
614
|
+
internals.onWorkspaceResolved({ cwd: taskRootPath, isolated: internals.isolation === "worktree" });
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
}
|
|
618
|
+
}
|
|
610
619
|
let rewindTarget = spec.resumeAt !== undefined ? (spec.rewindFiles ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
611
620
|
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
612
621
|
if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
|
|
@@ -1123,6 +1132,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1123
1132
|
phase: "config",
|
|
1124
1133
|
sessionId,
|
|
1125
1134
|
});
|
|
1135
|
+
emitTrace(deps.tracer, () => ({
|
|
1136
|
+
kind: "config.additional_directory_skipped",
|
|
1137
|
+
version: 1,
|
|
1138
|
+
taskId: hostTaskId,
|
|
1139
|
+
entry: dir,
|
|
1140
|
+
reason: `${c.error.code}: ${c.error.message}`,
|
|
1141
|
+
ts: Date.now(),
|
|
1142
|
+
}));
|
|
1126
1143
|
}
|
|
1127
1144
|
}
|
|
1128
1145
|
if (spec.envFacts?.scratchpadDir) {
|
|
@@ -1255,6 +1272,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1255
1272
|
...(internals?.parentNotify !== undefined ? { uplink: internals.parentNotify } : {}),
|
|
1256
1273
|
...(internals?.explicitAgentName !== undefined ? { senderName: internals.explicitAgentName } : {}),
|
|
1257
1274
|
...(internals?.parentRetainLedger !== undefined ? { siblingRetain: internals.parentRetainLedger } : {}),
|
|
1275
|
+
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
1276
|
+
...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
|
|
1258
1277
|
...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
|
|
1259
1278
|
...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
|
|
1260
1279
|
...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
|
|
@@ -1770,6 +1789,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1770
1789
|
deferMode: deps.deferMode,
|
|
1771
1790
|
model,
|
|
1772
1791
|
deferNames: (toolFaceSnapshot.defer ?? []).filter((n) => tools.some((t) => t.name === n)),
|
|
1792
|
+
alwaysLoadNames: [
|
|
1793
|
+
...(toolFaceSnapshot.alwaysLoad ?? []),
|
|
1794
|
+
...mcp.tools.filter((t) => t.mcpAlwaysLoad === true).map((t) => t.name),
|
|
1795
|
+
],
|
|
1773
1796
|
});
|
|
1774
1797
|
for (const n of [...deferred]) {
|
|
1775
1798
|
if (!tools.some((t) => t.name === n))
|
|
@@ -1809,11 +1832,27 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1809
1832
|
if (deferred.has(n))
|
|
1810
1833
|
activeTools.add(n);
|
|
1811
1834
|
}
|
|
1835
|
+
for (const entry of await session.getBranch()) {
|
|
1836
|
+
if (entry.type !== "compaction" || entry.fromHook === true)
|
|
1837
|
+
continue;
|
|
1838
|
+
for (const n of readCompactionActiveTools(entry.details)) {
|
|
1839
|
+
if (deferred.has(n))
|
|
1840
|
+
activeTools.add(n);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1812
1843
|
if (resume) {
|
|
1813
1844
|
for (const n of resume.seed.activeTools)
|
|
1814
1845
|
if (deferred.has(n))
|
|
1815
1846
|
activeTools.add(n);
|
|
1816
1847
|
}
|
|
1848
|
+
offloadReachableToolsRef.current = () => {
|
|
1849
|
+
const s = new Set(tools.map((t) => t.name));
|
|
1850
|
+
for (const n of deferred)
|
|
1851
|
+
if (!activeTools.has(n))
|
|
1852
|
+
s.delete(n);
|
|
1853
|
+
s.add(TOOL_SEARCH_NAME);
|
|
1854
|
+
return s;
|
|
1855
|
+
};
|
|
1817
1856
|
let toolSearch;
|
|
1818
1857
|
const buildToolList = (active) => {
|
|
1819
1858
|
const list = tools.map((t) => (deferred.has(t.name) && !active.has(t.name) ? placeholders.get(t.name) : t));
|
|
@@ -538,6 +538,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
538
538
|
prepared.reviewRef.token !== undefined) {
|
|
539
539
|
if (forceManual) {
|
|
540
540
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
|
|
541
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
|
|
541
542
|
drainManualCompact("mooted");
|
|
542
543
|
}
|
|
543
544
|
return undefined;
|
|
@@ -552,6 +553,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
552
553
|
reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt",
|
|
553
554
|
ts: Date.now(),
|
|
554
555
|
}));
|
|
556
|
+
queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "manual", reason: "compaction circuit breaker open (3 consecutive failures this task) — manual request drained without an attempt", ...ident() });
|
|
555
557
|
drainManualCompact("failed");
|
|
556
558
|
}
|
|
557
559
|
return undefined;
|
|
@@ -590,6 +592,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
590
592
|
if (trimPressureArmed)
|
|
591
593
|
prepared.trimPressureRef.droppedMessages = false;
|
|
592
594
|
const trimPressure = trimPressureArmed && !rs.counters.trimForceBackoff;
|
|
595
|
+
const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
|
|
593
596
|
let manualOutcome = "failed";
|
|
594
597
|
if (rs.counters.finalizeInjected && forceManual) {
|
|
595
598
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.manual_in_finalize", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
@@ -617,6 +620,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
617
620
|
onInputTruncated: (info) => emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.input_truncated", version: 1, taskId: rs.telemetry.taskId, label: info.label, droppedChars: info.droppedChars, keptChars: info.keptChars, ts: Date.now() })),
|
|
618
621
|
forceUnderThreshold: trimPressure,
|
|
619
622
|
overheadTokens: prepared.promptOverheadTokens,
|
|
623
|
+
...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
|
|
620
624
|
workingFileAttachments: spec.compaction?.attachWorkingFiles !== false && prepared.readTaskFile
|
|
621
625
|
? {
|
|
622
626
|
readFile: prepared.readTaskFile,
|
|
@@ -632,7 +636,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
632
636
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
633
637
|
...runnerHooks.seamCCompactionOptions(prepared),
|
|
634
638
|
...windowSafetyOptions(event.model),
|
|
635
|
-
...runnerHooks.compactionHookOptions(spec, prepared.sessionId,
|
|
639
|
+
...runnerHooks.compactionHookOptions(spec, prepared.sessionId, passTrigger),
|
|
636
640
|
});
|
|
637
641
|
if (comp.blocked) {
|
|
638
642
|
emitTrace(rs.telemetry.tracer, () => ({
|
|
@@ -670,17 +674,32 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
670
674
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.noop", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
671
675
|
}
|
|
672
676
|
}
|
|
677
|
+
if (!comp.compacted) {
|
|
678
|
+
const outcome = comp.blocked
|
|
679
|
+
? "blocked"
|
|
680
|
+
: comp.disabled
|
|
681
|
+
? (forceManual || trimPressure ? "disabled" : undefined)
|
|
682
|
+
: comp.suppressedByFloor
|
|
683
|
+
? "suppressed"
|
|
684
|
+
: comp.noop || forceManual
|
|
685
|
+
? "noop"
|
|
686
|
+
: undefined;
|
|
687
|
+
if (outcome !== undefined) {
|
|
688
|
+
queue.push({ type: "compaction_outcome", outcome, trigger: passTrigger, ...ident() });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
673
691
|
if (comp.compacted && !forceManual) {
|
|
674
692
|
if (recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
|
|
675
693
|
compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
|
|
676
694
|
runnerHooks.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
|
|
695
|
+
queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
|
|
677
696
|
}
|
|
678
697
|
}
|
|
679
698
|
runnerHooks.recordCompactionReuse(prepared, comp);
|
|
680
699
|
if (comp.compacted) {
|
|
681
700
|
queue.push({
|
|
682
701
|
type: "compacted",
|
|
683
|
-
trigger:
|
|
702
|
+
trigger: passTrigger,
|
|
684
703
|
tokensBefore: comp.tokensBefore ?? 0,
|
|
685
704
|
...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
|
|
686
705
|
...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
|
|
@@ -692,6 +711,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
692
711
|
...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
|
|
693
712
|
...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
|
|
694
713
|
...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
|
|
714
|
+
...ident(),
|
|
695
715
|
});
|
|
696
716
|
if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
|
|
697
717
|
const pd = comp.phaseDurations;
|
|
@@ -713,6 +733,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
713
733
|
const manualCancelAbort = !prepared.abortController.signal.aborted && isCompactionManualCancel(err);
|
|
714
734
|
if (manualCancelAbort) {
|
|
715
735
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
|
|
736
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...ident() });
|
|
716
737
|
manualOutcome = "mooted";
|
|
717
738
|
}
|
|
718
739
|
else if (!prepared.abortController.signal.aborted) {
|
|
@@ -721,18 +742,20 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
721
742
|
compactionBreaker.failures += 1;
|
|
722
743
|
runnerHooks.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
|
|
723
744
|
const msg = String(err instanceof Error ? err.message : err);
|
|
745
|
+
const failReason = walltimeAbort
|
|
746
|
+
? "walltime soft-deadline abort (engine-initiated write-out protection; not counted toward the breaker)"
|
|
747
|
+
: msg.length > 512
|
|
748
|
+
? `${msg.slice(0, 512)}…`
|
|
749
|
+
: msg;
|
|
724
750
|
emitTrace(rs.telemetry.tracer, () => ({
|
|
725
751
|
kind: "compaction.failed",
|
|
726
752
|
version: 1,
|
|
727
753
|
taskId: rs.telemetry.taskId,
|
|
728
|
-
trigger:
|
|
729
|
-
reason:
|
|
730
|
-
? "walltime soft-deadline abort (engine-initiated write-out protection; not counted toward the breaker)"
|
|
731
|
-
: msg.length > 512
|
|
732
|
-
? `${msg.slice(0, 512)}…`
|
|
733
|
-
: msg,
|
|
754
|
+
trigger: passTrigger,
|
|
755
|
+
reason: failReason,
|
|
734
756
|
ts: Date.now(),
|
|
735
757
|
}));
|
|
758
|
+
queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...ident() });
|
|
736
759
|
manualOutcome = "failed";
|
|
737
760
|
}
|
|
738
761
|
else {
|
|
@@ -2319,6 +2342,7 @@ export class Runner {
|
|
|
2319
2342
|
minTokens: 0,
|
|
2320
2343
|
force: true,
|
|
2321
2344
|
overheadTokens: prepared.promptOverheadTokens,
|
|
2345
|
+
...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
|
|
2322
2346
|
onInputTruncated: (info) => emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.input_truncated", version: 1, taskId: rs.telemetry.taskId, label: info.label, droppedChars: info.droppedChars, keptChars: info.keptChars, ts: Date.now() })),
|
|
2323
2347
|
workingFileAttachments: spec.compaction?.attachWorkingFiles !== false && prepared.readTaskFile
|
|
2324
2348
|
? {
|
|
@@ -2346,7 +2370,7 @@ export class Runner {
|
|
|
2346
2370
|
}
|
|
2347
2371
|
queue.push({
|
|
2348
2372
|
type: "compacted",
|
|
2349
|
-
trigger: "
|
|
2373
|
+
trigger: "forced",
|
|
2350
2374
|
tokensBefore: comp.tokensBefore ?? 0,
|
|
2351
2375
|
...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
|
|
2352
2376
|
...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
|
|
@@ -2358,6 +2382,7 @@ export class Runner {
|
|
|
2358
2382
|
...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
|
|
2359
2383
|
...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
|
|
2360
2384
|
...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
|
|
2385
|
+
...ident(),
|
|
2361
2386
|
});
|
|
2362
2387
|
if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
|
|
2363
2388
|
const pd = comp.phaseDurations;
|
|
@@ -2372,12 +2397,15 @@ export class Runner {
|
|
|
2372
2397
|
}
|
|
2373
2398
|
else {
|
|
2374
2399
|
compactionBreaker.failures += 1;
|
|
2400
|
+
queue.push({ type: "compaction_outcome", outcome: comp.noop ? "noop" : "failed", trigger: "forced", reason: "prompt-too-long recovery pass did not land", ...ident() });
|
|
2375
2401
|
}
|
|
2376
2402
|
return comp.compacted === true;
|
|
2377
2403
|
}
|
|
2378
2404
|
catch (err) {
|
|
2379
2405
|
if (!isCompactionWalltimeAbort(err))
|
|
2380
2406
|
compactionBreaker.failures += 1;
|
|
2407
|
+
const msg = String(err instanceof Error ? err.message : err);
|
|
2408
|
+
queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
|
|
2381
2409
|
return false;
|
|
2382
2410
|
}
|
|
2383
2411
|
},
|
|
@@ -2661,6 +2689,7 @@ export class Runner {
|
|
|
2661
2689
|
...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
|
|
2662
2690
|
...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
|
|
2663
2691
|
...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
|
|
2692
|
+
...ident(),
|
|
2664
2693
|
});
|
|
2665
2694
|
if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
|
|
2666
2695
|
const pd = comp.phaseDurations;
|
|
@@ -3453,6 +3482,7 @@ export class Runner {
|
|
|
3453
3482
|
customInstructions: spec.compaction?.instructions ?? DEFAULT_COMPACTION_INSTRUCTIONS,
|
|
3454
3483
|
minTokens: opts?.minTokens,
|
|
3455
3484
|
overheadTokens: prepared.promptOverheadTokens,
|
|
3485
|
+
...(prepared.activeTools.size > 0 ? { activeTools: [...prepared.activeTools] } : {}),
|
|
3456
3486
|
onInputTruncated: (info) => emitTrace(spec.tracer ?? this.deps.tracer, () => ({
|
|
3457
3487
|
kind: "compaction.input_truncated",
|
|
3458
3488
|
version: 1,
|
|
@@ -21,6 +21,7 @@ export declare function classifyDeferred(opts: {
|
|
|
21
21
|
deferMode?: "auto";
|
|
22
22
|
model: Model;
|
|
23
23
|
deferNames?: ReadonlyArray<string>;
|
|
24
|
+
alwaysLoadNames?: ReadonlyArray<string>;
|
|
24
25
|
}): Set<string>;
|
|
25
26
|
export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, tools: ReadonlyArray<{
|
|
26
27
|
name: string;
|
|
@@ -24,17 +24,24 @@ function inlinedChars(t) {
|
|
|
24
24
|
return t.description.length + schema.length;
|
|
25
25
|
}
|
|
26
26
|
export function classifyDeferred(opts) {
|
|
27
|
+
const pinned = new Set(opts.alwaysLoadNames ?? []);
|
|
28
|
+
for (const s of opts.specs) {
|
|
29
|
+
if (s.alwaysLoad === true)
|
|
30
|
+
pinned.add(s.name);
|
|
31
|
+
}
|
|
27
32
|
const deferred = new Set();
|
|
28
33
|
for (const s of opts.specs) {
|
|
29
|
-
if (s.defer === true)
|
|
34
|
+
if (s.defer === true && !pinned.has(s.name))
|
|
30
35
|
deferred.add(s.name);
|
|
31
36
|
}
|
|
32
37
|
for (const name of opts.mcpToolNames)
|
|
33
|
-
|
|
38
|
+
if (!pinned.has(name))
|
|
39
|
+
deferred.add(name);
|
|
34
40
|
for (const name of opts.deferNames ?? [])
|
|
35
|
-
|
|
41
|
+
if (!pinned.has(name))
|
|
42
|
+
deferred.add(name);
|
|
36
43
|
if (opts.deferMode === "auto") {
|
|
37
|
-
const candidates = opts.fullTools.filter((t) => !deferred.has(t.name));
|
|
44
|
+
const candidates = opts.fullTools.filter((t) => !deferred.has(t.name) && !pinned.has(t.name));
|
|
38
45
|
const total = candidates.reduce((n, t) => n + inlinedChars(t), 0);
|
|
39
46
|
const window = (opts.model.contextTokens ?? opts.model.contextWindow ?? 0) * CHARS_PER_TOKEN;
|
|
40
47
|
if (window > 0 && total > DEFER_AUTO_FRACTION * window) {
|
|
@@ -159,10 +166,14 @@ export function createToolSearchTool(opts) {
|
|
|
159
166
|
name: TOOL_SEARCH_NAME,
|
|
160
167
|
contract: { contractId: "core.tool_search@1", implementationRevision: "1" },
|
|
161
168
|
description: "Discover and activate deferred tools. Most tools start as name-only placeholders to keep requests " +
|
|
162
|
-
"small; to USE one you must activate it here first.
|
|
169
|
+
"small; to USE one you must activate it here first. When any instruction, reminder, or another " +
|
|
170
|
+
'tool\'s description names a deferred tool, activate it with query "select:<name>" before calling it. ' +
|
|
171
|
+
"Query forms: " +
|
|
163
172
|
'"select:ToolA,ToolB" — activate these exact tools by name; ' +
|
|
164
173
|
'"notebook jupyter" — keyword search, up to max_results best matches; ' +
|
|
165
174
|
"a bare tool name — activates that tool directly. " +
|
|
175
|
+
"Activate every tool you expect to need in one call (select accepts a comma-separated list) " +
|
|
176
|
+
"rather than one at a time. " +
|
|
166
177
|
"Activated tools become callable with their full parameters on your next turn.",
|
|
167
178
|
parameters: Type.Object({
|
|
168
179
|
query: Type.Optional(Type.String({
|
|
@@ -57,10 +57,11 @@ export const toolOutputFrom = (result) => {
|
|
|
57
57
|
return { output: raw, truncated: true, totalChars };
|
|
58
58
|
};
|
|
59
59
|
const CC_DETAIL_TYPES = new Set([
|
|
60
|
-
"edit", "multiedit", "create", "update", "bash", "notebook-edit", "notebook", "text", "grep", "glob", "mcp",
|
|
60
|
+
"edit", "multiedit", "create", "update", "bash", "notebook-edit", "notebook", "file_unchanged", "worktree", "text", "grep", "glob", "mcp",
|
|
61
61
|
"agent", "task", "task-list", "task-output", "memory-saved", "workflow-run",
|
|
62
62
|
"web-fetch", "web-search", "todo", "cron-create", "cron-delete", "cron-list", "image",
|
|
63
63
|
"task-stop", "tool-search", "memory-recall", "repo-map", "fork", "enter-plan-mode", "exit-plan-mode",
|
|
64
|
+
"monitor-start", "path_not_in_root",
|
|
64
65
|
]);
|
|
65
66
|
export const structuredFrom = (result) => {
|
|
66
67
|
const details = result !== null && typeof result === "object" ? result.details : undefined;
|
|
@@ -13,6 +13,7 @@ const INTERRUPTED_NEVER_STARTED = "[INTERRUPTED] The run was aborted before this
|
|
|
13
13
|
"no side effects — it is safe to re-issue this call if you still need it.";
|
|
14
14
|
export function findOrphanToolCalls(messages, suspendedBatch) {
|
|
15
15
|
const resolvedAt = new Map();
|
|
16
|
+
const callSitesAt = new Map();
|
|
16
17
|
messages.forEach((m, i) => {
|
|
17
18
|
if (m.role === "toolResult") {
|
|
18
19
|
const at = resolvedAt.get(m.toolCallId);
|
|
@@ -21,35 +22,54 @@ export function findOrphanToolCalls(messages, suspendedBatch) {
|
|
|
21
22
|
else
|
|
22
23
|
at.push(i);
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
else if (m.role === "assistant") {
|
|
26
|
+
for (const part of m.content) {
|
|
27
|
+
if (part.type !== "toolCall")
|
|
28
|
+
continue;
|
|
29
|
+
const at = callSitesAt.get(part.id);
|
|
30
|
+
if (at === undefined)
|
|
31
|
+
callSitesAt.set(part.id, [i]);
|
|
32
|
+
else
|
|
33
|
+
at.push(i);
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
});
|
|
37
|
+
const nextReuseAfter = (id, from) => callSitesAt.get(id)?.find((j) => j > from) ?? Number.POSITIVE_INFINITY;
|
|
35
38
|
const orphans = [];
|
|
36
39
|
messages.forEach((m, i) => {
|
|
37
|
-
if (m.role
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
if (m.role === "assistant") {
|
|
41
|
+
for (const part of m.content) {
|
|
42
|
+
if (part.type !== "toolCall" || suspendedBatch?.has(part.id))
|
|
43
|
+
continue;
|
|
44
|
+
const bound = nextReuseAfter(part.id, i);
|
|
45
|
+
const closed = (resolvedAt.get(part.id) ?? []).some((at) => at > i && at < bound);
|
|
46
|
+
if (!closed)
|
|
47
|
+
orphans.push({ toolCallId: part.id, toolName: part.name });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (m.role === "toolResult") {
|
|
51
|
+
if (suspendedBatch?.has(m.toolCallId))
|
|
52
|
+
return;
|
|
53
|
+
const sites = callSitesAt.get(m.toolCallId) ?? [];
|
|
54
|
+
const results = resolvedAt.get(m.toolCallId) ?? [];
|
|
55
|
+
let paired = false;
|
|
56
|
+
for (const j of sites) {
|
|
57
|
+
if (j >= i)
|
|
58
|
+
break;
|
|
59
|
+
if (!results.some((at) => at > j && at < i)) {
|
|
60
|
+
paired = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!paired)
|
|
65
|
+
orphans.push({ toolCallId: m.toolCallId, toolName: m.toolName, kind: "result" });
|
|
46
66
|
}
|
|
47
67
|
});
|
|
48
68
|
return orphans;
|
|
49
69
|
}
|
|
50
70
|
export async function reconcileInterruptedSession(session, toolEffects, suspendedBatch, startedToolCallIds) {
|
|
51
71
|
const { messages } = await session.buildContext();
|
|
52
|
-
const orphans = findOrphanToolCalls(messages, suspendedBatch);
|
|
72
|
+
const orphans = findOrphanToolCalls(messages, suspendedBatch).filter((o) => o.kind !== "result");
|
|
53
73
|
const recovered = [];
|
|
54
74
|
for (const orphan of orphans) {
|
|
55
75
|
const effect = toolEffects?.get(canonicalToolName(orphan.toolName)) ?? "write";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Checkpoint, type CheckpointStore, type ResumeOutcome } from "../checkpoint-store.js";
|
|
2
|
+
import { type ContractAssertionRunner } from "./contract-harness.js";
|
|
3
|
+
export declare function createCheckpointFixture(over?: Partial<Checkpoint>): Checkpoint;
|
|
4
|
+
export declare const ALLOW: Extract<ResumeOutcome, {
|
|
5
|
+
gate: "policy_ask";
|
|
6
|
+
}>;
|
|
7
|
+
export declare function checkpointStoreContract(make: () => CheckpointStore, runAssertion?: ContractAssertionRunner): Promise<void>;
|
|
8
|
+
export declare function checkpointListByScopeSummaries(store: CheckpointStore): Promise<import("../checkpoint-store.js").CheckpointSummary[]>;
|
|
9
|
+
export declare const EXPECTED_LISTBYSCOPE_SUMMARIES: ({
|
|
10
|
+
token: string;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
scope: string;
|
|
13
|
+
gateKind: string;
|
|
14
|
+
severity: number;
|
|
15
|
+
deadline: number;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
sourceTaskId: string;
|
|
18
|
+
principal: string;
|
|
19
|
+
toolCallId: string;
|
|
20
|
+
toolName: string;
|
|
21
|
+
toolInput: string;
|
|
22
|
+
spentMicroUsd?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
token: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
scope: string;
|
|
27
|
+
gateKind: string;
|
|
28
|
+
spentMicroUsd: number;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
severity?: undefined;
|
|
31
|
+
deadline?: undefined;
|
|
32
|
+
sourceTaskId?: undefined;
|
|
33
|
+
principal?: undefined;
|
|
34
|
+
toolCallId?: undefined;
|
|
35
|
+
toolName?: undefined;
|
|
36
|
+
toolInput?: undefined;
|
|
37
|
+
})[];
|