neoctl 0.2.37 → 0.2.38
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-definition.d.ts +2 -0
- package/dist/agents/agent-definition.js +10 -0
- package/dist/agents/agent-definition.js.map +1 -1
- package/dist/agents/agent-tool.js +4 -0
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/agents/coordination-boundary.test.d.ts +1 -0
- package/dist/agents/coordination-boundary.test.js +58 -0
- package/dist/agents/coordination-boundary.test.js.map +1 -0
- package/dist/context/context-manager.d.ts +4 -0
- package/dist/context/context-manager.js +4 -1
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/prompt-config.d.ts +35 -0
- package/dist/context/prompt-config.js +151 -0
- package/dist/context/prompt-config.js.map +1 -0
- package/dist/context/prompt-config.test.d.ts +1 -0
- package/dist/context/prompt-config.test.js +124 -0
- package/dist/context/prompt-config.test.js.map +1 -0
- package/dist/context/prompts.d.ts +1 -1
- package/dist/context/prompts.js +4 -27
- package/dist/context/prompts.js.map +1 -1
- package/dist/context/smoke-context.js +3 -1
- package/dist/context/smoke-context.js.map +1 -1
- package/dist/context/system.md +16 -0
- package/dist/core/query-engine.d.ts +27 -1
- package/dist/core/query-engine.js +301 -109
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query-session-settings.test.d.ts +1 -0
- package/dist/core/query-session-settings.test.js +252 -0
- package/dist/core/query-session-settings.test.js.map +1 -0
- package/dist/core/query.d.ts +16 -0
- package/dist/core/query.js +30 -0
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent.js +3 -5
- package/dist/core/run-agent.js.map +1 -1
- package/dist/core/session-settings-prompt.d.ts +35 -0
- package/dist/core/session-settings-prompt.js +61 -0
- package/dist/core/session-settings-prompt.js.map +1 -0
- package/dist/core/session-settings-prompt.test.d.ts +1 -0
- package/dist/core/session-settings-prompt.test.js +257 -0
- package/dist/core/session-settings-prompt.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/model-metadata.json +1 -3
- package/dist/model/openai-responses-instructions.test.d.ts +1 -0
- package/dist/model/openai-responses-instructions.test.js +64 -0
- package/dist/model/openai-responses-instructions.test.js.map +1 -0
- package/dist/model/openai-responses-mapper.js +5 -16
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/smoke-responses-mapper.js +8 -5
- package/dist/model/smoke-responses-mapper.js.map +1 -1
- package/dist/session/session-export.d.ts +6 -0
- package/dist/session/session-export.js.map +1 -1
- package/dist/session/session-store.d.ts +11 -0
- package/dist/session/session-store.js +25 -0
- package/dist/session/session-store.js.map +1 -1
- package/dist/tools/builtins/exec-process-manager.js +4 -1
- package/dist/tools/builtins/exec-process-manager.js.map +1 -1
- package/dist/tools/terminal-background-history.test.d.ts +1 -0
- package/dist/tools/terminal-background-history.test.js +77 -0
- package/dist/tools/terminal-background-history.test.js.map +1 -0
- package/dist/tools/terminal-output-store.d.ts +4 -0
- package/dist/tools/terminal-output-store.js +18 -0
- package/dist/tools/terminal-output-store.js.map +1 -1
- package/dist/web/index.d.ts +22 -1
- package/dist/web/index.js +122 -12
- package/dist/web/index.js.map +1 -1
- package/dist/web/prompt-config-protocol.d.ts +4 -0
- package/dist/web/prompt-config-protocol.js +57 -0
- package/dist/web/prompt-config-protocol.js.map +1 -0
- package/dist/web/prompt-config-protocol.test.d.ts +1 -0
- package/dist/web/prompt-config-protocol.test.js +78 -0
- package/dist/web/prompt-config-protocol.test.js.map +1 -0
- package/dist/web/runtime-context-protocol.d.ts +1 -0
- package/dist/web/runtime-context-protocol.js +1 -0
- package/dist/web/runtime-context-protocol.js.map +1 -1
- package/dist/web/session-prompt-integration.test.d.ts +1 -0
- package/dist/web/session-prompt-integration.test.js +57 -0
- package/dist/web/session-prompt-integration.test.js.map +1 -0
- package/dist/web/session-prompt-protocol.d.ts +8 -0
- package/dist/web/session-prompt-protocol.js +11 -0
- package/dist/web/session-prompt-protocol.js.map +1 -0
- package/dist/web/session-settings.test.d.ts +1 -0
- package/dist/web/session-settings.test.js +254 -0
- package/dist/web/session-settings.test.js.map +1 -0
- package/dist/web/smoke-terminal-output-http.js +10 -5
- package/dist/web/smoke-terminal-output-http.js.map +1 -1
- package/dist/web/smoke-web-terminal-tasks.js +100 -3
- package/dist/web/smoke-web-terminal-tasks.js.map +1 -1
- package/docs/prompt-config.md +71 -0
- package/package.json +4 -3
- package/scripts/build-standalone.mjs +3 -0
- package/scripts/copy-prompt-resources.mjs +5 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { InMemoryAppState } from "../app/app-state.js";
|
|
2
3
|
import { InMemoryAppPromptStore } from "../app/app-prompt.js";
|
|
3
4
|
import { AppPromptContextManager, DefaultContextManager, readProjectMemoryDocuments } from "../context/context-manager.js";
|
|
@@ -14,6 +15,7 @@ import { GENERAL_PURPOSE_AGENT } from "../agents/agent-definition.js";
|
|
|
14
15
|
import { SessionStore } from "../session/session-store.js";
|
|
15
16
|
import { buildPromptCacheDiagnostics } from "./prompt-cache-telemetry.js";
|
|
16
17
|
import { computeStaticTokens } from "./context-metrics.js";
|
|
18
|
+
import { applySessionPrompt, createSessionPromptSnapshot, parseSessionPromptUpdate, SessionPromptError } from "./session-settings-prompt.js";
|
|
17
19
|
const DEFAULT_SESSION_TITLE_DELAY_MS = 5000;
|
|
18
20
|
export class QueryEngine {
|
|
19
21
|
options;
|
|
@@ -25,14 +27,23 @@ export class QueryEngine {
|
|
|
25
27
|
currentModel;
|
|
26
28
|
currentReasoning;
|
|
27
29
|
currentFastMode = false;
|
|
30
|
+
running = false;
|
|
31
|
+
pendingSessionSettings = {};
|
|
32
|
+
sessionPromptState;
|
|
33
|
+
activeSessionPrompt = null;
|
|
34
|
+
ephemeralSessionKey = randomUUID();
|
|
35
|
+
// Idle mutations and the next generator share a barrier. Never compact alongside a request.
|
|
36
|
+
settingsMutation = Promise.resolve();
|
|
28
37
|
currentContextWindowTokens;
|
|
29
38
|
currentModelGateway;
|
|
30
39
|
sessionInitialized = false;
|
|
40
|
+
sessionInitialization;
|
|
31
41
|
titleSchedulerVersion = 0;
|
|
32
42
|
titleAgentRun;
|
|
33
43
|
sessionTitleListeners = new Set();
|
|
34
44
|
appPromptStore;
|
|
35
45
|
contextManager;
|
|
46
|
+
baselineContextManager;
|
|
36
47
|
additionalPromptContextManager;
|
|
37
48
|
pendingCwdTransitionPaths;
|
|
38
49
|
pendingRequestContext;
|
|
@@ -49,7 +60,12 @@ export class QueryEngine {
|
|
|
49
60
|
?? options.contextManager
|
|
50
61
|
?? new DefaultContextManager({ cwd: options.cwd });
|
|
51
62
|
this.additionalPromptContextManager = new AdditionalPromptContextManager(baseContextManager, options.additionalPromptSections);
|
|
52
|
-
this.
|
|
63
|
+
this.baselineContextManager = new AppPromptContextManager(this.additionalPromptContextManager, this.appPromptStore);
|
|
64
|
+
this.contextManager = { build: async (input) => {
|
|
65
|
+
// Capture before any asynchronous baseline work: an in-flight request never observes a later edit.
|
|
66
|
+
const content = this.activeSessionPrompt;
|
|
67
|
+
return applySessionPrompt(await this.baselineContextManager.build(input), content);
|
|
68
|
+
} };
|
|
53
69
|
this.pendingCwdTransitionPaths = normalizeCwdTransitionPaths(options.cwdTransitionPaths);
|
|
54
70
|
}
|
|
55
71
|
forkForSession(sessionId, resume = true) {
|
|
@@ -77,25 +93,39 @@ export class QueryEngine {
|
|
|
77
93
|
this.pendingRequestContextConsumed = onConsumed;
|
|
78
94
|
}
|
|
79
95
|
async initialize() {
|
|
96
|
+
if (this.sessionInitialization)
|
|
97
|
+
return this.sessionInitialization;
|
|
80
98
|
if (this.sessionInitialized)
|
|
81
99
|
return;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
const initialization = (async () => {
|
|
101
|
+
if (this.options.session?.enabled !== false && this.options.session) {
|
|
102
|
+
await this.openSession({ sessionId: this.options.session.sessionId, resume: this.options.session.resume });
|
|
103
|
+
}
|
|
104
|
+
this.sessionInitialized = true;
|
|
105
|
+
})();
|
|
106
|
+
this.sessionInitialization = initialization;
|
|
107
|
+
try {
|
|
108
|
+
await initialization;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
this.sessionInitialization = undefined;
|
|
112
|
+
}
|
|
91
113
|
}
|
|
92
114
|
async newSession() {
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
if (this.running)
|
|
116
|
+
throw new Error("cannot switch session while query engine is running");
|
|
117
|
+
const mutation = this.settingsMutation.then(async () => {
|
|
118
|
+
await this.sessionInitialization;
|
|
119
|
+
await this.openSession({ resume: false });
|
|
120
|
+
this.sessionInitialized = true;
|
|
121
|
+
const snapshot = this.sessionStore?.snapshot();
|
|
122
|
+
if (!snapshot)
|
|
123
|
+
throw new Error("session transcripts are disabled");
|
|
124
|
+
return snapshot;
|
|
125
|
+
});
|
|
126
|
+
// Session switches share the editor barrier: stale saves can never land in a new session.
|
|
127
|
+
this.settingsMutation = mutation.catch(() => undefined);
|
|
128
|
+
return mutation;
|
|
99
129
|
}
|
|
100
130
|
async listSessions(limit = 10) {
|
|
101
131
|
if (this.options.session?.enabled === false || !this.options.session)
|
|
@@ -121,92 +151,241 @@ export class QueryEngine {
|
|
|
121
151
|
});
|
|
122
152
|
}
|
|
123
153
|
async *sendUserText(text, options = {}) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
154
|
+
if (this.running)
|
|
155
|
+
throw new Error("query engine is already running");
|
|
156
|
+
this.running = true;
|
|
157
|
+
try {
|
|
158
|
+
await this.settingsMutation;
|
|
159
|
+
await this.initialize();
|
|
160
|
+
const cwdTransitionPaths = this.pendingCwdTransitionPaths;
|
|
161
|
+
const pendingRequestContext = this.pendingRequestContext;
|
|
162
|
+
const pendingRequestContextConsumed = this.pendingRequestContextConsumed;
|
|
163
|
+
this.pendingCwdTransitionPaths = undefined;
|
|
164
|
+
this.options.cwdTransitionPaths = undefined;
|
|
165
|
+
if (cwdTransitionPaths?.length)
|
|
166
|
+
void this.options.onCwdTransitionConsumed?.();
|
|
167
|
+
const userMessage = options.blocks
|
|
168
|
+
? await this.persistMessageImages({
|
|
169
|
+
...createTextMessage("user", options.displayText ?? text),
|
|
170
|
+
blocks: options.blocks,
|
|
171
|
+
})
|
|
172
|
+
: createTextMessage("user", text);
|
|
173
|
+
this.history.push(userMessage);
|
|
174
|
+
this.sessionStore?.recordMessage(userMessage);
|
|
175
|
+
this.scheduleSessionTitleCheck();
|
|
176
|
+
const initMessage = createSystemInitMessage({
|
|
177
|
+
agentId: this.agentId,
|
|
178
|
+
tools: this.options.tools.names(),
|
|
179
|
+
model: this.currentModel,
|
|
180
|
+
commands: [...(this.options.commands ?? [])],
|
|
181
|
+
agents: [...(this.options.agents ?? [])],
|
|
182
|
+
skills: [...(this.options.skills ?? [])],
|
|
183
|
+
plugins: [...(this.options.plugins ?? [])],
|
|
184
|
+
});
|
|
185
|
+
initMessage.metadata = {
|
|
186
|
+
...initMessage.metadata,
|
|
187
|
+
reasoning: cloneReasoningConfig(this.currentReasoning),
|
|
188
|
+
};
|
|
189
|
+
yield {
|
|
190
|
+
type: "message",
|
|
191
|
+
message: initMessage,
|
|
192
|
+
};
|
|
193
|
+
this.sessionStore?.recordMessage(initMessage);
|
|
194
|
+
const queryOptions = {
|
|
195
|
+
agentId: this.agentId,
|
|
196
|
+
model: this.currentModel,
|
|
197
|
+
reasoning: cloneReasoningConfig(this.currentReasoning),
|
|
198
|
+
queryOrigin: this.options.queryOrigin ?? "repl",
|
|
199
|
+
serviceTier: this.currentFastMode ? "priority" : undefined,
|
|
200
|
+
maxOutputTokensOverride: this.options.maxOutputTokensOverride,
|
|
201
|
+
contextWindowTokensOverride: this.currentContextWindowTokens,
|
|
202
|
+
maxTurns: this.options.maxTurns,
|
|
203
|
+
workspaceCwd: this.options.cwd,
|
|
204
|
+
requestContext: pendingRequestContext || cwdTransitionPaths?.length
|
|
205
|
+
? {
|
|
206
|
+
...pendingRequestContext,
|
|
207
|
+
...(cwdTransitionPaths?.length
|
|
208
|
+
? { cwdTransition: { paths: cwdTransitionPaths, current: cwdTransitionPaths.at(-1) } }
|
|
209
|
+
: {}),
|
|
210
|
+
}
|
|
211
|
+
: undefined,
|
|
212
|
+
onRequestContextConsumed: pendingRequestContext ? () => {
|
|
213
|
+
pendingRequestContextConsumed?.();
|
|
214
|
+
if (this.pendingRequestContext === pendingRequestContext) {
|
|
215
|
+
this.pendingRequestContext = undefined;
|
|
216
|
+
this.pendingRequestContextConsumed = undefined;
|
|
217
|
+
}
|
|
218
|
+
} : undefined,
|
|
219
|
+
abortSignal: options.abortSignal,
|
|
220
|
+
stopAfterTurn: options.stopAfterTurn,
|
|
221
|
+
beforeTurn: (messages) => this.consumeSessionSettings(messages),
|
|
222
|
+
};
|
|
223
|
+
const stream = query(this.history, {
|
|
224
|
+
...this.options,
|
|
225
|
+
contextManager: this.contextManager,
|
|
226
|
+
modelGateway: this.currentModelGateway,
|
|
227
|
+
taskNotificationSource: this.options.taskNotificationSource,
|
|
228
|
+
toolResultMemory: this.sessionStore?.toolResultMemory,
|
|
229
|
+
session: this.sessionStore ? { sessionId: this.sessionStore.sessionId, sessionDir: this.sessionStore.sessionDir, rootDir: this.options.session?.rootDir } : undefined,
|
|
230
|
+
recordContentReplacements: (records) => this.sessionStore?.recordContentReplacements(records),
|
|
231
|
+
exportToolCalls: (calls) => this.recordSyntheticToolCalls(calls),
|
|
232
|
+
applyCompaction: (result) => this.applyCompactionResult(result),
|
|
233
|
+
}, queryOptions);
|
|
234
|
+
for await (const event of stream) {
|
|
235
|
+
if (event.type === "message") {
|
|
236
|
+
const message = await this.persistMessageImages(event.message);
|
|
237
|
+
this.history.push(message);
|
|
238
|
+
this.sessionStore?.recordMessage(message);
|
|
239
|
+
yield { ...event, message };
|
|
240
|
+
continue;
|
|
175
241
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
181
|
-
|
|
242
|
+
if (event.type === "terminal") {
|
|
243
|
+
this.lastTerminalReason = event.reason;
|
|
244
|
+
// The final assistant/tool messages have already been recorded by this wrapper.
|
|
245
|
+
const hadPending = Object.keys(this.pendingSessionSettings).length > 0;
|
|
246
|
+
for (const result of await this.flushSessionSettings()) {
|
|
247
|
+
if (result.report)
|
|
248
|
+
yield { type: "context.compacted", compaction: result.report };
|
|
249
|
+
}
|
|
250
|
+
if (hadPending)
|
|
251
|
+
yield { type: "context.metrics", metrics: await this.contextMetrics() };
|
|
182
252
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
253
|
+
yield event;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
// Covers initialization errors, aborts, exceptions and consumer generator.return().
|
|
258
|
+
// Do not yield in finally: a closing consumer may never resume the generator.
|
|
259
|
+
try {
|
|
260
|
+
do {
|
|
261
|
+
await this.flushSessionSettings();
|
|
262
|
+
} while (Object.keys(this.pendingSessionSettings).length);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
this.running = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** Queue instance-local settings; no prompt messages or provider-global mutation. */
|
|
270
|
+
async updateSessionSettings(patch) {
|
|
271
|
+
const normalized = {};
|
|
272
|
+
if (Object.hasOwn(patch, "model"))
|
|
273
|
+
normalized.model = patch.model?.trim() || undefined;
|
|
274
|
+
if (Object.hasOwn(patch, "reasoning"))
|
|
275
|
+
normalized.reasoning = cloneReasoningConfig(patch.reasoning);
|
|
276
|
+
if (Object.hasOwn(patch, "fastMode"))
|
|
277
|
+
normalized.fastMode = patch.fastMode === true;
|
|
278
|
+
if (Object.hasOwn(patch, "contextWindowTokens")) {
|
|
279
|
+
const tokens = normalizePositiveInteger(patch.contextWindowTokens);
|
|
280
|
+
if (!tokens)
|
|
281
|
+
throw new Error("context window tokens must be a positive integer");
|
|
282
|
+
normalized.contextWindowTokens = tokens;
|
|
283
|
+
}
|
|
284
|
+
if (patch.compact)
|
|
285
|
+
normalized.compact = true;
|
|
286
|
+
if (this.running) {
|
|
287
|
+
this.pendingSessionSettings = { ...this.pendingSessionSettings, ...normalized };
|
|
288
|
+
return { deferred: true };
|
|
289
|
+
}
|
|
290
|
+
const mutation = this.settingsMutation.then(async () => {
|
|
291
|
+
await this.initialize();
|
|
292
|
+
const update = await this.applySessionSettings(normalized, this.getHistoryMessages());
|
|
293
|
+
if (update?.compaction)
|
|
294
|
+
this.applyCompactionResult(update.compaction);
|
|
295
|
+
return { deferred: false, ...(update?.compaction ? { compaction: update.compaction } : {}) };
|
|
296
|
+
});
|
|
297
|
+
// A rejected mutation must not poison subsequent settings or user input.
|
|
298
|
+
this.settingsMutation = mutation.catch(() => undefined);
|
|
299
|
+
return mutation;
|
|
300
|
+
}
|
|
301
|
+
/** Editor state includes accepted queued changes; runtime exports still describe the active turn. */
|
|
302
|
+
async getSessionPrompt() {
|
|
303
|
+
await this.settingsMutation;
|
|
304
|
+
await this.initialize();
|
|
305
|
+
const snapshot = await this.buildPromptExportSnapshot(this.getHistoryMessages());
|
|
306
|
+
return createSessionPromptSnapshot(this.sessionPromptKey(), this.sessionPromptState, snapshot.baseSystemPrompt ?? "");
|
|
307
|
+
}
|
|
308
|
+
async updateSessionPrompt(input) {
|
|
309
|
+
const update = parseSessionPromptUpdate(input);
|
|
310
|
+
const mutation = this.settingsMutation.then(async () => {
|
|
311
|
+
await this.initialize();
|
|
312
|
+
const key = this.sessionPromptKey();
|
|
313
|
+
const snapshot = await this.buildPromptExportSnapshot(this.getHistoryMessages());
|
|
314
|
+
const current = createSessionPromptSnapshot(key, this.sessionPromptState, snapshot.baseSystemPrompt ?? "");
|
|
315
|
+
if (key !== this.sessionPromptKey() || update.revision !== current.revision) {
|
|
316
|
+
throw new SessionPromptError("session prompt changed; reload before saving", 409);
|
|
317
|
+
}
|
|
318
|
+
const state = { content: update.reset ? null : update.content, revision: randomUUID() };
|
|
319
|
+
// Persist before accepting, including busy edits, so same-session resume cannot lose them.
|
|
320
|
+
this.sessionStore?.recordSessionPrompt(state);
|
|
321
|
+
this.sessionPromptState = state;
|
|
322
|
+
const deferred = this.running;
|
|
323
|
+
if (deferred)
|
|
324
|
+
this.pendingSessionSettings = { ...this.pendingSessionSettings, sessionPrompt: state.content };
|
|
325
|
+
else
|
|
326
|
+
this.activeSessionPrompt = state.content;
|
|
327
|
+
return { ok: true, ...createSessionPromptSnapshot(key, state, snapshot.baseSystemPrompt ?? ""), deferred };
|
|
328
|
+
});
|
|
329
|
+
this.settingsMutation = mutation.catch(() => undefined);
|
|
330
|
+
return mutation;
|
|
331
|
+
}
|
|
332
|
+
sessionPromptKey() {
|
|
333
|
+
return this.sessionStore ? `${this.sessionStore.sessionDir}:${this.agentId}` : this.ephemeralSessionKey;
|
|
334
|
+
}
|
|
335
|
+
getPendingSessionSettings() {
|
|
336
|
+
const patch = { ...this.pendingSessionSettings };
|
|
337
|
+
if (Object.hasOwn(patch, "reasoning"))
|
|
338
|
+
patch.reasoning = cloneReasoningConfig(patch.reasoning);
|
|
339
|
+
return patch;
|
|
340
|
+
}
|
|
341
|
+
async consumeSessionSettings(messages) {
|
|
342
|
+
const patch = this.pendingSessionSettings;
|
|
343
|
+
if (!Object.keys(patch).length)
|
|
344
|
+
return undefined;
|
|
345
|
+
this.pendingSessionSettings = {};
|
|
346
|
+
return this.applySessionSettings(patch, messages);
|
|
347
|
+
}
|
|
348
|
+
async applySessionSettings(patch, messages) {
|
|
349
|
+
const changed = {};
|
|
350
|
+
if (Object.hasOwn(patch, "sessionPrompt"))
|
|
351
|
+
this.activeSessionPrompt = patch.sessionPrompt ?? null;
|
|
352
|
+
if (Object.hasOwn(patch, "model") && patch.model !== this.currentModel) {
|
|
353
|
+
this.currentModel = patch.model;
|
|
354
|
+
changed.model = patch.model;
|
|
355
|
+
}
|
|
356
|
+
if (Object.hasOwn(patch, "reasoning") && !sameReasoningConfig(patch.reasoning, this.currentReasoning)) {
|
|
357
|
+
this.currentReasoning = cloneReasoningConfig(patch.reasoning);
|
|
358
|
+
changed.reasoning = cloneReasoningConfig(patch.reasoning);
|
|
359
|
+
}
|
|
360
|
+
if (Object.hasOwn(patch, "fastMode") && patch.fastMode !== this.currentFastMode) {
|
|
361
|
+
this.currentFastMode = patch.fastMode === true;
|
|
362
|
+
this.sessionStore?.recordFastMode(this.currentFastMode);
|
|
363
|
+
changed.fastMode = this.currentFastMode;
|
|
364
|
+
}
|
|
365
|
+
if (Object.hasOwn(patch, "contextWindowTokens") && patch.contextWindowTokens !== this.currentContextWindowTokens) {
|
|
366
|
+
this.currentContextWindowTokens = patch.contextWindowTokens;
|
|
367
|
+
this.sessionStore?.recordContextWindowTokens(this.currentContextWindowTokens);
|
|
368
|
+
changed.contextWindowTokens = this.currentContextWindowTokens;
|
|
369
|
+
}
|
|
370
|
+
let compaction;
|
|
371
|
+
if (patch.compact) {
|
|
372
|
+
const compactor = this.options.compactor ?? new ModelDrivenCompactor(this.currentModelGateway);
|
|
373
|
+
const budget = { ...this.options.contextBudget, contextWindowTokens: this.currentContextWindowTokens ?? this.options.contextBudget?.contextWindowTokens };
|
|
374
|
+
compaction = withCompactionReport(await (compactor.manualCompact?.(messages, budget) ?? compactor.compact(messages, budget)), messages.length);
|
|
375
|
+
}
|
|
376
|
+
return Object.keys(changed).length || compaction ? { settings: changed, compaction } : undefined;
|
|
377
|
+
}
|
|
378
|
+
async flushSessionSettings() {
|
|
379
|
+
const results = [];
|
|
380
|
+
while (Object.keys(this.pendingSessionSettings).length) {
|
|
381
|
+
const update = await this.consumeSessionSettings(this.getHistoryMessages());
|
|
382
|
+
if (update?.compaction) {
|
|
383
|
+
this.applyCompactionResult(update.compaction);
|
|
384
|
+
if (update.compaction.changed)
|
|
385
|
+
results.push(update.compaction);
|
|
205
386
|
}
|
|
206
|
-
if (event.type === "terminal")
|
|
207
|
-
this.lastTerminalReason = event.reason;
|
|
208
|
-
yield event;
|
|
209
387
|
}
|
|
388
|
+
return results;
|
|
210
389
|
}
|
|
211
390
|
setModel(model, reasoning, updateReasoning = false) {
|
|
212
391
|
this.currentModel = model?.trim() || undefined;
|
|
@@ -264,6 +443,9 @@ export class QueryEngine {
|
|
|
264
443
|
return this.appPromptStore.snapshot();
|
|
265
444
|
}
|
|
266
445
|
reset() {
|
|
446
|
+
if (this.running)
|
|
447
|
+
throw new Error("cannot reset while query engine is running");
|
|
448
|
+
this.pendingSessionSettings = {};
|
|
267
449
|
this.history.length = 0;
|
|
268
450
|
this.lastTerminalReason = undefined;
|
|
269
451
|
this.cancelPendingTitleWork();
|
|
@@ -271,17 +453,10 @@ export class QueryEngine {
|
|
|
271
453
|
this.notifySessionTitleChange(this.sessionStore?.snapshot());
|
|
272
454
|
}
|
|
273
455
|
async compact(options = {}) {
|
|
274
|
-
await this.initialize();
|
|
275
456
|
if (options.abortSignal?.aborted)
|
|
276
457
|
return { messages: this.getHistoryMessages(), changed: false, reason: "none" };
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
...this.options.contextBudget,
|
|
280
|
-
contextWindowTokens: this.currentContextWindowTokens ?? this.options.contextBudget?.contextWindowTokens,
|
|
281
|
-
};
|
|
282
|
-
const result = withCompactionReport(await (compactor.manualCompact?.(this.history, contextBudget) ?? compactor.compact(this.history, contextBudget)), this.history.length);
|
|
283
|
-
this.applyCompactionResult(result);
|
|
284
|
-
return result;
|
|
458
|
+
const result = await this.updateSessionSettings({ compact: true });
|
|
459
|
+
return result.compaction ?? { messages: this.getHistoryMessages(), changed: false, reason: "none" };
|
|
285
460
|
}
|
|
286
461
|
async pureCompact(options = {}) {
|
|
287
462
|
await this.initialize();
|
|
@@ -376,12 +551,15 @@ export class QueryEngine {
|
|
|
376
551
|
emit: () => undefined,
|
|
377
552
|
};
|
|
378
553
|
const initialToolDefinitions = this.options.tools.definitions(toolContext);
|
|
379
|
-
const
|
|
554
|
+
const activePrompt = this.activeSessionPrompt;
|
|
555
|
+
const baseline = await this.baselineContextManager.build({
|
|
380
556
|
agentId: this.agentId,
|
|
381
557
|
messages,
|
|
382
558
|
enabledTools: initialToolDefinitions.map((tool) => tool.name),
|
|
383
559
|
toolUseContext: toolContext,
|
|
384
560
|
});
|
|
561
|
+
const context = applySessionPrompt(baseline, activePrompt);
|
|
562
|
+
const desiredPrompt = createSessionPromptSnapshot(this.sessionPromptKey(), this.sessionPromptState, baseline.systemPrompt);
|
|
385
563
|
const toolDefinitions = this.options.tools.definitions(toolContext);
|
|
386
564
|
const messagesWithUserContext = applyRuntimeContextForPromptCache([], context.userContext, {});
|
|
387
565
|
const userContextPrompt = messagesWithUserContext[0]?.blocks
|
|
@@ -392,7 +570,13 @@ export class QueryEngine {
|
|
|
392
570
|
model: this.currentModel,
|
|
393
571
|
reasoning: cloneReasoningConfig(this.currentReasoning),
|
|
394
572
|
systemPrompt: context.systemPrompt,
|
|
395
|
-
baseSystemPrompt:
|
|
573
|
+
baseSystemPrompt: baseline.systemPrompt,
|
|
574
|
+
sessionPrompt: {
|
|
575
|
+
override: activePrompt !== null,
|
|
576
|
+
revision: desiredPrompt.revision,
|
|
577
|
+
deferred: Object.hasOwn(this.pendingSessionSettings, "sessionPrompt"),
|
|
578
|
+
pendingOverride: desiredPrompt.override,
|
|
579
|
+
},
|
|
396
580
|
promptSections: context.promptSections,
|
|
397
581
|
userContext: context.userContext,
|
|
398
582
|
systemContext: context.systemContext,
|
|
@@ -449,6 +633,9 @@ export class QueryEngine {
|
|
|
449
633
|
resume: options.resume,
|
|
450
634
|
toolResultThresholdChars: this.options.session.toolResultThresholdChars,
|
|
451
635
|
});
|
|
636
|
+
this.pendingSessionSettings = {};
|
|
637
|
+
this.sessionPromptState = this.sessionStore.getSessionPrompt();
|
|
638
|
+
this.activeSessionPrompt = this.sessionPromptState?.content ?? null;
|
|
452
639
|
this.history.length = 0;
|
|
453
640
|
if (options.resume)
|
|
454
641
|
this.history.push(...this.sessionStore.getInitialMessages());
|
|
@@ -609,6 +796,11 @@ function resolveSessionTitleDelayMs() {
|
|
|
609
796
|
}
|
|
610
797
|
return DEFAULT_SESSION_TITLE_DELAY_MS;
|
|
611
798
|
}
|
|
799
|
+
function sameReasoningConfig(a, b) {
|
|
800
|
+
if (a === null || b === null || a === undefined || b === undefined)
|
|
801
|
+
return a === b;
|
|
802
|
+
return a.effort === b.effort && a.summary === b.summary;
|
|
803
|
+
}
|
|
612
804
|
function cloneReasoningConfig(reasoning) {
|
|
613
805
|
if (reasoning === null)
|
|
614
806
|
return null;
|