neoctl 0.2.45 → 0.2.47
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/agent-tool.js +2 -1
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/context/compaction.d.ts +6 -0
- package/dist/context/compaction.js +10 -5
- package/dist/context/compaction.js.map +1 -1
- package/dist/context/prompts.js +2 -0
- package/dist/context/prompts.js.map +1 -1
- package/dist/core/abortable-stream.d.ts +4 -0
- package/dist/core/abortable-stream.js +52 -0
- package/dist/core/abortable-stream.js.map +1 -0
- package/dist/core/compaction-progress.d.ts +5 -0
- package/dist/core/compaction-progress.js +38 -0
- package/dist/core/compaction-progress.js.map +1 -0
- package/dist/core/execution-state.d.ts +12 -0
- package/dist/core/execution-state.js +19 -0
- package/dist/core/execution-state.js.map +1 -0
- package/dist/core/image-registry.d.ts +4 -2
- package/dist/core/image-registry.js +32 -7
- package/dist/core/image-registry.js.map +1 -1
- package/dist/core/image-storage.js +4 -0
- package/dist/core/image-storage.js.map +1 -1
- package/dist/core/query-engine.d.ts +31 -1
- package/dist/core/query-engine.js +198 -70
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +5 -1
- package/dist/core/query.js +77 -22
- package/dist/core/query.js.map +1 -1
- package/dist/core/session-images.d.ts +52 -0
- package/dist/core/session-images.js +395 -0
- package/dist/core/session-images.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/model/model-gateway.d.ts +4 -0
- package/dist/model/model-gateway.js.map +1 -1
- package/dist/model/openai-chat-mapper.js +2 -0
- package/dist/model/openai-chat-mapper.js.map +1 -1
- package/dist/model/openai-mappers.js +0 -2
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/model/openai-responses-mapper.js +3 -0
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/tool-input-stream.d.ts +31 -0
- package/dist/model/tool-input-stream.js +226 -0
- package/dist/model/tool-input-stream.js.map +1 -0
- package/dist/repl/index.js +2 -0
- package/dist/repl/index.js.map +1 -1
- package/dist/session/session-store.d.ts +9 -0
- package/dist/session/session-store.js +20 -2
- package/dist/session/session-store.js.map +1 -1
- package/dist/tasks/subagent-tools.js +1 -1
- package/dist/tasks/subagent-tools.js.map +1 -1
- package/dist/tools/builtins/exec-process-manager.d.ts +4 -1
- package/dist/tools/builtins/exec-process-manager.js +26 -2
- package/dist/tools/builtins/exec-process-manager.js.map +1 -1
- package/dist/tools/builtins/exec-tool.js +1 -1
- package/dist/tools/builtins/exec-tool.js.map +1 -1
- package/dist/tools/builtins/framework-feedback-tool.d.ts +26 -0
- package/dist/tools/builtins/framework-feedback-tool.js +97 -0
- package/dist/tools/builtins/framework-feedback-tool.js.map +1 -0
- package/dist/tools/builtins/image-generation-tool.d.ts +2 -0
- package/dist/tools/builtins/image-generation-tool.js +31 -141
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/builtins/image-loader-tool.d.ts +6 -1
- package/dist/tools/builtins/image-loader-tool.js +48 -26
- package/dist/tools/builtins/image-loader-tool.js.map +1 -1
- package/dist/tools/builtins/image-note-tool.js +2 -2
- package/dist/tools/builtins/image-note-tool.js.map +1 -1
- package/dist/tools/run-tool-use.js +4 -2
- package/dist/tools/run-tool-use.js.map +1 -1
- package/dist/tools/terminal-output-store.d.ts +1 -0
- package/dist/tools/terminal-output-store.js +13 -1
- package/dist/tools/terminal-output-store.js.map +1 -1
- package/dist/tools/tool-catalog.d.ts +1 -0
- package/dist/tools/tool-catalog.js +2 -0
- package/dist/tools/tool-catalog.js.map +1 -1
- package/dist/tools/tool.d.ts +1 -0
- package/dist/tools/tool.js.map +1 -1
- package/dist/types/events.d.ts +11 -0
- package/dist/types/messages.d.ts +3 -1
- package/dist/types/messages.js.map +1 -1
- package/dist/web/demo-mode.d.ts +37 -0
- package/dist/web/demo-mode.js +141 -0
- package/dist/web/demo-mode.js.map +1 -0
- package/dist/web/demo-tools.d.ts +37 -0
- package/dist/web/demo-tools.js +259 -0
- package/dist/web/demo-tools.js.map +1 -0
- package/dist/web/demo-video.d.ts +1 -0
- package/dist/web/demo-video.js +3 -0
- package/dist/web/demo-video.js.map +1 -0
- package/dist/web/image-result-metadata.d.ts +6 -0
- package/dist/web/image-result-metadata.js +3 -0
- package/dist/web/image-result-metadata.js.map +1 -1
- package/dist/web/index.d.ts +57 -3
- package/dist/web/index.js +388 -24
- package/dist/web/index.js.map +1 -1
- package/dist/web/tool-activity.d.ts +9 -0
- package/dist/web/tool-activity.js +19 -0
- package/dist/web/tool-activity.js.map +1 -0
- package/dist/web/tool-observation-result.d.ts +1 -0
- package/dist/web/tool-observation-result.js +35 -0
- package/dist/web/tool-observation-result.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
3
|
+
import { executionRecoveryHint, isResumableExecution } from "./execution-state.js";
|
|
2
4
|
import { InMemoryAppState } from "../app/app-state.js";
|
|
3
5
|
import { InMemoryAppPromptStore } from "../app/app-prompt.js";
|
|
4
6
|
import { AppPromptContextManager, DefaultContextManager, readProjectMemoryDocuments } from "../context/context-manager.js";
|
|
@@ -8,7 +10,8 @@ import { ToolRegistry } from "../tools/registry.js";
|
|
|
8
10
|
import { createSystemInitMessage, createTextMessage } from "../types/messages.js";
|
|
9
11
|
import { buildContextMetrics } from "./context-metrics.js";
|
|
10
12
|
import { applyRuntimeContextForPromptCache } from "./message-pipeline.js";
|
|
11
|
-
import {
|
|
13
|
+
import { abortableWork } from "./abortable-stream.js";
|
|
14
|
+
import { SessionImageStore } from "./session-images.js";
|
|
12
15
|
import { query } from "./query.js";
|
|
13
16
|
import { runAgent } from "./run-agent.js";
|
|
14
17
|
import { GENERAL_PURPOSE_AGENT } from "../agents/agent-definition.js";
|
|
@@ -19,13 +22,58 @@ import { applySessionPrompt, sessionPromptMode, createSessionPromptSnapshot, par
|
|
|
19
22
|
const DEFAULT_SESSION_TITLE_DELAY_MS = 5000;
|
|
20
23
|
export class QueryEngine {
|
|
21
24
|
options;
|
|
25
|
+
simulation;
|
|
26
|
+
displayOnlyMessages = [];
|
|
27
|
+
/** Instance-local execution boundary; never inherited by forkForSession or written to provider settings. */
|
|
28
|
+
setSimulation(simulation) {
|
|
29
|
+
if (this.running)
|
|
30
|
+
throw new Error("cannot replace execution while a query is running");
|
|
31
|
+
this.cancelPendingTitleWork();
|
|
32
|
+
this.simulation = simulation;
|
|
33
|
+
}
|
|
34
|
+
/** Persist a visible message without submitting it to a model or changing an in-flight context. */
|
|
35
|
+
async recordDisplayMessage(message) {
|
|
36
|
+
await this.initialize();
|
|
37
|
+
const store = this.sessionStore;
|
|
38
|
+
const windowId = store?.snapshot().firstWindowId;
|
|
39
|
+
const stored = await this.imageStore.persistMessage({ ...message, metadata: { ...message.metadata, displayOnly: true } });
|
|
40
|
+
if (store !== this.sessionStore || windowId !== store?.snapshot().firstWindowId)
|
|
41
|
+
throw new Error("Session changed while recording message");
|
|
42
|
+
if (store)
|
|
43
|
+
store.recordMessage(stored);
|
|
44
|
+
else
|
|
45
|
+
this.displayOnlyMessages.push(stored);
|
|
46
|
+
return stored;
|
|
47
|
+
}
|
|
22
48
|
agentId;
|
|
23
49
|
history = [];
|
|
24
50
|
titleTimers = new Set();
|
|
25
51
|
lastTerminalReason;
|
|
52
|
+
executionState;
|
|
53
|
+
executionStartedAt;
|
|
54
|
+
executionNow() {
|
|
55
|
+
return this.options.timingClock?.monotonicNow() ?? performance.now();
|
|
56
|
+
}
|
|
57
|
+
getExecutionState() {
|
|
58
|
+
if (!this.executionState)
|
|
59
|
+
return undefined;
|
|
60
|
+
const elapsedMs = (this.executionState.elapsedMs ?? 0) + (this.executionStartedAt === undefined
|
|
61
|
+
? 0 : Math.max(0, this.executionNow() - this.executionStartedAt));
|
|
62
|
+
return { ...this.executionState, elapsedMs: Math.floor(elapsedMs) };
|
|
63
|
+
}
|
|
64
|
+
markExecutionPaused() {
|
|
65
|
+
this.setExecutionState({ status: "paused", reason: "user_pause" });
|
|
66
|
+
}
|
|
67
|
+
setExecutionState(state) {
|
|
68
|
+
const elapsedMs = state.elapsedMs ?? this.getExecutionState()?.elapsedMs ?? 0;
|
|
69
|
+
this.executionState = { ...state, elapsedMs, ...(this.executionState?.resumeCompaction ? { resumeCompaction: true } : {}) };
|
|
70
|
+
this.executionStartedAt = state.status === "running" ? this.executionNow() : undefined;
|
|
71
|
+
this.sessionStore?.recordExecutionState(this.executionState);
|
|
72
|
+
}
|
|
26
73
|
queryTiming;
|
|
27
74
|
timingRecords = new Map();
|
|
28
75
|
sessionStore;
|
|
76
|
+
imageStore;
|
|
29
77
|
currentModel;
|
|
30
78
|
currentReasoning;
|
|
31
79
|
currentFastMode = false;
|
|
@@ -103,6 +151,7 @@ export class QueryEngine {
|
|
|
103
151
|
if (this.options.session?.enabled !== false && this.options.session) {
|
|
104
152
|
await this.openSession({ sessionId: this.options.session.sessionId, resume: this.options.session.resume });
|
|
105
153
|
}
|
|
154
|
+
this.imageStore ??= await SessionImageStore.open(undefined, this.agentId);
|
|
106
155
|
this.sessionInitialized = true;
|
|
107
156
|
})();
|
|
108
157
|
this.sessionInitialization = initialization;
|
|
@@ -152,13 +201,35 @@ export class QueryEngine {
|
|
|
152
201
|
rootDir: this.options.session.rootDir,
|
|
153
202
|
});
|
|
154
203
|
}
|
|
155
|
-
|
|
204
|
+
sendUserText(text, options = {}) {
|
|
205
|
+
return this.runQuery(text, options);
|
|
206
|
+
}
|
|
207
|
+
/** A UI continuation is a runtime action, not an invented user utterance. */
|
|
208
|
+
continueExecution(options = {}) {
|
|
209
|
+
return this.runQuery(undefined, options);
|
|
210
|
+
}
|
|
211
|
+
async *runQuery(text, options = {}) {
|
|
156
212
|
if (this.running)
|
|
157
213
|
throw new Error("query engine is already running");
|
|
158
214
|
this.running = true;
|
|
215
|
+
let terminalReceived = false;
|
|
216
|
+
let queryAccepted = text !== undefined;
|
|
159
217
|
try {
|
|
160
218
|
await this.settingsMutation;
|
|
161
219
|
await this.initialize();
|
|
220
|
+
const recovery = isResumableExecution(this.executionState) ? this.executionState : undefined;
|
|
221
|
+
if (text === undefined && !recovery)
|
|
222
|
+
throw new Error("No interrupted execution to continue");
|
|
223
|
+
queryAccepted = true;
|
|
224
|
+
if (recovery) {
|
|
225
|
+
this.pendingRequestContext = { ...this.pendingRequestContext,
|
|
226
|
+
executionRecovery: executionRecoveryHint(recovery),
|
|
227
|
+
...(text === undefined ? { continuationAction: "用户点击了继续按钮,请继续尚未完成的任务。这是运行时动作,不是用户发送的新消息。" } : {}),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const elapsedMs = recovery?.elapsedMs ?? 0;
|
|
231
|
+
this.setExecutionState(options.abortSignal?.aborted
|
|
232
|
+
? { status: "paused", reason: "user_pause", elapsedMs } : { status: "running", elapsedMs });
|
|
162
233
|
const cwdTransitionPaths = this.pendingCwdTransitionPaths;
|
|
163
234
|
const pendingRequestContext = this.pendingRequestContext;
|
|
164
235
|
const pendingRequestContextConsumed = this.pendingRequestContextConsumed;
|
|
@@ -166,18 +237,20 @@ export class QueryEngine {
|
|
|
166
237
|
this.options.cwdTransitionPaths = undefined;
|
|
167
238
|
if (cwdTransitionPaths?.length)
|
|
168
239
|
void this.options.onCwdTransitionConsumed?.();
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
240
|
+
if (text !== undefined) {
|
|
241
|
+
const userMessage = options.blocks
|
|
242
|
+
? await this.persistMessageImages({
|
|
243
|
+
...createTextMessage("user", options.displayText ?? text),
|
|
244
|
+
blocks: options.blocks,
|
|
245
|
+
})
|
|
246
|
+
: createTextMessage("user", text);
|
|
247
|
+
this.history.push(userMessage);
|
|
248
|
+
this.sessionStore?.recordMessage(userMessage);
|
|
249
|
+
this.scheduleSessionTitleCheck();
|
|
250
|
+
}
|
|
178
251
|
const initMessage = createSystemInitMessage({
|
|
179
252
|
agentId: this.agentId,
|
|
180
|
-
tools: this.options.tools.names(),
|
|
253
|
+
tools: (this.simulation?.tools ?? this.options.tools).names(),
|
|
181
254
|
model: this.currentModel,
|
|
182
255
|
commands: [...(this.options.commands ?? [])],
|
|
183
256
|
agents: [...(this.options.agents ?? [])],
|
|
@@ -193,6 +266,7 @@ export class QueryEngine {
|
|
|
193
266
|
message: initMessage,
|
|
194
267
|
};
|
|
195
268
|
this.sessionStore?.recordMessage(initMessage);
|
|
269
|
+
const compactor = this.cancellableCompactor(options.abortSignal);
|
|
196
270
|
const queryOptions = {
|
|
197
271
|
agentId: this.agentId,
|
|
198
272
|
model: this.currentModel,
|
|
@@ -220,29 +294,33 @@ export class QueryEngine {
|
|
|
220
294
|
} : undefined,
|
|
221
295
|
abortSignal: options.abortSignal,
|
|
222
296
|
stopAfterTurn: options.stopAfterTurn,
|
|
223
|
-
beforeTurn: (messages) => {
|
|
224
|
-
this.
|
|
225
|
-
|
|
297
|
+
beforeTurn: (messages, started) => {
|
|
298
|
+
if (!this.simulation)
|
|
299
|
+
this.options.refreshTools?.(this);
|
|
300
|
+
return this.consumeSessionSettings(messages, started, compactor);
|
|
226
301
|
},
|
|
227
302
|
};
|
|
228
303
|
const stream = query(this.history, {
|
|
229
304
|
...this.options,
|
|
230
|
-
acquireTurnResources: this.options.acquireTurnResources ? () => this.options.acquireTurnResources(this) : undefined,
|
|
305
|
+
acquireTurnResources: this.simulation ? undefined : this.options.acquireTurnResources ? () => this.options.acquireTurnResources(this) : undefined,
|
|
306
|
+
tools: this.simulation?.tools ?? this.options.tools,
|
|
307
|
+
compactor,
|
|
231
308
|
onTimingState: timing => { this.queryTiming = timing; },
|
|
232
309
|
onTiming: record => {
|
|
233
310
|
this.timingRecords.set(record.id, { ...record });
|
|
234
311
|
this.sessionStore?.recordTiming(record);
|
|
235
312
|
},
|
|
236
313
|
contextManager: this.contextManager,
|
|
237
|
-
modelGateway: this.currentModelGateway,
|
|
314
|
+
modelGateway: this.simulation?.modelGateway ?? this.currentModelGateway,
|
|
238
315
|
taskNotificationSource: this.options.taskNotificationSource,
|
|
316
|
+
imageStore: this.imageStore,
|
|
239
317
|
toolResultMemory: this.sessionStore?.toolResultMemory,
|
|
240
318
|
session: this.sessionStore ? { sessionId: this.sessionStore.sessionId, sessionDir: this.sessionStore.sessionDir, rootDir: this.options.session?.rootDir } : undefined,
|
|
241
319
|
recordContentReplacements: (records) => this.sessionStore?.recordContentReplacements(records),
|
|
242
320
|
// query owns synthetic tool-use messages; persist them only via the message event below.
|
|
243
321
|
// The export callback is an observer, not a second history writer.
|
|
244
322
|
exportToolCalls: this.options.exportToolCalls,
|
|
245
|
-
applyCompaction: (result) => this.applyCompactionResult(result),
|
|
323
|
+
applyCompaction: (result) => { options.abortSignal?.throwIfAborted(); this.applyCompactionResult(result); },
|
|
246
324
|
}, queryOptions);
|
|
247
325
|
for await (const event of stream) {
|
|
248
326
|
if (event.type === "message") {
|
|
@@ -253,13 +331,17 @@ export class QueryEngine {
|
|
|
253
331
|
continue;
|
|
254
332
|
}
|
|
255
333
|
if (event.type === "terminal") {
|
|
334
|
+
terminalReceived = true;
|
|
256
335
|
this.lastTerminalReason = event.reason;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
336
|
+
if (this.executionState?.status !== "paused") {
|
|
337
|
+
this.setExecutionState({
|
|
338
|
+
status: event.reason === "completed" || event.reason === "turn_yielded" ? "completed" : "interrupted",
|
|
339
|
+
reason: event.reason,
|
|
340
|
+
});
|
|
262
341
|
}
|
|
342
|
+
// Settle ordinary settings only; pending compact waits for the next model call.
|
|
343
|
+
const hadPending = Object.keys(this.pendingSessionSettings).length > 0;
|
|
344
|
+
await this.flushSessionSettings();
|
|
263
345
|
if (hadPending)
|
|
264
346
|
yield { type: "context.metrics", metrics: await this.contextMetrics() };
|
|
265
347
|
}
|
|
@@ -267,12 +349,12 @@ export class QueryEngine {
|
|
|
267
349
|
}
|
|
268
350
|
}
|
|
269
351
|
finally {
|
|
270
|
-
//
|
|
271
|
-
// Do not yield in finally: a closing consumer may never resume the generator.
|
|
352
|
+
// Even a failed journal write must release the engine run lock.
|
|
272
353
|
try {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
354
|
+
if (queryAccepted && !terminalReceived && this.executionState?.status !== "paused") {
|
|
355
|
+
this.setExecutionState({ status: "interrupted", reason: "unexpected_termination" });
|
|
356
|
+
}
|
|
357
|
+
await this.flushSessionSettings();
|
|
276
358
|
}
|
|
277
359
|
finally {
|
|
278
360
|
this.running = false;
|
|
@@ -280,7 +362,7 @@ export class QueryEngine {
|
|
|
280
362
|
}
|
|
281
363
|
}
|
|
282
364
|
/** Queue instance-local settings; no prompt messages or provider-global mutation. */
|
|
283
|
-
async updateSessionSettings(patch) {
|
|
365
|
+
async updateSessionSettings(patch, options = {}) {
|
|
284
366
|
const normalized = {};
|
|
285
367
|
if (Object.hasOwn(patch, "model"))
|
|
286
368
|
normalized.model = patch.model?.trim() || undefined;
|
|
@@ -302,10 +384,26 @@ export class QueryEngine {
|
|
|
302
384
|
}
|
|
303
385
|
const mutation = this.settingsMutation.then(async () => {
|
|
304
386
|
await this.initialize();
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
387
|
+
if (normalized.compact)
|
|
388
|
+
delete this.pendingSessionSettings.compact;
|
|
389
|
+
const previousExecution = this.getExecutionState();
|
|
390
|
+
if (normalized.compact)
|
|
391
|
+
this.setExecutionState({ status: "running", resumeCompaction: true });
|
|
392
|
+
try {
|
|
393
|
+
const update = await this.applySessionSettings(normalized, this.getHistoryMessages(), undefined, this.cancellableCompactor(options.abortSignal));
|
|
394
|
+
options.abortSignal?.throwIfAborted();
|
|
395
|
+
if (update?.compaction)
|
|
396
|
+
this.applyCompactionResult(update.compaction);
|
|
397
|
+
if (normalized.compact)
|
|
398
|
+
this.setExecutionState({ status: previousExecution?.status ?? "completed", reason: previousExecution?.reason });
|
|
399
|
+
return { deferred: false, ...(update?.compaction ? { compaction: update.compaction } : {}) };
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
if (normalized.compact)
|
|
403
|
+
this.setExecutionState(options.abortSignal?.aborted
|
|
404
|
+
? { status: "paused", reason: "user_pause" } : { status: "interrupted", reason: "compaction_failed" });
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
309
407
|
});
|
|
310
408
|
// A rejected mutation must not poison subsequent settings or user input.
|
|
311
409
|
this.settingsMutation = mutation.catch(() => undefined);
|
|
@@ -357,14 +455,14 @@ export class QueryEngine {
|
|
|
357
455
|
patch.sessionPrompt = { ...patch.sessionPrompt };
|
|
358
456
|
return patch;
|
|
359
457
|
}
|
|
360
|
-
async consumeSessionSettings(messages) {
|
|
361
|
-
const patch = this.pendingSessionSettings;
|
|
458
|
+
async consumeSessionSettings(messages, started, compactor) {
|
|
459
|
+
const patch = { ...this.pendingSessionSettings, ...(this.executionState?.resumeCompaction ? { compact: true } : {}) };
|
|
362
460
|
if (!Object.keys(patch).length)
|
|
363
461
|
return undefined;
|
|
364
462
|
this.pendingSessionSettings = {};
|
|
365
|
-
return this.applySessionSettings(patch, messages);
|
|
463
|
+
return this.applySessionSettings(patch, messages, started, compactor);
|
|
366
464
|
}
|
|
367
|
-
async applySessionSettings(patch, messages) {
|
|
465
|
+
async applySessionSettings(patch, messages, started, activeCompactor) {
|
|
368
466
|
const changed = {};
|
|
369
467
|
if (Object.hasOwn(patch, "sessionPrompt"))
|
|
370
468
|
this.activeSessionPrompt = patch.sessionPrompt ?? null;
|
|
@@ -388,23 +486,20 @@ export class QueryEngine {
|
|
|
388
486
|
}
|
|
389
487
|
let compaction;
|
|
390
488
|
if (patch.compact) {
|
|
391
|
-
const compactor =
|
|
392
|
-
const budget = { ...this.options.contextBudget, contextWindowTokens: this.currentContextWindowTokens ?? this.options.contextBudget?.contextWindowTokens };
|
|
489
|
+
const compactor = activeCompactor ?? this.cancellableCompactor();
|
|
490
|
+
const budget = { ...this.options.contextBudget, onCompactionStart: started, imageRegistry: this.imageStore?.snapshot(), contextWindowTokens: this.currentContextWindowTokens ?? this.options.contextBudget?.contextWindowTokens };
|
|
393
491
|
compaction = withCompactionReport(await (compactor.manualCompact?.(messages, budget) ?? compactor.compact(messages, budget)), messages.length);
|
|
394
492
|
}
|
|
395
493
|
return Object.keys(changed).length || compaction ? { settings: changed, compaction } : undefined;
|
|
396
494
|
}
|
|
397
495
|
async flushSessionSettings() {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
results.push(update.compaction);
|
|
405
|
-
}
|
|
496
|
+
// Ordinary settings can settle at the terminal boundary. A busy compact request
|
|
497
|
+
// must wait for the next model call, including when the current query ends/aborts.
|
|
498
|
+
while (Object.keys(this.pendingSessionSettings).some(key => key !== "compact")) {
|
|
499
|
+
const { compact, ...patch } = this.pendingSessionSettings;
|
|
500
|
+
this.pendingSessionSettings = compact ? { compact: true } : {};
|
|
501
|
+
await this.applySessionSettings(patch, this.getHistoryMessages());
|
|
406
502
|
}
|
|
407
|
-
return results;
|
|
408
503
|
}
|
|
409
504
|
setModel(model, reasoning, updateReasoning = false) {
|
|
410
505
|
this.currentModel = model?.trim() || undefined;
|
|
@@ -464,9 +559,13 @@ export class QueryEngine {
|
|
|
464
559
|
reset() {
|
|
465
560
|
if (this.running)
|
|
466
561
|
throw new Error("cannot reset while query engine is running");
|
|
562
|
+
this.simulation = undefined;
|
|
563
|
+
this.displayOnlyMessages.length = 0;
|
|
467
564
|
this.pendingSessionSettings = {};
|
|
468
565
|
this.history.length = 0;
|
|
469
566
|
this.lastTerminalReason = undefined;
|
|
567
|
+
this.executionState = undefined;
|
|
568
|
+
this.executionStartedAt = undefined;
|
|
470
569
|
this.cancelPendingTitleWork();
|
|
471
570
|
this.sessionStore?.reset();
|
|
472
571
|
this.queryTiming = undefined;
|
|
@@ -476,16 +575,17 @@ export class QueryEngine {
|
|
|
476
575
|
async compact(options = {}) {
|
|
477
576
|
if (options.abortSignal?.aborted)
|
|
478
577
|
return { messages: this.getHistoryMessages(), changed: false, reason: "none" };
|
|
479
|
-
const result = await this.updateSessionSettings({ compact: true });
|
|
578
|
+
const result = await this.updateSessionSettings({ compact: true }, options);
|
|
480
579
|
return result.compaction ?? { messages: this.getHistoryMessages(), changed: false, reason: "none" };
|
|
481
580
|
}
|
|
482
581
|
async pureCompact(options = {}) {
|
|
483
582
|
await this.initialize();
|
|
484
583
|
if (options.abortSignal?.aborted)
|
|
485
584
|
return { messages: this.getHistoryMessages(), changed: false, reason: "none" };
|
|
486
|
-
const compactor = this.options.compactor ?? new ModelDrivenCompactor(this.currentModelGateway);
|
|
585
|
+
const compactor = this.simulation?.compactor ?? this.options.compactor ?? new ModelDrivenCompactor(this.currentModelGateway);
|
|
487
586
|
const contextBudget = {
|
|
488
587
|
...this.options.contextBudget,
|
|
588
|
+
imageRegistry: this.imageStore?.snapshot(),
|
|
489
589
|
contextWindowTokens: this.currentContextWindowTokens ?? this.options.contextBudget?.contextWindowTokens,
|
|
490
590
|
};
|
|
491
591
|
const result = withCompactionReport(await (compactor.pureCompact?.(this.history, contextBudget) ?? { messages: this.getHistoryMessages(), changed: false, reason: "none" }), this.history.length);
|
|
@@ -521,12 +621,44 @@ export class QueryEngine {
|
|
|
521
621
|
return [...records.values()].map(record => ({ ...record }));
|
|
522
622
|
}
|
|
523
623
|
getDisplayEntries() {
|
|
524
|
-
return this.sessionStore?.getDisplayEntries() ?? this.history.map((message) => ({ type: "message", message: cloneMessage(message) }));
|
|
624
|
+
return this.sessionStore?.getDisplayEntries() ?? [...this.history, ...this.displayOnlyMessages].map((message) => ({ type: "message", message: cloneMessage(message) }));
|
|
625
|
+
}
|
|
626
|
+
clearPendingCompaction() {
|
|
627
|
+
if (!this.executionState?.resumeCompaction)
|
|
628
|
+
return;
|
|
629
|
+
delete this.executionState.resumeCompaction;
|
|
630
|
+
this.setExecutionState(this.getExecutionState());
|
|
631
|
+
}
|
|
632
|
+
/** Keep the durable history untouched until the entire result can be committed. */
|
|
633
|
+
cancellableCompactor(signal) {
|
|
634
|
+
const source = this.simulation?.compactor ?? this.options.compactor ?? new ModelDrivenCompactor(this.currentModelGateway);
|
|
635
|
+
const run = (messages, budget = {}, kind = "compact", error) => {
|
|
636
|
+
const started = reason => {
|
|
637
|
+
signal?.throwIfAborted();
|
|
638
|
+
if (!this.executionState?.resumeCompaction) {
|
|
639
|
+
this.setExecutionState({ ...(this.getExecutionState() ?? { status: "running" }), resumeCompaction: true });
|
|
640
|
+
}
|
|
641
|
+
budget.onCompactionStart?.(reason);
|
|
642
|
+
};
|
|
643
|
+
return abortableWork(async () => {
|
|
644
|
+
if (kind !== "compact")
|
|
645
|
+
started(kind === "manualCompact" ? "manualcompact" : "reactive_compact");
|
|
646
|
+
const options = { ...budget, abortSignal: signal, onCompactionStart: started };
|
|
647
|
+
return kind === "reactiveCompact" && source.reactiveCompact ? source.reactiveCompact(messages, error, options)
|
|
648
|
+
: kind === "manualCompact" && source.manualCompact ? source.manualCompact(messages, options)
|
|
649
|
+
: source.compact(messages, options);
|
|
650
|
+
}, signal);
|
|
651
|
+
};
|
|
652
|
+
return { compact: (m, b) => run(m, b), manualCompact: (m, b) => run(m, b, "manualCompact"),
|
|
653
|
+
reactiveCompact: (m, e, b) => run(m, b, "reactiveCompact", e) };
|
|
525
654
|
}
|
|
526
655
|
applyCompactionResult(result) {
|
|
527
|
-
if (!result.changed)
|
|
656
|
+
if (!result.changed) {
|
|
657
|
+
this.clearPendingCompaction();
|
|
528
658
|
return;
|
|
659
|
+
}
|
|
529
660
|
this.sessionStore?.recordCompactCheckpoint(result.messages, result.reason, result.report);
|
|
661
|
+
this.clearPendingCompaction();
|
|
530
662
|
this.history.length = 0;
|
|
531
663
|
this.history.push(...result.messages.map(cloneMessage));
|
|
532
664
|
}
|
|
@@ -559,14 +691,14 @@ export class QueryEngine {
|
|
|
559
691
|
return this.buildPromptExportSnapshot(this.getHistoryMessages());
|
|
560
692
|
}
|
|
561
693
|
refreshToolsIfIdle() {
|
|
562
|
-
if (!this.running)
|
|
694
|
+
if (!this.running && !this.simulation)
|
|
563
695
|
this.options.refreshTools?.(this);
|
|
564
696
|
}
|
|
565
697
|
async buildPromptExportSnapshot(messages, desiredState = this.sessionPromptState) {
|
|
566
698
|
this.refreshToolsIfIdle();
|
|
567
699
|
const toolContext = {
|
|
568
700
|
agentId: this.agentId,
|
|
569
|
-
tools: this.options.tools,
|
|
701
|
+
tools: this.simulation?.tools ?? this.options.tools,
|
|
570
702
|
appState: new InMemoryAppState(this.agentId, this.options.cwd),
|
|
571
703
|
toolResultMemory: this.sessionStore?.toolResultMemory,
|
|
572
704
|
session: this.sessionStore ? {
|
|
@@ -585,7 +717,7 @@ export class QueryEngine {
|
|
|
585
717
|
},
|
|
586
718
|
emit: () => undefined,
|
|
587
719
|
};
|
|
588
|
-
const initialToolDefinitions = this.options.tools.definitions(toolContext);
|
|
720
|
+
const initialToolDefinitions = (this.simulation?.tools ?? this.options.tools).definitions(toolContext);
|
|
589
721
|
const activePrompt = this.activeSessionPrompt;
|
|
590
722
|
const baseline = await this.baselineContextManager.build({
|
|
591
723
|
agentId: this.agentId,
|
|
@@ -630,24 +762,15 @@ export class QueryEngine {
|
|
|
630
762
|
};
|
|
631
763
|
}
|
|
632
764
|
async persistMessageImages(message) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const persistedBlocks = await persistMessageImages(message.blocks, { sessionDir: this.sessionStore?.sessionDir, agentId: this.agentId });
|
|
636
|
-
const hasRetention = typeof message.metadata?.imageRetention === "string";
|
|
637
|
-
return {
|
|
638
|
-
...message,
|
|
639
|
-
blocks: persistedBlocks,
|
|
640
|
-
metadata: message.role === "user"
|
|
641
|
-
? { ...message.metadata, imageRetention: "pinned", imageTtlTurns: undefined }
|
|
642
|
-
: hasRetention
|
|
643
|
-
? message.metadata
|
|
644
|
-
: { ...message.metadata, imageRetention: "while_relevant", imageTtlTurns: 4 },
|
|
645
|
-
};
|
|
765
|
+
this.imageStore ??= await SessionImageStore.open(this.sessionStore?.sessionDir, this.agentId);
|
|
766
|
+
return this.imageStore.persistMessage(message);
|
|
646
767
|
}
|
|
647
768
|
async openSession(options) {
|
|
648
769
|
if (this.options.session?.enabled === false || !this.options.session)
|
|
649
770
|
return;
|
|
650
771
|
this.cancelPendingTitleWork();
|
|
772
|
+
this.simulation = undefined;
|
|
773
|
+
this.displayOnlyMessages.length = 0;
|
|
651
774
|
this.sessionStore = await SessionStore.open({
|
|
652
775
|
agentId: this.agentId,
|
|
653
776
|
cwd: this.options.cwd ?? process.cwd(),
|
|
@@ -656,6 +779,11 @@ export class QueryEngine {
|
|
|
656
779
|
resume: options.resume,
|
|
657
780
|
toolResultThresholdChars: this.options.session.toolResultThresholdChars,
|
|
658
781
|
});
|
|
782
|
+
this.imageStore = await SessionImageStore.open(this.sessionStore.sessionDir, this.agentId);
|
|
783
|
+
const execution = this.sessionStore.getExecutionState();
|
|
784
|
+
this.executionState = execution?.status === "running"
|
|
785
|
+
? { ...execution, status: "interrupted", reason: "runtime_terminated" } : execution;
|
|
786
|
+
this.executionStartedAt = undefined;
|
|
659
787
|
this.pendingSessionSettings = {};
|
|
660
788
|
this.sessionPromptState = this.sessionStore.getSessionPrompt();
|
|
661
789
|
this.activeSessionPrompt = this.sessionPromptState ?? null;
|
|
@@ -674,7 +802,7 @@ export class QueryEngine {
|
|
|
674
802
|
}
|
|
675
803
|
scheduleSessionTitleCheck() {
|
|
676
804
|
const store = this.sessionStore;
|
|
677
|
-
if (!store || titleWorkComplete(store))
|
|
805
|
+
if (this.simulation || !store || titleWorkComplete(store))
|
|
678
806
|
return;
|
|
679
807
|
const version = this.titleSchedulerVersion;
|
|
680
808
|
const timer = setTimeout(() => {
|
|
@@ -689,7 +817,7 @@ export class QueryEngine {
|
|
|
689
817
|
if (version !== this.titleSchedulerVersion)
|
|
690
818
|
return;
|
|
691
819
|
const store = this.sessionStore;
|
|
692
|
-
if (!store || titleWorkComplete(store))
|
|
820
|
+
if (this.simulation || !store || titleWorkComplete(store))
|
|
693
821
|
return;
|
|
694
822
|
if (this.titleAgentRun?.version === version)
|
|
695
823
|
return;
|