@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
|
@@ -66,6 +66,9 @@ export declare function settleBackgroundAgentLane(core: DurableAgentCore, id: st
|
|
|
66
66
|
result?: string;
|
|
67
67
|
resultFull?: string;
|
|
68
68
|
error?: string;
|
|
69
|
+
errorCode?: string;
|
|
70
|
+
retryable?: boolean;
|
|
71
|
+
errorKind?: string;
|
|
69
72
|
stoppedBy?: StopSource;
|
|
70
73
|
seq?: number;
|
|
71
74
|
}): "completed" | "failed" | "killed" | undefined;
|
|
@@ -98,6 +101,9 @@ export declare function settleRevivedAgentLane(core: DurableAgentCore, id: strin
|
|
|
98
101
|
result?: string;
|
|
99
102
|
resultFull?: string;
|
|
100
103
|
error?: string;
|
|
104
|
+
errorCode?: string;
|
|
105
|
+
retryable?: boolean;
|
|
106
|
+
errorKind?: string;
|
|
101
107
|
}): "completed" | "failed" | "killed" | undefined;
|
|
102
108
|
export declare function unmarkRetainedContinuationLane(core: DurableAgentCore, id: string): void;
|
|
103
109
|
export declare function attachAgentNotifyLane(core: DurableAgentCore, id: string, notify: NonNullable<BackgroundAgentTaskHandle["notify"]>, cycle?: number): void;
|
|
@@ -3,6 +3,7 @@ import { uuidv7 } from "../internal/harness.js";
|
|
|
3
3
|
import { canAccessAgentRecord, BackgroundAgentStoreError, } from "./background-agent-store.js";
|
|
4
4
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
5
5
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
6
|
+
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
6
7
|
import { mintCompletionId, commitCompletionIdIfEmpty, clipTaskOutput, assertOwnership, sleepPollStep, alreadyTerminalStopNote, canAccess, normalizeAgentName, closestName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, } from "./task-registry-shared.js";
|
|
7
8
|
import { buildToolResultRef, OFFLOAD_TOOL_NAME } from "./tool-result-store.js";
|
|
8
9
|
export function ensureDurableHeartbeatLane(core) {
|
|
@@ -636,6 +637,7 @@ export async function consumeParkedFlipLane(core, id, stores, ticket) {
|
|
|
636
637
|
handle.resolveParkedStop = undefined;
|
|
637
638
|
handle.updatedAt = Date.now();
|
|
638
639
|
handle.completionId = undefined;
|
|
640
|
+
handle.terminalNotified = undefined;
|
|
639
641
|
if (flipped.seq !== undefined)
|
|
640
642
|
handle.cycleSeq = flipped.seq;
|
|
641
643
|
return true;
|
|
@@ -681,6 +683,7 @@ export function settleBackgroundAgentLane(core, id, outcome) {
|
|
|
681
683
|
handle.status = outcome.status;
|
|
682
684
|
mintCompletionId(handle);
|
|
683
685
|
handle.notify = undefined;
|
|
686
|
+
handle.onReapTerminal = undefined;
|
|
684
687
|
for (const [, , qResolve] of handle.preAttachQueue ?? [])
|
|
685
688
|
qResolve?.({ ok: false, reason: "not_running" });
|
|
686
689
|
handle.preAttachQueue = undefined;
|
|
@@ -699,6 +702,12 @@ export function settleBackgroundAgentLane(core, id, outcome) {
|
|
|
699
702
|
}
|
|
700
703
|
if (outcome.error !== undefined && !(outcome.status === "killed" && outcome.error === BG_AGENT_REAP_STOP_ERROR)) {
|
|
701
704
|
handle.error = outcome.error;
|
|
705
|
+
if (outcome.errorCode !== undefined)
|
|
706
|
+
handle.errorCode = outcome.errorCode;
|
|
707
|
+
if (outcome.retryable !== undefined)
|
|
708
|
+
handle.errorRetryable = outcome.retryable;
|
|
709
|
+
if (outcome.errorKind !== undefined)
|
|
710
|
+
handle.errorKind = outcome.errorKind;
|
|
702
711
|
}
|
|
703
712
|
handle.updatedAt = Date.now();
|
|
704
713
|
if (outcome.seq !== undefined)
|
|
@@ -837,9 +846,13 @@ export function reviveBackgroundAgentLane(core, id, access, abort) {
|
|
|
837
846
|
handle.resultFull = undefined;
|
|
838
847
|
handle.spillRef = undefined;
|
|
839
848
|
handle.error = undefined;
|
|
849
|
+
handle.errorCode = undefined;
|
|
850
|
+
handle.errorRetryable = undefined;
|
|
851
|
+
handle.errorKind = undefined;
|
|
840
852
|
handle.resultIsPartial = undefined;
|
|
841
853
|
handle.stopSource = undefined;
|
|
842
854
|
handle.completionId = undefined;
|
|
855
|
+
handle.terminalNotified = undefined;
|
|
843
856
|
handle.reviveCycle = (handle.reviveCycle ?? 0) + 1;
|
|
844
857
|
handle.cycleSeq = (handle.cycleSeq ?? 1) + 1;
|
|
845
858
|
handle.updatedAt = Date.now();
|
|
@@ -1022,6 +1035,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1022
1035
|
...(row.resultIsPartial ? { partial_result: true } : {}),
|
|
1023
1036
|
...(row.completionId !== undefined ? { completionId: row.completionId } : {}),
|
|
1024
1037
|
},
|
|
1038
|
+
...(row.status === "failed" ? { isError: true } : {}),
|
|
1025
1039
|
};
|
|
1026
1040
|
}
|
|
1027
1041
|
export async function spillClippedAgentResult(handle, full, clipped, store, sessionId) {
|
|
@@ -1057,6 +1071,9 @@ The agent is durably suspended, waiting for an approval decision. It resumes whe
|
|
|
1057
1071
|
}
|
|
1058
1072
|
const fullResult = handle.result ? (handle.resultFull ?? handle.result) : undefined;
|
|
1059
1073
|
const resultText = fullResult !== undefined ? await spillClippedAgentResult(handle, fullResult, clipTaskOutput(fullResult, handle.outputFile), store, sessionId) : undefined;
|
|
1074
|
+
const kindClause = handle.status === "failed" && handle.errorKind !== undefined && handle.errorRetryable !== undefined
|
|
1075
|
+
? ` (error_kind: ${handle.errorKind}, retryable: ${handle.errorRetryable})`
|
|
1076
|
+
: "";
|
|
1060
1077
|
const body = running
|
|
1061
1078
|
? oneShot === true
|
|
1062
1079
|
? `status: running
|
|
@@ -1064,7 +1081,7 @@ This is a ONE-SHOT submission — there is no later turn for a background notifi
|
|
|
1064
1081
|
: `status: running
|
|
1065
1082
|
The agent is still working — you will be notified when it completes.`
|
|
1066
1083
|
: `status: ${handle.status}
|
|
1067
|
-
${handle.error ? `error: ${handle.error}
|
|
1084
|
+
${handle.error ? `error: ${handle.error}${kindClause}
|
|
1068
1085
|
` : ""}${handle.result ? `--- result${handle.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
|
|
1069
1086
|
${resultText}` : "(no result text)"}`;
|
|
1070
1087
|
return {
|
|
@@ -1076,9 +1093,15 @@ ${resultText}` : "(no result text)"}`;
|
|
|
1076
1093
|
retrieval_status: retrieval,
|
|
1077
1094
|
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
1078
1095
|
...(handle.status === "killed" && handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
1096
|
+
...(handle.status === "failed" && handle.error !== undefined
|
|
1097
|
+
? { error: delimitUntrusted("agent error", boundedRedactedSummary(handle.error, 300)) }
|
|
1098
|
+
: {}),
|
|
1099
|
+
...(handle.status === "failed" && handle.errorCode !== undefined ? { errorCode: handle.errorCode } : {}),
|
|
1100
|
+
...(handle.status === "failed" && handle.errorRetryable !== undefined ? { retryable: handle.errorRetryable } : {}),
|
|
1079
1101
|
...(handle.resultIsPartial ? { partial_result: true } : {}),
|
|
1080
1102
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1081
1103
|
},
|
|
1104
|
+
...(handle.status === "failed" ? { isError: true } : {}),
|
|
1082
1105
|
};
|
|
1083
1106
|
}
|
|
1084
1107
|
export async function stopBackgroundAgentLane(core, handle) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
2
|
-
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP,
|
|
2
|
+
import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, MONITOR_SPILL_CAP_CHARS, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
|
|
3
3
|
import { buildToolResultRef, OFFLOAD_TOOL_NAME } from "./tool-result-store.js";
|
|
4
4
|
export function registerMonitorLane(core, input) {
|
|
5
5
|
assertOwnership(input, "registerMonitor");
|
|
@@ -49,14 +49,14 @@ function spillRolledMonitorChunk(handle, stream, dropped) {
|
|
|
49
49
|
const store = handle.toolResultStore;
|
|
50
50
|
if (store === undefined)
|
|
51
51
|
return;
|
|
52
|
-
const used = handle.
|
|
53
|
-
if (used >=
|
|
52
|
+
const used = handle.spillCharsUsed ?? 0;
|
|
53
|
+
if (used >= MONITOR_SPILL_CAP_CHARS) {
|
|
54
54
|
handle.spillCapped = true;
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
const n = stream === "out" ? (handle.spillSegCount ?? 0) : (handle.spillErrSegCount ?? 0);
|
|
58
58
|
const ref = buildToolResultRef(handle.spillSessionId ?? "no-session", `${handle.id}_${stream}_seg${n}`);
|
|
59
|
-
handle.
|
|
59
|
+
handle.spillCharsUsed = used + dropped.length;
|
|
60
60
|
if (stream === "out")
|
|
61
61
|
handle.spillSegCount = n + 1;
|
|
62
62
|
else
|
|
@@ -88,7 +88,7 @@ function monitorSpillNote(handle) {
|
|
|
88
88
|
const coverage = handle.spillFailed === true
|
|
89
89
|
? " — a write failed partway through; the ref chain may be INCOMPLETE, read what is there"
|
|
90
90
|
: handle.spillCapped === true
|
|
91
|
-
? ` — spill cap (${
|
|
91
|
+
? ` — spill cap (${MONITOR_SPILL_CAP_CHARS} chars) reached; earlier segments retained, later rolls were not spilled`
|
|
92
92
|
: "";
|
|
93
93
|
return `; spilled to ${clauses.join(", ")} (read back via ${OFFLOAD_TOOL_NAME})${coverage}`;
|
|
94
94
|
}
|
|
@@ -359,7 +359,7 @@ function explicitStopTerminalNote(handle, alreadyGone) {
|
|
|
359
359
|
task_type: "monitor",
|
|
360
360
|
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
361
361
|
status: "killed",
|
|
362
|
-
stoppedBy: handle.stoppedBy,
|
|
362
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
363
363
|
summary: `${terminalTaskSummary("monitor", label, "killed")} — ${detail}${droppedGapNote(handle.spool)}`,
|
|
364
364
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
365
365
|
});
|
|
@@ -16,6 +16,8 @@ export interface UnifiedTaskOutput {
|
|
|
16
16
|
retrieval_status: TaskRetrievalStatus;
|
|
17
17
|
content?: string;
|
|
18
18
|
error?: string;
|
|
19
|
+
errorCode?: string;
|
|
20
|
+
retryable?: boolean;
|
|
19
21
|
stoppedBy?: StopSource;
|
|
20
22
|
seq?: number;
|
|
21
23
|
partial_result?: boolean;
|
|
@@ -135,9 +137,14 @@ export interface BackgroundAgentTaskHandle extends SemaTaskHandle {
|
|
|
135
137
|
resultFull?: string;
|
|
136
138
|
spillRef?: string;
|
|
137
139
|
error?: string;
|
|
140
|
+
errorCode?: string;
|
|
141
|
+
errorRetryable?: boolean;
|
|
142
|
+
errorKind?: string;
|
|
138
143
|
resultIsPartial?: boolean;
|
|
139
144
|
stopSource?: StopSource;
|
|
140
145
|
stoppedBy?: StopSource;
|
|
146
|
+
onReapTerminal?: () => void;
|
|
147
|
+
terminalNotified?: true;
|
|
141
148
|
}
|
|
142
149
|
export interface MonitorTimers {
|
|
143
150
|
setInterval: (fn: () => unknown, ms: number) => unknown;
|
|
@@ -188,7 +195,7 @@ export interface MonitorTaskHandle extends SemaTaskHandle {
|
|
|
188
195
|
spillSessionId?: string;
|
|
189
196
|
spillSegCount?: number;
|
|
190
197
|
spillErrSegCount?: number;
|
|
191
|
-
|
|
198
|
+
spillCharsUsed?: number;
|
|
192
199
|
spillCapped?: true;
|
|
193
200
|
spillFailed?: true;
|
|
194
201
|
}
|
|
@@ -237,7 +244,7 @@ export declare function statusFromBackground(status: string, exitCode?: number):
|
|
|
237
244
|
export declare function rollSpoolText(spool: {
|
|
238
245
|
rolledChars: number;
|
|
239
246
|
}, s: string, cap: number, onDrop?: (dropped: string) => void): string;
|
|
240
|
-
export declare const
|
|
247
|
+
export declare const MONITOR_SPILL_CAP_CHARS: number;
|
|
241
248
|
export declare function accountDroppedBytes(spool: {
|
|
242
249
|
droppedBytes?: number;
|
|
243
250
|
dropUnknown?: true;
|
|
@@ -94,7 +94,7 @@ export function rollSpoolText(spool, s, cap, onDrop) {
|
|
|
94
94
|
onDrop?.(dropped);
|
|
95
95
|
return s.slice(0, half) + s.slice(s.length - half);
|
|
96
96
|
}
|
|
97
|
-
export const
|
|
97
|
+
export const MONITOR_SPILL_CAP_CHARS = 64 * 1024 * 1024;
|
|
98
98
|
export function accountDroppedBytes(spool, poll) {
|
|
99
99
|
const d = poll.bytesDroppedBeforeCursor ?? 0;
|
|
100
100
|
if (d > 0)
|
|
@@ -133,6 +133,9 @@ export declare class TaskRegistry {
|
|
|
133
133
|
result?: string;
|
|
134
134
|
resultFull?: string;
|
|
135
135
|
error?: string;
|
|
136
|
+
errorCode?: string;
|
|
137
|
+
retryable?: boolean;
|
|
138
|
+
errorKind?: string;
|
|
136
139
|
stoppedBy?: StopSource;
|
|
137
140
|
seq?: number;
|
|
138
141
|
}): "completed" | "failed" | "killed" | undefined;
|
|
@@ -166,9 +169,14 @@ export declare class TaskRegistry {
|
|
|
166
169
|
result?: string;
|
|
167
170
|
resultFull?: string;
|
|
168
171
|
error?: string;
|
|
172
|
+
errorCode?: string;
|
|
173
|
+
retryable?: boolean;
|
|
174
|
+
errorKind?: string;
|
|
169
175
|
}): "completed" | "failed" | "killed" | undefined;
|
|
170
176
|
unmarkRetainedContinuation(id: string): void;
|
|
171
177
|
attachAgentNotify(id: string, notify: NonNullable<BackgroundAgentTaskHandle["notify"]>, cycle?: number): void;
|
|
178
|
+
attachAgentTerminalNotifier(id: string, fn: () => void): void;
|
|
179
|
+
claimAgentTerminalNotify(id: string): boolean;
|
|
172
180
|
deliverToRunningAgent(id: string, access: TaskAccess, notification: import("./task-notification.js").TaskNotificationPayload, opts?: {
|
|
173
181
|
priority?: import("./task-notification.js").SystemInjectionPriority;
|
|
174
182
|
}): Promise<{
|
|
@@ -52,6 +52,23 @@ function bashMirrorGapNote(handle) {
|
|
|
52
52
|
? " [!] The output file is INCOMPLETE (one or more writes to it failed) — trust the result text / TaskOutput over the file."
|
|
53
53
|
: "";
|
|
54
54
|
}
|
|
55
|
+
function explicitStopBashTerminalNote(handle, alreadyGone) {
|
|
56
|
+
const resultText = handle.spool !== undefined ? bashTerminalResult(handle.spool) : "";
|
|
57
|
+
const detail = alreadyGone
|
|
58
|
+
? "killed before completion (stopped via TaskStop; the process was already gone)"
|
|
59
|
+
: "killed before completion (stopped via TaskStop)";
|
|
60
|
+
handle.onTerminal?.({
|
|
61
|
+
task_id: handle.id,
|
|
62
|
+
task_type: "background_bash",
|
|
63
|
+
...(handle.toolUseId !== undefined ? { toolUseId: handle.toolUseId } : {}),
|
|
64
|
+
...(handle.outputFile !== undefined ? { output_file: handle.outputFile } : {}),
|
|
65
|
+
status: "killed",
|
|
66
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
67
|
+
summary: `${terminalTaskSummary("bash", (handle.description ?? "background command").slice(0, 200), "killed")} — ${detail}${bashMirrorGapNote(handle)}${handle.spool !== undefined ? droppedGapNote(handle.spool) : ""}`,
|
|
68
|
+
...(resultText.length > 0 ? { result: resultText, partial: true } : {}),
|
|
69
|
+
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
55
72
|
function monitorInTimeoutWindow(handle) {
|
|
56
73
|
return handle.status === "running" && handle.deadlineAt !== undefined && handle.timers.now() < handle.deadlineAt;
|
|
57
74
|
}
|
|
@@ -145,6 +162,23 @@ export class TaskRegistry {
|
|
|
145
162
|
attachAgentNotify(id, notify, cycle) {
|
|
146
163
|
return attachAgentNotifyLane(this.core, id, notify, cycle);
|
|
147
164
|
}
|
|
165
|
+
attachAgentTerminalNotifier(id, fn) {
|
|
166
|
+
const handle = this.handles.get(id);
|
|
167
|
+
if (!handle || handle.type !== "background_agent")
|
|
168
|
+
return;
|
|
169
|
+
if (handle.status !== "running" && handle.status !== "parked" && handle.status !== "pending")
|
|
170
|
+
return;
|
|
171
|
+
handle.onReapTerminal = fn;
|
|
172
|
+
}
|
|
173
|
+
claimAgentTerminalNotify(id) {
|
|
174
|
+
const handle = this.handles.get(id);
|
|
175
|
+
if (!handle || handle.type !== "background_agent")
|
|
176
|
+
return true;
|
|
177
|
+
if (handle.terminalNotified === true)
|
|
178
|
+
return false;
|
|
179
|
+
handle.terminalNotified = true;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
148
182
|
async deliverToRunningAgent(id, access, notification, opts) {
|
|
149
183
|
return deliverToRunningAgentLane(this.core, id, access, notification, opts);
|
|
150
184
|
}
|
|
@@ -334,6 +368,17 @@ export class TaskRegistry {
|
|
|
334
368
|
async settleKilledForOwner(access, opts) {
|
|
335
369
|
const source = opts?.source ?? "parent";
|
|
336
370
|
const clause = (by) => by === "user" ? "stopped by user" : by === "parent" ? "its parent run ended" : `stopped by ${by}`;
|
|
371
|
+
if (source === "user") {
|
|
372
|
+
for (const handle of this.handles.values()) {
|
|
373
|
+
if (handle.type !== "background_agent" || handle.status !== "running")
|
|
374
|
+
continue;
|
|
375
|
+
if (!canAccess(handle, { owner: access.owner, scope: access.scope }))
|
|
376
|
+
continue;
|
|
377
|
+
if (opts?.skipSessionScoped && handle.sessionScoped)
|
|
378
|
+
continue;
|
|
379
|
+
this.markStopSource(handle.id, source);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
337
382
|
let settled = 0;
|
|
338
383
|
for (const handle of this.handles.values()) {
|
|
339
384
|
if (handle.type !== "background_bash" && handle.type !== "monitor")
|
|
@@ -471,8 +516,17 @@ export class TaskRegistry {
|
|
|
471
516
|
if (!canAccess(handle, access))
|
|
472
517
|
continue;
|
|
473
518
|
this.markStopSource(handle.id, "system");
|
|
519
|
+
const reapTerminalNote = handle.onReapTerminal;
|
|
474
520
|
handle.abort.abort();
|
|
475
521
|
this.settleBackgroundAgent(handle.id, { status: "killed", error: "session released" });
|
|
522
|
+
if (reapTerminalNote !== undefined && handle.terminalNotified !== true) {
|
|
523
|
+
handle.terminalNotified = true;
|
|
524
|
+
try {
|
|
525
|
+
reapTerminalNote();
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
476
530
|
reaped++;
|
|
477
531
|
}
|
|
478
532
|
this.pokeBgQuiescence(sessionId);
|
|
@@ -1122,7 +1176,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1122
1176
|
handle.status = "killed";
|
|
1123
1177
|
handle.updatedAt = Date.now();
|
|
1124
1178
|
mintCompletionId(handle);
|
|
1125
|
-
this.
|
|
1179
|
+
this.notifyTerminalOnce(handle, () => explicitStopBashTerminalNote(handle, true));
|
|
1126
1180
|
return {
|
|
1127
1181
|
content: `Terminated ${handle.id} (the process was already gone).`,
|
|
1128
1182
|
details: {
|
|
@@ -1151,7 +1205,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
1151
1205
|
handle.status = "killed";
|
|
1152
1206
|
handle.updatedAt = Date.now();
|
|
1153
1207
|
mintCompletionId(handle);
|
|
1154
|
-
this.
|
|
1208
|
+
this.notifyTerminalOnce(handle, () => explicitStopBashTerminalNote(handle, false));
|
|
1155
1209
|
return {
|
|
1156
1210
|
content: `Terminated ${handle.id}.`,
|
|
1157
1211
|
details: {
|
|
@@ -1193,9 +1247,10 @@ export function createTaskOutputTool(opts) {
|
|
|
1193
1247
|
let effectiveTimeoutMs = requestedMs;
|
|
1194
1248
|
let waitClampNote = "";
|
|
1195
1249
|
const wall = opts.deadlineMs?.();
|
|
1250
|
+
const requestedWaitMs = Math.max(1, Math.floor(requestedMs ?? BLOCK_DEFAULT_TIMEOUT_MS));
|
|
1196
1251
|
if (args.block !== false && wall !== undefined) {
|
|
1197
1252
|
const remainMs = wall - Date.now() - BLOCK_WAIT_WRITEOUT_RESERVE_MS;
|
|
1198
|
-
const wantMs = Math.min(BLOCK_MAX_TIMEOUT_MS,
|
|
1253
|
+
const wantMs = Math.min(BLOCK_MAX_TIMEOUT_MS, requestedWaitMs);
|
|
1199
1254
|
if (remainMs <= 0) {
|
|
1200
1255
|
effectiveTimeoutMs = 1;
|
|
1201
1256
|
waitClampNote =
|
|
@@ -1203,7 +1258,7 @@ export function createTaskOutputTool(opts) {
|
|
|
1203
1258
|
}
|
|
1204
1259
|
else if (remainMs < wantMs) {
|
|
1205
1260
|
effectiveTimeoutMs = remainMs;
|
|
1206
|
-
waitClampNote = `\n\nNOTE: wait clamped to ${Math.round(remainMs / 1000)}s (requested ${Math.round(
|
|
1261
|
+
waitClampNote = `\n\nNOTE: wait clamped to ${Math.round(remainMs / 1000)}s (requested ${Math.round(requestedWaitMs / 1000)}s) — the task's wall-clock deadline is near. If the task is still running after this wait, do NOT re-wait: proceed with other work or write out your results now.`;
|
|
1207
1262
|
}
|
|
1208
1263
|
}
|
|
1209
1264
|
const r = await opts.registry.pollTask(id, { owner: ctx.taskId ?? opts.owner, scope: ctx.principal ?? opts.scope, ...((ctx.sessionId ?? opts.sessionId) !== undefined ? { sessionId: ctx.sessionId ?? opts.sessionId } : {}) }, {
|
|
@@ -45,6 +45,7 @@ export declare class ScopedToolResultStore implements ToolResultStore {
|
|
|
45
45
|
}
|
|
46
46
|
export declare function isVolatileOffloadStore(store: ToolResultStore): boolean;
|
|
47
47
|
export declare const OFFLOAD_TOOL_NAME = "ReadToolResult";
|
|
48
|
+
export declare function offloadPagebackHint(ref: string, form: "preview" | "cleared", reachableTools?: ReadonlySet<string>): string;
|
|
48
49
|
export declare const PERSISTED_OUTPUT_PREFIX = "<persisted-output ref=";
|
|
49
50
|
export declare const DEFAULT_TOOL_RESULT_THRESHOLD_CHARS = 20000;
|
|
50
51
|
export declare function firstPartyOffloadPolicy(toolName: string): {
|
|
@@ -54,6 +55,6 @@ export declare function firstPartyOffloadPolicy(toolName: string): {
|
|
|
54
55
|
export declare function buildPreview(full: string, ref: string, sizes?: {
|
|
55
56
|
head: number;
|
|
56
57
|
tail: number;
|
|
57
|
-
}): string;
|
|
58
|
-
export declare function withToolResultOffload(tool: AgentTool, store: ToolResultStore, thresholdChars: number, sessionId: string): AgentTool;
|
|
58
|
+
}, reachableTools?: ReadonlySet<string>): string;
|
|
59
|
+
export declare function withToolResultOffload(tool: AgentTool, store: ToolResultStore, thresholdChars: number, sessionId: string, reachableTools?: () => ReadonlySet<string> | undefined): AgentTool;
|
|
59
60
|
export declare function createReadToolResultTool(store: ToolResultStore): AgentTool;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { defineTool, errorResult } from "./tools.js";
|
|
4
|
+
import { TOOL_SEARCH_NAME } from "./runner/tool-disclosure.js";
|
|
4
5
|
export function assertSafeToolResultRef(ref) {
|
|
5
6
|
const bad = ref === "" ||
|
|
6
7
|
ref === "." ||
|
|
@@ -92,6 +93,13 @@ export function isVolatileOffloadStore(store) {
|
|
|
92
93
|
return store instanceof InMemoryToolResultStore;
|
|
93
94
|
}
|
|
94
95
|
export const OFFLOAD_TOOL_NAME = "ReadToolResult";
|
|
96
|
+
export function offloadPagebackHint(ref, form, reachableTools) {
|
|
97
|
+
const activate = reachableTools !== undefined && !reachableTools.has(OFFLOAD_TOOL_NAME)
|
|
98
|
+
? `${OFFLOAD_TOOL_NAME} is not active yet — call ${TOOL_SEARCH_NAME} with {"query":"select:${OFFLOAD_TOOL_NAME}"} to activate it, then `
|
|
99
|
+
: "";
|
|
100
|
+
const core = `${activate === "" && form === "preview" ? "Call" : `${activate}call`} ${OFFLOAD_TOOL_NAME} with ref`;
|
|
101
|
+
return form === "preview" ? `${core}="${ref}" (offset, limit) to read more.` : `full text persisted; ${core} "${ref}" to read it back`;
|
|
102
|
+
}
|
|
95
103
|
export const PERSISTED_OUTPUT_PREFIX = "<persisted-output ref=";
|
|
96
104
|
export const DEFAULT_TOOL_RESULT_THRESHOLD_CHARS = 20_000;
|
|
97
105
|
export function firstPartyOffloadPolicy(toolName) {
|
|
@@ -122,7 +130,7 @@ function totalTextChars(content) {
|
|
|
122
130
|
n += b.text.length;
|
|
123
131
|
return n;
|
|
124
132
|
}
|
|
125
|
-
export function buildPreview(full, ref, sizes) {
|
|
133
|
+
export function buildPreview(full, ref, sizes, reachableTools) {
|
|
126
134
|
const total = full.length;
|
|
127
135
|
const headChars = sizes?.head ?? PREVIEW_HEAD_CHARS;
|
|
128
136
|
const tailChars = sizes?.tail ?? PREVIEW_TAIL_CHARS;
|
|
@@ -131,11 +139,11 @@ export function buildPreview(full, ref, sizes) {
|
|
|
131
139
|
const tail = full.slice(tailStart);
|
|
132
140
|
return (`${PERSISTED_OUTPUT_PREFIX}"${ref}" chars="${total}">\n` +
|
|
133
141
|
`${head}\n` +
|
|
134
|
-
`…[truncated — ${total} chars total.
|
|
142
|
+
`…[truncated — ${total} chars total. ${offloadPagebackHint(ref, "preview", reachableTools)}]\n` +
|
|
135
143
|
`${tail}\n` +
|
|
136
144
|
`</persisted-output>`);
|
|
137
145
|
}
|
|
138
|
-
export function withToolResultOffload(tool, store, thresholdChars, sessionId) {
|
|
146
|
+
export function withToolResultOffload(tool, store, thresholdChars, sessionId, reachableTools) {
|
|
139
147
|
const wrappedExecute = async (toolCallId, params, signal, onUpdate) => {
|
|
140
148
|
const res = await tool.execute(toolCallId, params, signal, onUpdate);
|
|
141
149
|
if (totalTextChars(res.content) <= thresholdChars)
|
|
@@ -149,7 +157,7 @@ export function withToolResultOffload(tool, store, thresholdChars, sessionId) {
|
|
|
149
157
|
const ref = buildToolResultRef(sessionId, toolCallId);
|
|
150
158
|
await store.put(ref, full);
|
|
151
159
|
const images = res.content.filter((b) => b.type !== "text");
|
|
152
|
-
return { ...res, content: [{ type: "text", text: buildPreview(full, ref) }, ...images] };
|
|
160
|
+
return { ...res, content: [{ type: "text", text: buildPreview(full, ref, undefined, reachableTools?.()) }, ...images] };
|
|
153
161
|
};
|
|
154
162
|
return { ...tool, execute: wrappedExecute };
|
|
155
163
|
}
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -104,6 +104,13 @@ export type TraceEvent = {
|
|
|
104
104
|
}>;
|
|
105
105
|
overrideReasons?: Record<string, string>;
|
|
106
106
|
ts: number;
|
|
107
|
+
} | {
|
|
108
|
+
kind: "config.additional_directory_skipped";
|
|
109
|
+
version: 1;
|
|
110
|
+
taskId: string;
|
|
111
|
+
entry: string;
|
|
112
|
+
reason: string;
|
|
113
|
+
ts: number;
|
|
107
114
|
} | {
|
|
108
115
|
kind: "task.end";
|
|
109
116
|
version: 1;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
|
47
47
|
offload?: boolean;
|
|
48
48
|
offloadThresholdChars?: number;
|
|
49
49
|
defer?: boolean;
|
|
50
|
+
alwaysLoad?: boolean;
|
|
50
51
|
contract?: {
|
|
51
52
|
contractId: string;
|
|
52
53
|
implementationRevision: string;
|
|
@@ -256,6 +257,7 @@ export interface TaskSpec {
|
|
|
256
257
|
tools?: ToolSpec[];
|
|
257
258
|
excludeTools?: string[];
|
|
258
259
|
deferTools?: string[];
|
|
260
|
+
alwaysLoadTools?: string[];
|
|
259
261
|
promptProfile?: "simple" | "classic";
|
|
260
262
|
agents?: AgentDefinition[];
|
|
261
263
|
toolPolicy?: import("./tool-policy.js").ToolPolicy;
|
|
@@ -514,9 +516,9 @@ export type TaskEvent = ({
|
|
|
514
516
|
};
|
|
515
517
|
usageMissing?: true;
|
|
516
518
|
stopReason?: string;
|
|
517
|
-
} & TaskEventIdentity) | {
|
|
519
|
+
} & TaskEventIdentity) | ({
|
|
518
520
|
type: "compacted";
|
|
519
|
-
trigger: "auto" | "manual";
|
|
521
|
+
trigger: "auto" | "manual" | "forced";
|
|
520
522
|
tokensBefore: number;
|
|
521
523
|
tokensAfter?: number;
|
|
522
524
|
triggerTokensBefore?: number;
|
|
@@ -534,7 +536,12 @@ export type TaskEvent = ({
|
|
|
534
536
|
clampedRatio?: number;
|
|
535
537
|
clampReason?: "budget" | "walltime" | "tolerance";
|
|
536
538
|
phaseDurations?: import("./auto-compaction.js").CompactionPhaseDurations;
|
|
537
|
-
} | ({
|
|
539
|
+
} & TaskEventIdentity) | ({
|
|
540
|
+
type: "compaction_outcome";
|
|
541
|
+
outcome: Exclude<CompactOutcome, "compacted"> | "suppressed";
|
|
542
|
+
trigger: "auto" | "manual" | "forced";
|
|
543
|
+
reason?: string;
|
|
544
|
+
} & TaskEventIdentity) | ({
|
|
538
545
|
type: "steering_injected";
|
|
539
546
|
source: "deadline_nudge" | "finalize" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
540
547
|
preview: string;
|
|
@@ -8,6 +8,9 @@ export interface CompactionDetails {
|
|
|
8
8
|
modifiedFiles: string[];
|
|
9
9
|
modifiedFilesByRecency?: string[];
|
|
10
10
|
invokedSkills?: InvokedSkillRetention[];
|
|
11
|
+
elidedMessages?: number;
|
|
12
|
+
persistedOutputRefs?: string[];
|
|
13
|
+
activeTools?: string[];
|
|
11
14
|
}
|
|
12
15
|
export interface CompactionResult<T = unknown> {
|
|
13
16
|
summary: string;
|
|
@@ -68,6 +71,8 @@ export interface CompactionPreparation {
|
|
|
68
71
|
previousSummary?: string;
|
|
69
72
|
fileOps: FileOperations;
|
|
70
73
|
invokedSkills: InvokedSkillRetention[];
|
|
74
|
+
persistedOutputRefs?: string[];
|
|
75
|
+
elidedMessages?: number;
|
|
71
76
|
settings: CompactionSettings;
|
|
72
77
|
}
|
|
73
78
|
export declare function prepareCompaction(pathEntries: SessionTreeEntry[], settings: CompactionSettings, charsPerToken?: number, windowTokens?: number): Result<CompactionPreparation | undefined, CompactionError>;
|
|
@@ -2,7 +2,7 @@ import { resolveAgentCoreCompleteFn, } from "../loop/runtime-deps.js";
|
|
|
2
2
|
import { asAgentMessage, convertToLlm, createCompactionSummaryMessage, createCustomMessage, } from "../harness/messages.js";
|
|
3
3
|
import { buildSessionContext } from "../session/session.js";
|
|
4
4
|
import { CompactionError, err, ok, } from "../harness/types.js";
|
|
5
|
-
import { budgetInvokedSkillsRetention, computeFileLists, createFileOps, extractFileOpsFromMessage, extractInvokedSkills, formatFileOperations, readRetainedInvokedSkills, replaceInvokedSkillBodiesForSummary, serializeConversation, stripFileOperationsFooter, } from "./utils.js";
|
|
5
|
+
import { budgetInvokedSkillsRetention, computeFileLists, createFileOps, extractFileOpsFromMessage, extractInvokedSkills, extractPersistedOutputRefs, formatFileOperations, formatPersistedOutputRefs, PERSISTED_OUTPUT_REFS_MAX_ENTRIES, readElidedMessages, readPersistedOutputRefs, readRetainedInvokedSkills, replaceInvokedSkillBodiesForSummary, serializeConversation, stripFileOperationsFooter, } from "./utils.js";
|
|
6
6
|
function safeJsonStringify(value) {
|
|
7
7
|
try {
|
|
8
8
|
return JSON.stringify(value) ?? "undefined";
|
|
@@ -256,6 +256,51 @@ export function findTurnStartIndex(entries, entryIndex, startIndex) {
|
|
|
256
256
|
}
|
|
257
257
|
return -1;
|
|
258
258
|
}
|
|
259
|
+
function enforceToolPairContainment(entries, startIndex, endIndex, cutIndex) {
|
|
260
|
+
const callSites = new Map();
|
|
261
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
262
|
+
const entry = entries[i];
|
|
263
|
+
if (entry.type !== "message")
|
|
264
|
+
continue;
|
|
265
|
+
const msg = entry.message;
|
|
266
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
267
|
+
continue;
|
|
268
|
+
for (const block of msg.content) {
|
|
269
|
+
if (block.type === "toolCall" && typeof block.id === "string") {
|
|
270
|
+
const at = callSites.get(block.id);
|
|
271
|
+
if (at === undefined)
|
|
272
|
+
callSites.set(block.id, [i]);
|
|
273
|
+
else
|
|
274
|
+
at.push(i);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (callSites.size === 0)
|
|
279
|
+
return cutIndex;
|
|
280
|
+
for (let i = cutIndex; i < endIndex; i++) {
|
|
281
|
+
const entry = entries[i];
|
|
282
|
+
if (entry.type !== "message")
|
|
283
|
+
continue;
|
|
284
|
+
const msg = entry.message;
|
|
285
|
+
if (msg.role !== "toolResult")
|
|
286
|
+
continue;
|
|
287
|
+
const sites = callSites.get(msg.toolCallId);
|
|
288
|
+
if (sites === undefined)
|
|
289
|
+
continue;
|
|
290
|
+
let site = -1;
|
|
291
|
+
for (const s of sites) {
|
|
292
|
+
if (s < i)
|
|
293
|
+
site = s;
|
|
294
|
+
else
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
if (site === -1 || site >= cutIndex)
|
|
298
|
+
continue;
|
|
299
|
+
cutIndex = site;
|
|
300
|
+
i = site;
|
|
301
|
+
}
|
|
302
|
+
return cutIndex;
|
|
303
|
+
}
|
|
259
304
|
export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
|
|
260
305
|
const cutPoints = findValidCutPoints(entries, startIndex, endIndex);
|
|
261
306
|
if (cutPoints.length === 0) {
|
|
@@ -290,6 +335,7 @@ export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens, ch
|
|
|
290
335
|
break;
|
|
291
336
|
}
|
|
292
337
|
}
|
|
338
|
+
cutIndex = enforceToolPairContainment(entries, startIndex, endIndex, cutIndex);
|
|
293
339
|
const cutEntry = entries[cutIndex];
|
|
294
340
|
const isUserMessage = cutEntry.type === "message" && cutEntry.message.role === "user";
|
|
295
341
|
const turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);
|
|
@@ -721,6 +767,21 @@ export function prepareCompaction(pathEntries, settings, charsPerToken = DEFAULT
|
|
|
721
767
|
? Math.max(0, windowTokens - settings.reserveTokens - settings.keepRecentTokens) * charsPerToken
|
|
722
768
|
: undefined;
|
|
723
769
|
const invokedSkills = budgetInvokedSkillsRetention(extractInvokedSkills([...messagesToSummarize, ...turnPrefixMessages], prevRetainedSkills), retentionBudgetChars);
|
|
770
|
+
const refSet = new Set();
|
|
771
|
+
if (prevCompactionIndex >= 0 && !pathEntries[prevCompactionIndex].fromHook) {
|
|
772
|
+
for (const r of readPersistedOutputRefs(pathEntries[prevCompactionIndex].details)) {
|
|
773
|
+
refSet.add(r);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
extractPersistedOutputRefs(messagesToSummarize, refSet);
|
|
777
|
+
if (cutPoint.isSplitTurn) {
|
|
778
|
+
extractPersistedOutputRefs(turnPrefixMessages, refSet);
|
|
779
|
+
}
|
|
780
|
+
const persistedOutputRefs = [...refSet].slice(-PERSISTED_OUTPUT_REFS_MAX_ENTRIES);
|
|
781
|
+
const prevElided = prevCompactionIndex >= 0 && !pathEntries[prevCompactionIndex].fromHook
|
|
782
|
+
? readElidedMessages(pathEntries[prevCompactionIndex].details)
|
|
783
|
+
: undefined;
|
|
784
|
+
const elidedMessages = (prevElided ?? 0) + messagesToSummarize.length + turnPrefixMessages.length;
|
|
724
785
|
const [summarizeReady, prefixReady] = replaceInvokedSkillBodiesForSummary([messagesToSummarize, turnPrefixMessages], new Set(invokedSkills.map((s) => s.name)));
|
|
725
786
|
return ok({
|
|
726
787
|
firstKeptEntryId,
|
|
@@ -731,6 +792,8 @@ export function prepareCompaction(pathEntries, settings, charsPerToken = DEFAULT
|
|
|
731
792
|
previousSummary,
|
|
732
793
|
fileOps,
|
|
733
794
|
invokedSkills,
|
|
795
|
+
persistedOutputRefs,
|
|
796
|
+
elidedMessages,
|
|
734
797
|
settings,
|
|
735
798
|
});
|
|
736
799
|
}
|
|
@@ -750,7 +813,7 @@ Summarize the prefix to provide context for the retained suffix:
|
|
|
750
813
|
Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
751
814
|
export { computeFileLists, serializeConversation } from "./utils.js";
|
|
752
815
|
export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry) {
|
|
753
|
-
const { firstKeptEntryId, messagesToSummarize, turnPrefixMessages, isSplitTurn, tokensBefore, previousSummary, fileOps, invokedSkills, settings, } = preparation;
|
|
816
|
+
const { firstKeptEntryId, messagesToSummarize, turnPrefixMessages, isSplitTurn, tokensBefore, previousSummary, fileOps, invokedSkills, persistedOutputRefs, elidedMessages, settings, } = preparation;
|
|
754
817
|
if (!firstKeptEntryId) {
|
|
755
818
|
return err(new CompactionError("invalid_session", "First kept entry has no UUID - session may need migration"));
|
|
756
819
|
}
|
|
@@ -784,6 +847,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
784
847
|
}
|
|
785
848
|
const { readFiles, modifiedFiles, modifiedFilesByRecency } = computeFileLists(fileOps);
|
|
786
849
|
summary += formatFileOperations(readFiles, modifiedFiles);
|
|
850
|
+
summary += formatPersistedOutputRefs(persistedOutputRefs ?? []);
|
|
787
851
|
return ok({
|
|
788
852
|
summary,
|
|
789
853
|
firstKeptEntryId,
|
|
@@ -793,6 +857,8 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
793
857
|
modifiedFiles,
|
|
794
858
|
modifiedFilesByRecency,
|
|
795
859
|
...(invokedSkills !== undefined && invokedSkills.length > 0 ? { invokedSkills } : {}),
|
|
860
|
+
...(persistedOutputRefs !== undefined && persistedOutputRefs.length > 0 ? { persistedOutputRefs } : {}),
|
|
861
|
+
...(elidedMessages !== undefined && elidedMessages > 0 ? { elidedMessages } : {}),
|
|
796
862
|
},
|
|
797
863
|
});
|
|
798
864
|
}
|
|
@@ -21,6 +21,12 @@ export declare function budgetInvokedSkillsRetention(newestFirst: InvokedSkillRe
|
|
|
21
21
|
export declare function replaceInvokedSkillBodiesForSummary(groups: AgentMessage[][], retainedNames: ReadonlySet<string>): AgentMessage[][];
|
|
22
22
|
export declare function readRetainedInvokedSkills(details: unknown): InvokedSkillRetention[];
|
|
23
23
|
export declare function renderInvokedSkillsRetention(skills: InvokedSkillRetention[]): string;
|
|
24
|
+
export declare const PERSISTED_OUTPUT_REFS_MAX_ENTRIES = 50;
|
|
25
|
+
export declare function extractPersistedOutputRefs(messages: AgentMessage[], into: Set<string>): void;
|
|
26
|
+
export declare function readElidedMessages(details: unknown): number | undefined;
|
|
27
|
+
export declare function readCompactionActiveTools(details: unknown): string[];
|
|
28
|
+
export declare function readPersistedOutputRefs(details: unknown): string[];
|
|
29
|
+
export declare function formatPersistedOutputRefs(refs: string[]): string;
|
|
24
30
|
export declare function computeFileLists(fileOps: FileOperations): {
|
|
25
31
|
readFiles: string[];
|
|
26
32
|
modifiedFiles: string[];
|