neoctl 0.2.30 → 0.2.32
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/README.md +37 -3
- package/dist/agents/agent-activity.d.ts +3 -0
- package/dist/agents/agent-activity.js +33 -6
- package/dist/agents/agent-activity.js.map +1 -1
- package/dist/agents/agent-definition.d.ts +2 -2
- package/dist/agents/agent-definition.js +6 -6
- package/dist/agents/agent-definition.js.map +1 -1
- package/dist/agents/agent-report-tool.d.ts +1 -1
- package/dist/agents/agent-report-tool.js +4 -4
- package/dist/agents/agent-report-tool.js.map +1 -1
- package/dist/agents/agent-tool.d.ts +1 -1
- package/dist/agents/agent-tool.js +97 -18
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/agents/local-agent-task.d.ts +12 -0
- package/dist/agents/local-agent-task.js +38 -1
- package/dist/agents/local-agent-task.js.map +1 -1
- package/dist/agents/smoke-agents.js +37 -26
- package/dist/agents/smoke-agents.js.map +1 -1
- package/dist/context/compaction.d.ts +20 -4
- package/dist/context/compaction.js +378 -187
- package/dist/context/compaction.js.map +1 -1
- package/dist/context/context-manager.d.ts +7 -1
- package/dist/context/context-manager.js +15 -11
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/prompts.js +8 -6
- package/dist/context/prompts.js.map +1 -1
- package/dist/context/smoke-context.js +273 -24
- package/dist/context/smoke-context.js.map +1 -1
- package/dist/core/assistant-output-filter.d.ts +1 -0
- package/dist/core/assistant-output-filter.js +54 -2
- package/dist/core/assistant-output-filter.js.map +1 -1
- package/dist/core/context-metrics.d.ts +1 -0
- package/dist/core/context-metrics.js +18 -12
- package/dist/core/context-metrics.js.map +1 -1
- package/dist/core/image-registry.d.ts +16 -3
- package/dist/core/image-registry.js +65 -30
- package/dist/core/image-registry.js.map +1 -1
- package/dist/core/image-storage.d.ts +15 -1
- package/dist/core/image-storage.js +140 -17
- package/dist/core/image-storage.js.map +1 -1
- package/dist/core/message-pipeline.js +18 -4
- package/dist/core/message-pipeline.js.map +1 -1
- package/dist/core/query-engine.d.ts +12 -2
- package/dist/core/query-engine.js +59 -8
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +13 -2
- package/dist/core/query.js +144 -27
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent.d.ts +13 -2
- package/dist/core/run-agent.js +16 -13
- package/dist/core/run-agent.js.map +1 -1
- package/dist/core/smoke-core-loop.js +121 -3
- package/dist/core/smoke-core-loop.js.map +1 -1
- package/dist/core/smoke-image-integrity.d.ts +1 -0
- package/dist/core/smoke-image-integrity.js +67 -0
- package/dist/core/smoke-image-integrity.js.map +1 -0
- package/dist/core/state.d.ts +1 -1
- package/dist/core/state.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/model/model-metadata.json +4 -4
- package/dist/plugins/plugin-system.d.ts +61 -0
- package/dist/plugins/plugin-system.js +189 -0
- package/dist/plugins/plugin-system.js.map +1 -0
- package/dist/plugins/smoke-plugin-system.d.ts +1 -0
- package/dist/plugins/smoke-plugin-system.js +63 -0
- package/dist/plugins/smoke-plugin-system.js.map +1 -0
- package/dist/repl/index.js +22 -17
- package/dist/repl/index.js.map +1 -1
- package/dist/secrets/secret-redaction.d.ts +5 -0
- package/dist/secrets/secret-redaction.js +43 -0
- package/dist/secrets/secret-redaction.js.map +1 -1
- package/dist/secrets/secret-types.d.ts +4 -0
- package/dist/secrets/secret-types.js.map +1 -1
- package/dist/secrets/smoke-secrets.js +7 -3
- package/dist/secrets/smoke-secrets.js.map +1 -1
- package/dist/session/session-export.d.ts +2 -0
- package/dist/session/session-export.js +10 -0
- package/dist/session/session-export.js.map +1 -1
- package/dist/session/session-store.d.ts +41 -0
- package/dist/session/session-store.js +292 -59
- package/dist/session/session-store.js.map +1 -1
- package/dist/session/smoke-session.js +127 -7
- package/dist/session/smoke-session.js.map +1 -1
- package/dist/skills/smoke-skills.js +5 -5
- package/dist/skills/smoke-skills.js.map +1 -1
- package/dist/tasks/subagent-tools.d.ts +31 -0
- package/dist/tasks/{task-tools.js → subagent-tools.js} +26 -32
- package/dist/tasks/subagent-tools.js.map +1 -0
- package/dist/tools/builtins/edit-tool.js +24 -6
- package/dist/tools/builtins/edit-tool.js.map +1 -1
- package/dist/tools/builtins/exec-process-manager.d.ts +13 -0
- package/dist/tools/builtins/exec-process-manager.js +57 -7
- package/dist/tools/builtins/exec-process-manager.js.map +1 -1
- package/dist/tools/builtins/exec-tool.js +34 -10
- package/dist/tools/builtins/exec-tool.js.map +1 -1
- package/dist/tools/builtins/filesystem-tools.js +2 -4
- package/dist/tools/builtins/filesystem-tools.js.map +1 -1
- package/dist/tools/builtins/grep-tool.js +2 -3
- package/dist/tools/builtins/grep-tool.js.map +1 -1
- package/dist/tools/builtins/image-generation-tool.js +36 -46
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/builtins/image-loader-tool.js +12 -7
- package/dist/tools/builtins/image-loader-tool.js.map +1 -1
- package/dist/tools/builtins/image-note-tool.js +8 -4
- package/dist/tools/builtins/image-note-tool.js.map +1 -1
- package/dist/tools/builtins/plan-tool.js +1 -1
- package/dist/tools/builtins/plan-tool.js.map +1 -1
- package/dist/tools/builtins/search-tool.js +3 -3
- package/dist/tools/builtins/search-tool.js.map +1 -1
- package/dist/tools/registry.d.ts +8 -1
- package/dist/tools/registry.js +37 -13
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-tool-use.js +3 -2
- package/dist/tools/run-tool-use.js.map +1 -1
- package/dist/tools/smoke-exec-process.js +12 -0
- package/dist/tools/smoke-exec-process.js.map +1 -1
- package/dist/tools/smoke-tool-system.js +68 -31
- package/dist/tools/smoke-tool-system.js.map +1 -1
- package/dist/tools/tool-catalog.d.ts +36 -0
- package/dist/tools/tool-catalog.js +64 -0
- package/dist/tools/tool-catalog.js.map +1 -0
- package/dist/tools/tool-orchestration.d.ts +20 -3
- package/dist/tools/tool-orchestration.js +29 -4
- package/dist/tools/tool-orchestration.js.map +1 -1
- package/dist/tools/tool.d.ts +13 -0
- package/dist/tools/tool.js.map +1 -1
- package/dist/types/events.d.ts +28 -1
- package/dist/types/messages.d.ts +13 -5
- package/dist/types/messages.js +4 -0
- package/dist/types/messages.js.map +1 -1
- package/dist/ui/display-message.d.ts +10 -2
- package/dist/ui/display-message.js +18 -9
- package/dist/ui/display-message.js.map +1 -1
- package/dist/web/index.d.ts +198 -9
- package/dist/web/index.js +1208 -146
- package/dist/web/index.js.map +1 -1
- package/dist/web/runtime-context-protocol.d.ts +19 -1
- package/dist/web/runtime-context-protocol.js +19 -3
- package/dist/web/runtime-context-protocol.js.map +1 -1
- package/dist/web/smoke-runtime-context-protocol.js +13 -2
- package/dist/web/smoke-runtime-context-protocol.js.map +1 -1
- package/dist/web/smoke-web-history.d.ts +1 -0
- package/dist/web/smoke-web-history.js +179 -0
- package/dist/web/smoke-web-history.js.map +1 -0
- package/dist/web/smoke-web-terminal-tasks.js +35 -3
- package/dist/web/smoke-web-terminal-tasks.js.map +1 -1
- package/package.json +4 -1
- package/dist/tasks/task-tools.d.ts +0 -31
- package/dist/tasks/task-tools.js.map +0 -1
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { createTextMessage } from "../types/messages.js";
|
|
1
|
+
import { createTextMessage, withoutThinkingBlocks } from "../types/messages.js";
|
|
2
2
|
import { estimateTextTokens } from "../core/context-metrics.js";
|
|
3
3
|
import { buildImageRegistry, extractRegistryFromBoundary, mergeImageRegistries, formatImageRegistryForContext } from "../core/image-registry.js";
|
|
4
4
|
import { resolveImageBlockDataLengthSync } from "../core/image-storage.js";
|
|
5
5
|
export const CLEARED_TOOL_RESULT_CONTENT = "[Old tool result content cleared]";
|
|
6
|
+
const COMPACT_USER_MESSAGE_MAX_TOKENS = 20_000;
|
|
6
7
|
export class DeterministicCompactor {
|
|
7
8
|
async compact(messages, options = {}) {
|
|
8
9
|
const micro = microCompactIfNeeded(messages, options);
|
|
9
|
-
const auto = autoCompactIfNeeded(micro.messages, options);
|
|
10
|
-
const snipped = snipCompactIfNeeded(auto.messages, options);
|
|
11
|
-
if (snipped.changed)
|
|
12
|
-
return mergeResults([micro, auto, snipped], snipped.reason);
|
|
10
|
+
const auto = autoCompactIfNeeded(micro.messages, options, messages);
|
|
13
11
|
if (auto.changed)
|
|
14
|
-
return
|
|
12
|
+
return auto;
|
|
13
|
+
const snipped = snipCompactIfNeeded(micro.messages, options);
|
|
14
|
+
if (snipped.changed)
|
|
15
|
+
return mergeResults([micro, snipped], snipped.reason);
|
|
15
16
|
if (micro.changed)
|
|
16
17
|
return micro;
|
|
17
18
|
return { messages: [...messages], changed: false, reason: "none" };
|
|
@@ -24,87 +25,53 @@ export class DeterministicCompactor {
|
|
|
24
25
|
}
|
|
25
26
|
async reactiveCompact(messages, error, options = {}) {
|
|
26
27
|
const micro = microCompactIfNeeded(messages, options);
|
|
27
|
-
const reactive = reactiveCompactWithSummary(micro.messages, `Reactive compact after model context error: ${error.message}`, options);
|
|
28
|
-
return
|
|
28
|
+
const reactive = reactiveCompactWithSummary(messages, micro.messages, `Reactive compact after model context error: ${error.message}`, options);
|
|
29
|
+
return reactive;
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
export class ModelDrivenCompactor {
|
|
32
33
|
modelGateway;
|
|
33
|
-
fallback = new DeterministicCompactor();
|
|
34
34
|
constructor(modelGateway) {
|
|
35
35
|
this.modelGateway = modelGateway;
|
|
36
36
|
}
|
|
37
37
|
async compact(messages, options = {}) {
|
|
38
|
-
|
|
39
|
-
const auto = await this.modelAutoCompactIfNeeded(micro.messages, options);
|
|
40
|
-
const snipped = snipCompactIfNeeded(auto.messages, options);
|
|
41
|
-
if (snipped.changed)
|
|
42
|
-
return mergeResults([micro, auto, snipped], snipped.reason);
|
|
43
|
-
if (auto.changed)
|
|
44
|
-
return mergeResults([micro, auto], auto.reason);
|
|
45
|
-
if (micro.changed)
|
|
46
|
-
return micro;
|
|
47
|
-
return { messages: [...messages], changed: false, reason: "none" };
|
|
48
|
-
}
|
|
49
|
-
async manualCompact(messages, options = {}) {
|
|
50
|
-
const splitIndex = computeKeepRecentSplit(messages, options);
|
|
51
|
-
const recent = messages.slice(splitIndex);
|
|
52
|
-
const older = messages.slice(0, splitIndex);
|
|
53
|
-
if (older.length === 0)
|
|
38
|
+
if (!shouldCompactForBudget(messages, options, options.autoCompactMaxChars)) {
|
|
54
39
|
return { messages: [...messages], changed: false, reason: "none" };
|
|
55
|
-
try {
|
|
56
|
-
const summary = await this.summarizeWithModel(older, MANUAL_COMPACT_INSTRUCTIONS, options);
|
|
57
|
-
return buildCompactionResult(messages, recent, summary, "manualcompact", true);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return this.fallback.manualCompact?.(messages, options) ?? manualCompactWithSummary(messages, options);
|
|
61
40
|
}
|
|
41
|
+
return this.createCheckpoint(messages, "autocompact", options);
|
|
42
|
+
}
|
|
43
|
+
async manualCompact(messages, options = {}) {
|
|
44
|
+
return this.createCheckpoint(messages, "manualcompact", options);
|
|
62
45
|
}
|
|
63
46
|
async pureCompact(messages, options = {}) {
|
|
64
47
|
return pureCompactWithSanitizedSummary(messages, options);
|
|
65
48
|
}
|
|
66
|
-
async reactiveCompact(messages,
|
|
67
|
-
|
|
68
|
-
const splitIndex = computeKeepRecentSplit(micro.messages, options);
|
|
69
|
-
const recent = micro.messages.slice(splitIndex);
|
|
70
|
-
const older = micro.messages.slice(0, splitIndex);
|
|
71
|
-
try {
|
|
72
|
-
const summary = await this.summarizeWithModel(older, buildReactiveCompactInstruction(error), options);
|
|
73
|
-
const reactive = buildCompactionResult(micro.messages, recent, summary, "reactive_compact", true);
|
|
74
|
-
return micro.changed ? mergeResults([micro, reactive], reactive.reason) : reactive;
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
const fallback = await this.fallback.reactiveCompact(micro.messages, error, options);
|
|
78
|
-
return micro.changed ? mergeResults([micro, fallback], fallback.reason) : fallback;
|
|
79
|
-
}
|
|
49
|
+
async reactiveCompact(messages, _error, options = {}) {
|
|
50
|
+
return this.createCheckpoint(messages, "reactive_compact", options);
|
|
80
51
|
}
|
|
81
|
-
async
|
|
82
|
-
if (
|
|
83
|
-
return { messages: [
|
|
84
|
-
}
|
|
85
|
-
const splitIndex = computeKeepRecentSplit(messages, options);
|
|
86
|
-
const recent = messages.slice(splitIndex);
|
|
87
|
-
const older = messages.slice(0, splitIndex);
|
|
88
|
-
if (older.length === 0)
|
|
89
|
-
return { messages: [...messages], changed: false, reason: "none" };
|
|
52
|
+
async createCheckpoint(messages, reason, options) {
|
|
53
|
+
if (messages.length === 0)
|
|
54
|
+
return { messages: [], changed: false, reason: "none" };
|
|
90
55
|
try {
|
|
91
|
-
const summary = await this.summarizeWithModel(
|
|
92
|
-
return buildCompactionResult(messages,
|
|
56
|
+
const summary = await this.summarizeWithModel(messages, CHECKPOINT_COMPACT_INSTRUCTIONS, options);
|
|
57
|
+
return buildCompactionResult(messages, summary, reason, true, options);
|
|
93
58
|
}
|
|
94
59
|
catch {
|
|
95
|
-
return
|
|
60
|
+
return checkpointCompactWithSummary(messages, options, reason);
|
|
96
61
|
}
|
|
97
62
|
}
|
|
98
63
|
async summarizeWithModel(messages, instructions, options) {
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
64
|
+
const compactMessages = prepareMessagesForModelSummary(messages, instructions);
|
|
65
|
+
if (compactMessages.length === 1)
|
|
101
66
|
return "No earlier conversation content required preservation.";
|
|
102
|
-
let
|
|
67
|
+
let streamedText = "";
|
|
68
|
+
let finalAssistantText = "";
|
|
69
|
+
let completed = false;
|
|
103
70
|
for await (const event of this.modelGateway.stream({
|
|
104
71
|
model: options.compactModel,
|
|
105
|
-
|
|
106
|
-
messages: [createTextMessage("user", transcript)],
|
|
72
|
+
messages: compactMessages,
|
|
107
73
|
tools: [],
|
|
74
|
+
toolChoice: "none",
|
|
108
75
|
stream: true,
|
|
109
76
|
maxOutputTokens: options.compactMaxOutputTokens ?? 2500,
|
|
110
77
|
queryOrigin: "compact",
|
|
@@ -113,18 +80,22 @@ export class ModelDrivenCompactor {
|
|
|
113
80
|
},
|
|
114
81
|
})) {
|
|
115
82
|
if (event.type === "assistant_delta")
|
|
116
|
-
|
|
83
|
+
streamedText += event.text;
|
|
117
84
|
if (event.type === "assistant_message")
|
|
118
|
-
|
|
85
|
+
finalAssistantText = extractText(event.message).trim() || finalAssistantText;
|
|
86
|
+
if (event.type === "response_completed")
|
|
87
|
+
completed = true;
|
|
88
|
+
if (event.type === "response_incomplete")
|
|
89
|
+
throw new Error(`Model compaction response was incomplete: ${event.reason ?? "unknown reason"}`);
|
|
119
90
|
if (event.type === "error")
|
|
120
91
|
throw event.error;
|
|
121
92
|
}
|
|
122
|
-
|
|
93
|
+
if (!completed)
|
|
94
|
+
throw new Error("Model compaction stream ended before completion");
|
|
95
|
+
const summary = (finalAssistantText || streamedText).trim();
|
|
123
96
|
if (!summary)
|
|
124
97
|
throw new Error("Model compaction produced an empty summary");
|
|
125
|
-
return summary
|
|
126
|
-
? `${summary.slice(0, options.summaryMaxChars ?? 6000)}\n- ...model summary truncated...`
|
|
127
|
-
: summary;
|
|
98
|
+
return summary;
|
|
128
99
|
}
|
|
129
100
|
}
|
|
130
101
|
export class NoopCompactor {
|
|
@@ -160,6 +131,40 @@ export class ManualOnlyCompactor {
|
|
|
160
131
|
return { messages: [...messages], changed: false, reason: "none" };
|
|
161
132
|
}
|
|
162
133
|
}
|
|
134
|
+
export function withCompactionReport(result, sourceMessages) {
|
|
135
|
+
if (!result.changed || result.report)
|
|
136
|
+
return result;
|
|
137
|
+
const reason = result.reason && result.reason !== "none" ? result.reason : "autocompact";
|
|
138
|
+
const boundary = findLastCompactBoundary(result.messages);
|
|
139
|
+
const boundaryText = boundary ? extractText(boundary) : "";
|
|
140
|
+
const continuationState = extractCompactState(boundaryText) || result.summary || "";
|
|
141
|
+
const imageRegistry = boundary?.metadata?.imageRegistry;
|
|
142
|
+
const report = {
|
|
143
|
+
reason,
|
|
144
|
+
summary: result.summary || continuationState,
|
|
145
|
+
continuationState,
|
|
146
|
+
sourceMessages: Math.max(0, Math.round(sourceMessages)),
|
|
147
|
+
preservedUserMessages: result.messages.filter((message) => message.metadata?.compactPreservedUser === true).length,
|
|
148
|
+
newWindowMessages: result.messages.length,
|
|
149
|
+
charsFreed: Math.max(0, result.charsFreed ?? result.tokensFreed ?? 0),
|
|
150
|
+
modelDriven: boundary?.metadata?.modelDriven === true,
|
|
151
|
+
imageCount: Array.isArray(imageRegistry?.images) ? imageRegistry.images.length : 0,
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
...result,
|
|
155
|
+
messages: result.messages.map((message) => message === boundary
|
|
156
|
+
? { ...message, metadata: { ...message.metadata, compactionReport: report } }
|
|
157
|
+
: message),
|
|
158
|
+
report,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function findLastCompactBoundary(messages) {
|
|
162
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
163
|
+
if (messages[index].metadata?.compactBoundary === true)
|
|
164
|
+
return messages[index];
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
163
168
|
export function estimateMessagesChars(messages) {
|
|
164
169
|
return messages.reduce((total, message) => total + serializeMessage(message).length, 0);
|
|
165
170
|
}
|
|
@@ -234,20 +239,17 @@ export function microCompactIfNeeded(messages, options = {}) {
|
|
|
234
239
|
tokensFreed: freed,
|
|
235
240
|
};
|
|
236
241
|
}
|
|
237
|
-
export function autoCompactIfNeeded(messages, options = {}) {
|
|
242
|
+
export function autoCompactIfNeeded(messages, options = {}, sourceMessages = messages) {
|
|
238
243
|
const maxChars = options.autoCompactMaxChars ?? 50000;
|
|
239
244
|
if (!shouldCompactForBudget(messages, options, maxChars))
|
|
240
245
|
return { messages: [...messages], changed: false, reason: "none" };
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
return { messages: [...messages], changed: false, reason: "none" };
|
|
246
|
-
const summary = buildHistorySummary(older, options.summaryMaxChars ?? 5000);
|
|
247
|
-
return buildCompactionResult(messages, recent, summary, "autocompact", false);
|
|
246
|
+
if (sourceMessages.length === 0)
|
|
247
|
+
return { messages: [], changed: false, reason: "none" };
|
|
248
|
+
const summary = buildCheckpointFallbackSummary(sourceMessages, options.summaryMaxChars ?? 5000);
|
|
249
|
+
return buildCompactionResult(sourceMessages, summary || "No conversation content was available to summarize.", "autocompact", false, options, messages);
|
|
248
250
|
}
|
|
249
251
|
function computeKeepRecentSplit(messages, options, fallbackCount) {
|
|
250
|
-
const tokenBudget = options.keepRecentTokenBudget;
|
|
252
|
+
const tokenBudget = options.keepRecentTokenBudget ?? defaultRecentTokenBudget(options.contextWindowTokens);
|
|
251
253
|
const keepCount = fallbackCount ?? options.keepRecentMessages ?? 8;
|
|
252
254
|
if (!tokenBudget)
|
|
253
255
|
return Math.max(0, messages.length - keepCount);
|
|
@@ -265,23 +267,20 @@ function shouldCompactForBudget(messages, options, fallbackMaxChars, triggerRati
|
|
|
265
267
|
const triggerRatio = triggerRatioOverride ?? options.autoCompactTriggerRatio ?? 0.92;
|
|
266
268
|
return options.estimatedInputTokens / options.contextWindowTokens >= triggerRatio;
|
|
267
269
|
}
|
|
268
|
-
return estimateMessagesChars(messages) > fallbackMaxChars;
|
|
270
|
+
return fallbackMaxChars !== undefined && estimateMessagesChars(messages) > fallbackMaxChars;
|
|
269
271
|
}
|
|
270
272
|
function manualCompactWithSummary(messages, options) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
return { messages: [
|
|
276
|
-
const summary =
|
|
277
|
-
return buildCompactionResult(messages,
|
|
273
|
+
return checkpointCompactWithSummary(messages, options, "manualcompact");
|
|
274
|
+
}
|
|
275
|
+
function checkpointCompactWithSummary(messages, options, reason) {
|
|
276
|
+
if (messages.length === 0)
|
|
277
|
+
return { messages: [], changed: false, reason: "none" };
|
|
278
|
+
const summary = buildCheckpointFallbackSummary(messages, options.summaryMaxChars ?? 6000);
|
|
279
|
+
return buildCompactionResult(messages, summary || "No conversation content was available to summarize.", reason, false, options);
|
|
278
280
|
}
|
|
279
|
-
function reactiveCompactWithSummary(
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
const older = messages.slice(0, splitIndex);
|
|
283
|
-
const summary = buildHistorySummary(older, options.summaryMaxChars ?? 6000);
|
|
284
|
-
return buildCompactionResult(messages, recent, `${heading}\n\n${summary || "No older messages were available to summarize."}`, "reactive_compact", false);
|
|
281
|
+
function reactiveCompactWithSummary(sourceMessages, messagesForSelection, heading, options) {
|
|
282
|
+
const summary = buildCheckpointFallbackSummary(sourceMessages, options.summaryMaxChars ?? 6000);
|
|
283
|
+
return buildCompactionResult(sourceMessages, `${heading}\n\n${summary || "No conversation content was available to summarize."}`, "reactive_compact", false, options, messagesForSelection);
|
|
285
284
|
}
|
|
286
285
|
function pureCompactWithSanitizedSummary(messages, options) {
|
|
287
286
|
if (messages.length === 0)
|
|
@@ -299,68 +298,111 @@ function pureCompactWithSanitizedSummary(messages, options) {
|
|
|
299
298
|
tokensFreed: freed,
|
|
300
299
|
};
|
|
301
300
|
}
|
|
302
|
-
function
|
|
303
|
-
const facts = [];
|
|
304
|
-
for (const message of messages) {
|
|
305
|
-
if (message.metadata?.compactBoundary) {
|
|
306
|
-
const text = extractText(message);
|
|
307
|
-
const existingFacts = text.match(/(?<=Persistent facts:\n)([\s\S]*?)(?=\n(?:Working context:|<\/compact_state>))/);
|
|
308
|
-
if (existingFacts) {
|
|
309
|
-
for (const line of existingFacts[1].split("\n")) {
|
|
310
|
-
const trimmed = line.trim();
|
|
311
|
-
if (trimmed && trimmed !== "- ...summary truncated...")
|
|
312
|
-
facts.push(trimmed);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
for (const block of message.blocks) {
|
|
318
|
-
if (block.type === "text") {
|
|
319
|
-
const text = block.text;
|
|
320
|
-
if (message.role === "user") {
|
|
321
|
-
if (/\b(must|require|constraint|never|always|important|rule)\b/i.test(text)) {
|
|
322
|
-
facts.push(`- User constraint: ${text.replace(/\s+/g, " ").trim().slice(0, 200)}`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
if (/\b(error|fail|exception|bug)\b/i.test(text) && message.role !== "progress") {
|
|
326
|
-
const errorLine = text.split("\n").find((l) => /error|fail|exception|bug/i.test(l));
|
|
327
|
-
if (errorLine)
|
|
328
|
-
facts.push(`- Error encountered: ${errorLine.trim().slice(0, 200)}`);
|
|
329
|
-
}
|
|
330
|
-
if (/\b(decide|chosen|approach|architecture|design|agreed)\b/i.test(text) && message.role === "assistant") {
|
|
331
|
-
facts.push(`- Decision: ${text.replace(/\s+/g, " ").trim().slice(0, 200)}`);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if (block.type === "tool_result" && !block.ok) {
|
|
335
|
-
facts.push(`- Failed tool: ${block.name} (${serializeToolOutput(block.output).slice(0, 150)})`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return dedupeLines(facts).slice(0, 30);
|
|
340
|
-
}
|
|
341
|
-
function buildCompactionResult(originalMessages, recentMessages, summary, reason, modelDriven) {
|
|
301
|
+
function buildCompactionResult(originalMessages, summary, reason, modelDriven, options, messagesForSelection = originalMessages) {
|
|
342
302
|
const label = reason === "autocompact"
|
|
343
303
|
? "Auto compacted earlier conversation."
|
|
344
304
|
: reason === "manualcompact"
|
|
345
305
|
? "Manually compacted earlier conversation."
|
|
346
306
|
: "Reactive compacted earlier conversation.";
|
|
347
|
-
const persistentFacts = extractPersistentFacts(originalMessages);
|
|
348
|
-
const layeredSummary = persistentFacts.length > 0
|
|
349
|
-
? `Persistent facts:\n${persistentFacts.join("\n")}\n\nWorking context:\n${summary}`
|
|
350
|
-
: summary;
|
|
351
307
|
const imageRegistry = buildMergedImageRegistry(originalMessages);
|
|
352
|
-
const
|
|
353
|
-
const
|
|
308
|
+
const preservedUsers = selectRecentUserMessages(messagesForSelection, options.keepRecentTokenBudget ?? COMPACT_USER_MESSAGE_MAX_TOKENS);
|
|
309
|
+
const boundary = createCompactionBoundaryMessage(`${label}\n\n${summary}`, reason, modelDriven, imageRegistry);
|
|
310
|
+
const compacted = [...preservedUsers, boundary];
|
|
354
311
|
const freed = Math.max(0, estimateMessagesChars(originalMessages) - estimateMessagesChars(compacted));
|
|
355
312
|
return {
|
|
356
313
|
messages: compacted,
|
|
357
|
-
summary
|
|
314
|
+
summary,
|
|
358
315
|
changed: true,
|
|
359
316
|
reason,
|
|
360
317
|
charsFreed: freed,
|
|
361
318
|
tokensFreed: freed,
|
|
362
319
|
};
|
|
363
320
|
}
|
|
321
|
+
function selectRecentUserMessages(messages, tokenBudget) {
|
|
322
|
+
if (tokenBudget <= 0)
|
|
323
|
+
return [];
|
|
324
|
+
const users = messages.filter(isRealUserMessage);
|
|
325
|
+
const selected = [];
|
|
326
|
+
let remaining = tokenBudget;
|
|
327
|
+
for (let index = users.length - 1; index >= 0 && remaining > 0; index -= 1) {
|
|
328
|
+
const message = users[index];
|
|
329
|
+
const tokens = estimateMessageTokens(message);
|
|
330
|
+
if (tokens <= remaining) {
|
|
331
|
+
selected.push(markPreservedUserMessage(message));
|
|
332
|
+
remaining -= tokens;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const truncated = truncateUserMessageToTokenBudget(message, remaining);
|
|
336
|
+
if (truncated)
|
|
337
|
+
selected.push(markPreservedUserMessage(truncated));
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
return selected.reverse();
|
|
341
|
+
}
|
|
342
|
+
function isRealUserMessage(message) {
|
|
343
|
+
return message.role === "user" && message.isMeta !== true && message.metadata?.compactBoundary !== true;
|
|
344
|
+
}
|
|
345
|
+
function estimateMessageTokens(message) {
|
|
346
|
+
return estimateTextTokens(serializeMessage(message));
|
|
347
|
+
}
|
|
348
|
+
function markPreservedUserMessage(message) {
|
|
349
|
+
return {
|
|
350
|
+
...message,
|
|
351
|
+
blocks: message.blocks.map((block) => ({ ...block })),
|
|
352
|
+
metadata: { ...message.metadata, compactPreservedUser: true },
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function truncateUserMessageToTokenBudget(message, maxTokens) {
|
|
356
|
+
if (maxTokens <= 0)
|
|
357
|
+
return undefined;
|
|
358
|
+
const marker = "[... user message truncated for compaction token budget ...]";
|
|
359
|
+
const textLength = message.blocks.reduce((total, block) => total + (block.type === "text" ? block.text.length : 0), 0);
|
|
360
|
+
if (textLength === 0)
|
|
361
|
+
return undefined;
|
|
362
|
+
let low = 0;
|
|
363
|
+
let high = textLength;
|
|
364
|
+
let best;
|
|
365
|
+
while (low <= high) {
|
|
366
|
+
const mid = Math.floor((low + high) / 2);
|
|
367
|
+
const blocks = truncateUserBlocks(message.blocks, mid, marker);
|
|
368
|
+
if (estimateTextTokens(blocks.map(serializeBlock).join("\n")) <= maxTokens) {
|
|
369
|
+
best = blocks;
|
|
370
|
+
low = mid + 1;
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
high = mid - 1;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return best ? { ...message, blocks: best } : undefined;
|
|
377
|
+
}
|
|
378
|
+
function truncateUserBlocks(blocks, textChars, marker) {
|
|
379
|
+
const result = [];
|
|
380
|
+
let remaining = textChars;
|
|
381
|
+
let markerInserted = false;
|
|
382
|
+
for (const block of blocks) {
|
|
383
|
+
if (block.type !== "text") {
|
|
384
|
+
result.push({ ...block });
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (remaining <= 0)
|
|
388
|
+
continue;
|
|
389
|
+
const kept = block.text.slice(0, remaining);
|
|
390
|
+
remaining -= kept.length;
|
|
391
|
+
if (kept)
|
|
392
|
+
result.push({ type: "text", text: kept });
|
|
393
|
+
}
|
|
394
|
+
for (let index = result.length - 1; index >= 0; index -= 1) {
|
|
395
|
+
const block = result[index];
|
|
396
|
+
if (block.type !== "text")
|
|
397
|
+
continue;
|
|
398
|
+
result[index] = { type: "text", text: `${block.text.trimEnd()}\n${marker}` };
|
|
399
|
+
markerInserted = true;
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
if (!markerInserted)
|
|
403
|
+
result.unshift({ type: "text", text: marker });
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
364
406
|
function buildMergedImageRegistry(messages) {
|
|
365
407
|
const previousRegistry = extractRegistryFromBoundary(messages);
|
|
366
408
|
const currentRegistry = buildImageRegistry(messages);
|
|
@@ -405,6 +447,10 @@ function renderInternalContinuationState(summary, reason) {
|
|
|
405
447
|
"</compact_state>",
|
|
406
448
|
].join("\n");
|
|
407
449
|
}
|
|
450
|
+
function extractCompactState(text) {
|
|
451
|
+
const match = /<compact_state>\s*([\s\S]*?)\s*<\/compact_state>/i.exec(text);
|
|
452
|
+
return match?.[1]?.trim() ?? "";
|
|
453
|
+
}
|
|
408
454
|
function normalizeSummaryForInternalState(summary) {
|
|
409
455
|
return summary
|
|
410
456
|
.split(/\r?\n/)
|
|
@@ -412,6 +458,141 @@ function normalizeSummaryForInternalState(summary) {
|
|
|
412
458
|
.join("\n")
|
|
413
459
|
.trim();
|
|
414
460
|
}
|
|
461
|
+
function buildCheckpointFallbackSummary(messages, maxChars) {
|
|
462
|
+
const previousState = findLatestCheckpointState(messages);
|
|
463
|
+
const runtimeContext = messages
|
|
464
|
+
.filter((message) => message.isMeta === true && (message.metadata?.runtimeContext === true || message.metadata?.userContext === true || message.metadata?.systemContext === true))
|
|
465
|
+
.map(extractText)
|
|
466
|
+
.filter(Boolean)
|
|
467
|
+
.at(-1);
|
|
468
|
+
const userContext = messages
|
|
469
|
+
.filter(isRealUserMessage)
|
|
470
|
+
.map((message) => summarizeMessageText(message, 700))
|
|
471
|
+
.filter(Boolean)
|
|
472
|
+
.slice(-12);
|
|
473
|
+
const assistantProgress = messages
|
|
474
|
+
.filter((message) => message.role === "assistant" && message.isApiErrorMessage !== true)
|
|
475
|
+
.map((message) => summarizeMessageText(message, 700))
|
|
476
|
+
.filter(Boolean)
|
|
477
|
+
.slice(-10);
|
|
478
|
+
const references = collectContinuationReferences(messages).slice(0, 24);
|
|
479
|
+
const toolActivity = summarizeFallbackToolActivity(messages);
|
|
480
|
+
const sections = [
|
|
481
|
+
formatFallbackSection("User goals, constraints, and requests", userContext),
|
|
482
|
+
formatFallbackSection("Assistant progress and decisions", assistantProgress),
|
|
483
|
+
previousState ? `Previous checkpoint state:\n${truncateSummaryText(previousState, 2400)}` : "",
|
|
484
|
+
runtimeContext ? `Runtime context:\n${truncateSummaryText(runtimeContext, 900)}` : "",
|
|
485
|
+
references.length > 0 ? `Critical references:\n${references.map((reference) => `- ${reference}`).join("\n")}` : "",
|
|
486
|
+
toolActivity ? `Tool activity relevant to continuation:\n${toolActivity}` : "",
|
|
487
|
+
].filter(Boolean);
|
|
488
|
+
const nextStep = "Next step: continue from the latest unresolved user request using the checkpoint state above; verify the current workspace before making further changes.";
|
|
489
|
+
return fitSummarySectionsWithFooter(sections, nextStep, maxChars);
|
|
490
|
+
}
|
|
491
|
+
function findLatestCheckpointState(messages) {
|
|
492
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
493
|
+
const message = messages[index];
|
|
494
|
+
if (message.metadata?.compactBoundary !== true)
|
|
495
|
+
continue;
|
|
496
|
+
return extractCompactState(extractText(message)) || extractText(message);
|
|
497
|
+
}
|
|
498
|
+
return "";
|
|
499
|
+
}
|
|
500
|
+
function summarizeMessageText(message, maxChars) {
|
|
501
|
+
const parts = message.blocks.flatMap((block) => {
|
|
502
|
+
if (block.type === "text")
|
|
503
|
+
return [block.text];
|
|
504
|
+
if (block.type === "image")
|
|
505
|
+
return [`[image: ${block.label ?? "unlabeled"}, ${block.mimeType}]`];
|
|
506
|
+
return [];
|
|
507
|
+
});
|
|
508
|
+
return truncateSummaryText(parts.join("\n").replace(/\s+/g, " ").trim(), maxChars);
|
|
509
|
+
}
|
|
510
|
+
function collectContinuationReferences(messages) {
|
|
511
|
+
const references = [];
|
|
512
|
+
const patterns = [
|
|
513
|
+
/[A-Za-z]:[\\/](?:[^\s"'`<>|]+[\\/]?)+/g,
|
|
514
|
+
/(?:^|\s)(\/(?:[^\s"'`<>]+\/?)+)/g,
|
|
515
|
+
/https?:\/\/[^\s"'`<>]+/g,
|
|
516
|
+
/\b[0-9a-f]{7,40}\b/gi,
|
|
517
|
+
/\b(?:task|session|request|response|artifact|turn|thread)[-_ ]?id\s*[:=]\s*[^\s,;]+/gi,
|
|
518
|
+
];
|
|
519
|
+
for (const message of messages) {
|
|
520
|
+
for (const block of message.blocks) {
|
|
521
|
+
const text = block.type === "text"
|
|
522
|
+
? block.text
|
|
523
|
+
: block.type === "tool_use"
|
|
524
|
+
? serializeToolOutput(block.input)
|
|
525
|
+
: block.type === "tool_result"
|
|
526
|
+
? serializeToolOutput(block.output)
|
|
527
|
+
: "";
|
|
528
|
+
for (const pattern of patterns) {
|
|
529
|
+
pattern.lastIndex = 0;
|
|
530
|
+
for (const match of text.matchAll(pattern)) {
|
|
531
|
+
const value = (match[1] ?? match[0]).trim().replace(/[),.;:]+$/, "");
|
|
532
|
+
if (value)
|
|
533
|
+
references.push(value);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return dedupeLines(references).slice(-24);
|
|
539
|
+
}
|
|
540
|
+
function summarizeFallbackToolActivity(messages) {
|
|
541
|
+
const successful = [];
|
|
542
|
+
const failedByName = new Map();
|
|
543
|
+
for (const message of messages) {
|
|
544
|
+
for (const block of message.blocks) {
|
|
545
|
+
if (block.type !== "tool_result")
|
|
546
|
+
continue;
|
|
547
|
+
if (block.ok) {
|
|
548
|
+
const output = serializeToolOutput(block.output).replace(/\s+/g, " ").trim();
|
|
549
|
+
if (output && output !== CLEARED_TOOL_RESULT_CONTENT) {
|
|
550
|
+
successful.push(`- ${block.name}: ${truncateSummaryText(output, 320)}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
failedByName.set(block.name, (failedByName.get(block.name) ?? 0) + 1);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const lines = successful.slice(-6);
|
|
559
|
+
if (failedByName.size > 0) {
|
|
560
|
+
const counts = [...failedByName.entries()].map(([name, count]) => `${name} ×${count}`).join(", ");
|
|
561
|
+
lines.push(`- Some tool attempts failed (${counts}); individual transient errors were omitted. Re-check only if still relevant.`);
|
|
562
|
+
}
|
|
563
|
+
return lines.join("\n");
|
|
564
|
+
}
|
|
565
|
+
function formatFallbackSection(title, items) {
|
|
566
|
+
if (items.length === 0)
|
|
567
|
+
return "";
|
|
568
|
+
return `${title}:\n${items.map((item) => `- ${item}`).join("\n")}`;
|
|
569
|
+
}
|
|
570
|
+
function truncateSummaryText(text, maxChars) {
|
|
571
|
+
if (text.length <= maxChars)
|
|
572
|
+
return text;
|
|
573
|
+
if (maxChars <= 1)
|
|
574
|
+
return "…".slice(0, maxChars);
|
|
575
|
+
return `${text.slice(0, maxChars - 1).trimEnd()}…`;
|
|
576
|
+
}
|
|
577
|
+
function fitSummarySectionsWithFooter(sections, footer, maxChars) {
|
|
578
|
+
if (maxChars <= 0)
|
|
579
|
+
return "";
|
|
580
|
+
if (maxChars <= footer.length)
|
|
581
|
+
return truncateSummaryText(footer, maxChars);
|
|
582
|
+
const footerWithSeparator = `\n\n${footer}`;
|
|
583
|
+
const bodyBudget = maxChars - footerWithSeparator.length;
|
|
584
|
+
let body = "";
|
|
585
|
+
for (const section of sections) {
|
|
586
|
+
const separator = body ? "\n\n" : "";
|
|
587
|
+
const remaining = bodyBudget - body.length - separator.length;
|
|
588
|
+
if (remaining <= 0)
|
|
589
|
+
break;
|
|
590
|
+
body += separator + truncateSummaryText(section, remaining);
|
|
591
|
+
if (section.length > remaining)
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
return body ? `${body}${footerWithSeparator}` : footer;
|
|
595
|
+
}
|
|
415
596
|
function scoreMessageImportance(message) {
|
|
416
597
|
let score = 1;
|
|
417
598
|
if (message.metadata?.compactBoundary)
|
|
@@ -505,7 +686,7 @@ function buildSmartMessageSummary(message) {
|
|
|
505
686
|
else if (block.type === "image") {
|
|
506
687
|
const label = block.label ? `"${block.label}"` : "unlabeled";
|
|
507
688
|
const stored = block.storage?.path ? ", stored payload available via image registry" : "";
|
|
508
|
-
parts.push(`[image omitted from text compaction: ${label}, ${block.mimeType}${stored}; use
|
|
689
|
+
parts.push(`[image omitted from text compaction: ${label}, ${block.mimeType}${stored}; use image_inspect by id/label for visual inspection]`);
|
|
509
690
|
}
|
|
510
691
|
else if (block.type === "tool_result") {
|
|
511
692
|
parts.push(extractStructuredToolResult(block));
|
|
@@ -618,38 +799,58 @@ function summarizePathForPureState(value) {
|
|
|
618
799
|
const tail = parts.slice(Math.max(0, parts.length - 3)).join("/");
|
|
619
800
|
return tail ? `[path:${tail}]` : "[path]";
|
|
620
801
|
}
|
|
621
|
-
function
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
802
|
+
function prepareMessagesForModelSummary(messages, instructions) {
|
|
803
|
+
const history = withoutThinkingBlocks(messages)
|
|
804
|
+
.filter((message) => message.role !== "progress" && message.role !== "tombstone")
|
|
805
|
+
.map(normalizeMessageForModelSummary);
|
|
806
|
+
const compactInstruction = {
|
|
807
|
+
...createTextMessage("user", instructions),
|
|
808
|
+
isMeta: true,
|
|
809
|
+
metadata: { compactInstruction: true },
|
|
810
|
+
};
|
|
811
|
+
return [...history, compactInstruction];
|
|
812
|
+
}
|
|
813
|
+
function normalizeMessageForModelSummary(message) {
|
|
814
|
+
let changed = false;
|
|
815
|
+
const blocks = message.blocks.map((block) => {
|
|
816
|
+
if (block.type === "image") {
|
|
817
|
+
changed = true;
|
|
818
|
+
const storage = block.storage?.path ? `; stored payload=${block.storage.path}` : "";
|
|
819
|
+
return {
|
|
820
|
+
type: "text",
|
|
821
|
+
text: `[Historical image: ${block.label ?? "unlabeled"}, ${block.mimeType}${storage}. Pixels are omitted from compaction input; preserve the image reference if it may still matter.]`,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (block.type === "tool_result") {
|
|
825
|
+
const output = serializeToolOutput(block.output);
|
|
826
|
+
const normalizedOutput = summarizeToolResultForModelSummary(output);
|
|
827
|
+
if (normalizedOutput !== output) {
|
|
828
|
+
changed = true;
|
|
829
|
+
return { ...block, output: normalizedOutput };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return { ...block };
|
|
632
833
|
});
|
|
633
|
-
|
|
634
|
-
|
|
834
|
+
return changed
|
|
835
|
+
? { ...message, blocks, metadata: { ...message.metadata, compactInputNormalized: true } }
|
|
836
|
+
: { ...message, blocks };
|
|
837
|
+
}
|
|
838
|
+
function summarizeToolResultForModelSummary(output) {
|
|
839
|
+
const maxChars = 12_000;
|
|
840
|
+
if (output.length <= maxChars)
|
|
841
|
+
return output;
|
|
842
|
+
const edgeChars = Math.floor((maxChars - 160) / 2);
|
|
843
|
+
return [
|
|
844
|
+
`[Tool result normalized for compaction: ${output.length} chars total]`,
|
|
845
|
+
"Head:",
|
|
846
|
+
output.slice(0, edgeChars),
|
|
847
|
+
"Tail:",
|
|
848
|
+
output.slice(-edgeChars),
|
|
849
|
+
].join("\n");
|
|
635
850
|
}
|
|
636
851
|
function serializeMessage(message) {
|
|
637
852
|
return message.blocks.map(serializeBlock).join("\n");
|
|
638
853
|
}
|
|
639
|
-
function serializeMessageForSummary(message) {
|
|
640
|
-
if (message.metadata?.compactBoundary === true)
|
|
641
|
-
return extractText(message) || "[compact boundary]";
|
|
642
|
-
return message.blocks
|
|
643
|
-
.map((block) => {
|
|
644
|
-
if (block.type !== "tool_result")
|
|
645
|
-
return serializeBlock(block);
|
|
646
|
-
const output = serializeToolOutput(block.output);
|
|
647
|
-
if (output === CLEARED_TOOL_RESULT_CONTENT)
|
|
648
|
-
return `tool_result ${block.name}: ${CLEARED_TOOL_RESULT_CONTENT}`;
|
|
649
|
-
return `tool_result ${block.name}: ${summarizeToolOutput(output)}`;
|
|
650
|
-
})
|
|
651
|
-
.join("\n");
|
|
652
|
-
}
|
|
653
854
|
function serializeBlock(block) {
|
|
654
855
|
if (block.type === "text")
|
|
655
856
|
return block.text;
|
|
@@ -670,11 +871,6 @@ function serializeBlock(block) {
|
|
|
670
871
|
function serializeToolOutput(output) {
|
|
671
872
|
return typeof output === "string" ? output : JSON.stringify(output);
|
|
672
873
|
}
|
|
673
|
-
function summarizeToolOutput(output) {
|
|
674
|
-
if (output.length <= 800)
|
|
675
|
-
return output;
|
|
676
|
-
return `[large tool result: ${output.length} chars, head: ${output.slice(0, 400)}]`;
|
|
677
|
-
}
|
|
678
874
|
function collectToolResultIds(messages) {
|
|
679
875
|
const ids = [];
|
|
680
876
|
for (const message of messages) {
|
|
@@ -691,21 +887,16 @@ function extractText(message) {
|
|
|
691
887
|
.map((block) => block.text)
|
|
692
888
|
.join("\n");
|
|
693
889
|
}
|
|
694
|
-
function
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
"
|
|
701
|
-
"
|
|
702
|
-
"
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
"Summarize the earlier agent conversation for continuation after context compaction.",
|
|
706
|
-
"Preserve: user goals and constraints, decisions made, files or commands mentioned, completed work, pending work, task ids, important tool results, and any errors or blockers.",
|
|
707
|
-
"Drop: repetitive logs, transient progress chatter, and irrelevant wording.",
|
|
708
|
-
"Return concise plain text labels, not Markdown headings. Use labels like Goal:, Constraints:, Work completed:, Important facts:, Pending work:, Open risks:.",
|
|
709
|
-
"Do not include final-answer prose; this summary is an internal continuation state only.",
|
|
890
|
+
function defaultRecentTokenBudget(contextWindowTokens) {
|
|
891
|
+
if (!contextWindowTokens)
|
|
892
|
+
return undefined;
|
|
893
|
+
return Math.max(4_000, Math.min(20_000, Math.floor(contextWindowTokens * 0.15)));
|
|
894
|
+
}
|
|
895
|
+
const CHECKPOINT_COMPACT_INSTRUCTIONS = [
|
|
896
|
+
"You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task without access to the earlier transcript.",
|
|
897
|
+
"Include the current goal, progress, and key decisions; important context, constraints, and user preferences; completed work and validation status; what remains to be done with clear next steps; and any critical files, paths, identifiers, commands, data, examples, or references needed to continue.",
|
|
898
|
+
"Use task continuity and current relevance to decide what to preserve. Report tool activity through durable findings and current consequences, not as a chronological call log. Include errors only when they remain relevant as blockers, risks, unresolved failures, or important lessons.",
|
|
899
|
+
"Treat any earlier compaction summary as prior handoff state. Merge it with later developments, preserve still-valid constraints, and replace stale or superseded status with the newest known state instead of repeating both.",
|
|
900
|
+
"Be concise, structured, and specific enough for the next LLM to continue immediately. Use the conversation's primary language when practical. Do not invent facts, do not continue the task, and do not write a user-facing final answer.",
|
|
710
901
|
].join("\n");
|
|
711
902
|
//# sourceMappingURL=compaction.js.map
|