newmark-agent 0.5.14 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-utility-host.bundle.cjs +4565 -2998
- package/dist/conversation-utility-host.js +1 -1
- package/dist/core/agent.d.ts +108 -16
- package/dist/core/agent.js +698 -195
- package/dist/core/agentKernel/agent.d.ts +1 -0
- package/dist/core/agentKernel/agent.js +9 -0
- package/dist/core/agentKernelDiagnostics.d.ts +4 -6
- package/dist/core/agentKernelDiagnostics.js +12 -9
- package/dist/core/agentKernelRunner.d.ts +5 -0
- package/dist/core/agentKernelRunner.js +250 -107
- package/dist/core/autoRouter.js +17 -27
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +7 -1
- package/dist/core/conversationCommandState.d.ts +17 -0
- package/dist/core/conversationCommandState.js +140 -0
- package/dist/core/conversationKernel.d.ts +25 -4
- package/dist/core/conversationKernel.js +360 -89
- package/dist/core/conversationListEvent.d.ts +6 -0
- package/dist/core/conversationListEvent.js +14 -0
- package/dist/core/electronUtilityAgentClient.d.ts +4 -1
- package/dist/core/electronUtilityAgentClient.js +4 -4
- package/dist/core/electronUtilityRuntimePool.d.ts +8 -2
- package/dist/core/electronUtilityRuntimePool.js +11 -5
- package/dist/core/installUpdate.js +41 -29
- package/dist/core/modelResponseHealth.d.ts +17 -0
- package/dist/core/modelResponseHealth.js +193 -0
- package/dist/core/providerUsageAccounting.d.ts +47 -0
- package/dist/core/providerUsageAccounting.js +71 -0
- package/dist/core/requestContextEstimate.d.ts +18 -0
- package/dist/core/requestContextEstimate.js +54 -0
- package/dist/core/subagent.d.ts +89 -5
- package/dist/core/subagent.js +264 -41
- package/dist/core/subagentCommunication.d.ts +43 -0
- package/dist/core/subagentCommunication.js +167 -0
- package/dist/core/types.d.ts +34 -1
- package/dist/core/utilityAgentProtocol.d.ts +4 -0
- package/dist/core/workEventCoalescer.js +4 -1
- package/dist/core/wslAgentClient.d.ts +18 -5
- package/dist/core/wslAgentClient.js +79 -27
- package/dist/core/wslAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentRuntimePool.d.ts +8 -2
- package/dist/core/wslAgentRuntimePool.js +19 -6
- package/dist/core/wslRuntimeProcessTree.d.ts +10 -0
- package/dist/core/wslRuntimeProcessTree.js +104 -0
- package/dist/llm/provider.d.ts +5 -12
- package/dist/llm/provider.js +150 -269
- package/dist/main.js +582 -356
- package/dist/preload.js +3 -3
- package/dist/providers/chat-completions.adapter.d.ts +2 -0
- package/dist/providers/chat-completions.adapter.js +84 -36
- package/dist/providers/provider-adapter.d.ts +2 -0
- package/dist/providers/provider-events.d.ts +23 -14
- package/dist/providers/provider-events.js +148 -43
- package/dist/providers/provider-headers.d.ts +7 -3
- package/dist/providers/provider-headers.js +109 -39
- package/dist/providers/provider-request-compat.d.ts +6 -0
- package/dist/providers/provider-request-compat.js +37 -0
- package/dist/providers/responses.adapter.d.ts +2 -0
- package/dist/providers/responses.adapter.js +192 -126
- package/dist/server.d.ts +9 -2
- package/dist/server.js +100 -24
- package/dist/tools/index.js +17 -1
- package/dist/ui/index.html +2515 -693
- package/dist/ui/lucide-sprite.svg +7 -0
- package/dist/ui/startup.html +6 -6
- package/dist/wsl-agent-host.bundle.cjs +4575 -3006
- package/dist/wsl-agent-host.js +9 -4
- package/package.json +23 -4
|
@@ -38,15 +38,132 @@ class ConversationKernel {
|
|
|
38
38
|
return () => this.listeners.delete(listener);
|
|
39
39
|
}
|
|
40
40
|
isRunning(target) {
|
|
41
|
-
|
|
41
|
+
const runtime = this.findRuntime(target);
|
|
42
|
+
return !!runtime?.activePromise || runtime?.externalOwner?.active === true;
|
|
43
|
+
}
|
|
44
|
+
conversationOwner(target) {
|
|
45
|
+
return this.findRuntime(target)?.runner || null;
|
|
46
|
+
}
|
|
47
|
+
/** Branch mutations retain the Agent but reload that branch's durable queue. */
|
|
48
|
+
refreshIdleConversation(target) {
|
|
49
|
+
const previous = this.findRuntime(target);
|
|
50
|
+
if (!previous)
|
|
51
|
+
return;
|
|
52
|
+
if (this.isRunning(target) || previous.preparingArchive)
|
|
53
|
+
throw new Error('Cannot refresh a running conversation');
|
|
54
|
+
previous.preparingArchive = true;
|
|
55
|
+
this.finishArchive(target, true, true);
|
|
56
|
+
const restored = this.runtime(previous.target, previous.options, previous.runner);
|
|
57
|
+
restored.queuePaused = previous.queuePaused;
|
|
58
|
+
restored.externalOwner = previous.externalOwner;
|
|
59
|
+
restored.runId = previous.runId;
|
|
60
|
+
this.emitQueueUpdate(restored);
|
|
61
|
+
}
|
|
62
|
+
/** Detach idle owners before config replacement or workspace removal. */
|
|
63
|
+
disposeIdle(workspaceKey) {
|
|
64
|
+
const runtimes = Array.from(this.runtimes.values()).filter(runtime => !workspaceKey || runtime.target.workspaceKey === workspaceKey);
|
|
65
|
+
if (runtimes.some(runtime => runtime.activePromise || runtime.externalOwner || runtime.pendingNextTurn.length || runtime.runner.subagents.hasPendingWork())) {
|
|
66
|
+
throw new Error('Cannot release a conversation with running or queued work');
|
|
67
|
+
}
|
|
68
|
+
for (const runtime of runtimes) {
|
|
69
|
+
runtime.runner.flushWorkspaceConversationState();
|
|
70
|
+
runtime.preparingArchive = true;
|
|
71
|
+
this.finishArchive(runtime.target, true);
|
|
72
|
+
}
|
|
73
|
+
return runtimes.map(runtime => runtime.runtimeKey);
|
|
74
|
+
}
|
|
75
|
+
/** A Flow and its queue share one Agent; no second history writer is created. */
|
|
76
|
+
beginExternalRun(targetInput, options, createOwner, previousQueuePaused) {
|
|
77
|
+
const target = this.normalizeTarget(targetInput);
|
|
78
|
+
const existing = this.findRuntime(target);
|
|
79
|
+
if (existing?.activePromise || existing?.externalOwner?.active)
|
|
80
|
+
throw new Error('Target conversation is already running');
|
|
81
|
+
const runtime = existing || this.runtime(target, options, createOwner());
|
|
82
|
+
runtime.options = { ...options };
|
|
83
|
+
runtime.externalOwner = { active: true, wasPaused: runtime.externalOwner?.wasPaused ?? previousQueuePaused ?? runtime.queuePaused };
|
|
84
|
+
runtime.queuePaused = true;
|
|
85
|
+
runtime.runId ||= (0, crypto_1.randomUUID)();
|
|
86
|
+
runtime.stopRequestedRunId = '';
|
|
87
|
+
this.emitQueueUpdate(runtime);
|
|
88
|
+
return runtime.runner;
|
|
89
|
+
}
|
|
90
|
+
settleExternalRun(target, completed) {
|
|
91
|
+
const runtime = this.findRuntime(target);
|
|
92
|
+
if (!runtime?.externalOwner)
|
|
93
|
+
return;
|
|
94
|
+
const wasPaused = runtime.externalOwner.wasPaused;
|
|
95
|
+
if (completed)
|
|
96
|
+
runtime.externalOwner = undefined;
|
|
97
|
+
else
|
|
98
|
+
runtime.externalOwner.active = false;
|
|
99
|
+
runtime.queuePaused = completed && !runtime.preparingArchive ? wasPaused : true;
|
|
100
|
+
runtime.runner.saveWorkspaceConversationState(true);
|
|
101
|
+
this.emitQueueUpdate(runtime);
|
|
102
|
+
if (!runtime.queuePaused && runtime.pendingNextTurn.length)
|
|
103
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
104
|
+
}
|
|
105
|
+
releaseExternalRun(target, paused) {
|
|
106
|
+
const runtime = this.findRuntime(target);
|
|
107
|
+
if (!runtime)
|
|
108
|
+
return;
|
|
109
|
+
if (runtime.externalOwner?.active)
|
|
110
|
+
throw new Error('Wait for the Flow to stop before releasing its queue');
|
|
111
|
+
runtime.externalOwner = undefined;
|
|
112
|
+
this.setQueuePaused(target, paused);
|
|
113
|
+
}
|
|
114
|
+
async prepareForArchive(target) {
|
|
115
|
+
const runtime = this.findRuntime(target);
|
|
116
|
+
if (!runtime)
|
|
117
|
+
return null;
|
|
118
|
+
if (runtime.runner.subagents.hasPendingWork())
|
|
119
|
+
throw new Error('Cannot archive a conversation with running or queued subagents');
|
|
120
|
+
runtime.preparingArchive = true;
|
|
121
|
+
runtime.queuePaused = true;
|
|
122
|
+
if (runtime.goalContinuationTimer)
|
|
123
|
+
clearTimeout(runtime.goalContinuationTimer);
|
|
124
|
+
runtime.goalContinuationTimer = undefined;
|
|
125
|
+
runtime.runner.abortActiveKernelRun('user_stop');
|
|
126
|
+
if (runtime.activePromise) {
|
|
127
|
+
runtime.stopRequestedRunId = runtime.runId;
|
|
128
|
+
try {
|
|
129
|
+
await runtime.activePromise;
|
|
130
|
+
}
|
|
131
|
+
catch { /* archive owns the terminal outcome */ }
|
|
132
|
+
}
|
|
133
|
+
runtime.runner.flushWorkspaceConversationState();
|
|
134
|
+
return runtime.runner;
|
|
135
|
+
}
|
|
136
|
+
finishArchive(target, completed, preserveOwner = false) {
|
|
137
|
+
const runtime = this.findRuntime(target);
|
|
138
|
+
if (!runtime?.preparingArchive)
|
|
139
|
+
return;
|
|
140
|
+
if (!completed) {
|
|
141
|
+
runtime.preparingArchive = false;
|
|
142
|
+
runtime.queuePaused = true;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Branch refresh reuses this facade immediately; terminal release must
|
|
146
|
+
// detach its shared-manager callbacks as well as the kernel subscriptions.
|
|
147
|
+
if (!preserveOwner)
|
|
148
|
+
runtime.runner.releaseConversationRuntimeBindings();
|
|
149
|
+
runtime.unsubscribe?.();
|
|
150
|
+
runtime.unsubscribePeer?.();
|
|
151
|
+
runtime.unsubscribeRootInboxWake?.();
|
|
152
|
+
runtime.unsubscribeUserMessageStart?.();
|
|
153
|
+
if (runtime.goalContinuationTimer)
|
|
154
|
+
clearTimeout(runtime.goalContinuationTimer);
|
|
155
|
+
this.runtimes.delete(runtime.runtimeKey);
|
|
42
156
|
}
|
|
43
157
|
isAnyRunning() {
|
|
44
158
|
for (const runtime of this.runtimes.values()) {
|
|
45
|
-
if (runtime.activePromise)
|
|
159
|
+
if (runtime.activePromise || runtime.externalOwner?.active)
|
|
46
160
|
return true;
|
|
47
161
|
}
|
|
48
162
|
return false;
|
|
49
163
|
}
|
|
164
|
+
hasRetainedWork() {
|
|
165
|
+
return Array.from(this.runtimes.values()).some(runtime => runtime.activePromise || runtime.externalOwner || runtime.pendingNextTurn.length || runtime.runner.subagents.hasPendingWork());
|
|
166
|
+
}
|
|
50
167
|
flushPersistence() {
|
|
51
168
|
this.host.flushWorkspaceConversationState();
|
|
52
169
|
for (const runtime of this.runtimes.values())
|
|
@@ -81,10 +198,12 @@ class ConversationKernel {
|
|
|
81
198
|
const visibleMode = message.visibleMode;
|
|
82
199
|
return {
|
|
83
200
|
text,
|
|
201
|
+
...(message.userMessageId || message.clientMessageId ? { userMessageId: message.userMessageId || message.clientMessageId } : {}),
|
|
84
202
|
...(images?.length ? { images } : {}),
|
|
85
203
|
...(attachments?.length ? { attachments } : {}),
|
|
86
204
|
...(visible ? { visibleUserInput: visible } : {}),
|
|
87
205
|
...(visibleMode ? { visibleMode } : {}),
|
|
206
|
+
...(message.goalObjective ? { goalObjective: message.goalObjective } : {}),
|
|
88
207
|
// A normal user follow-up must never inherit the identity metadata that
|
|
89
208
|
// was only needed while it lived in the runtime queue. In particular,
|
|
90
209
|
// do not let a stale/forwarded hiddenUserInput flag classify it as an
|
|
@@ -112,13 +231,15 @@ class ConversationKernel {
|
|
|
112
231
|
goalObjective: item.message.goalObjective,
|
|
113
232
|
runId: item.message.runId,
|
|
114
233
|
createdAt: String(item.message.createdAt || ''),
|
|
234
|
+
images: item.message.images?.map(image => ({ ...image })),
|
|
115
235
|
}];
|
|
116
236
|
});
|
|
117
237
|
}
|
|
118
238
|
enqueueNext(target, input) {
|
|
119
239
|
const runtime = this.findRuntime(target);
|
|
120
|
-
if (!runtime
|
|
121
|
-
throw new Error('Target conversation is
|
|
240
|
+
if (!runtime)
|
|
241
|
+
throw new Error('Target conversation runtime is unavailable');
|
|
242
|
+
runtime.runId ||= (0, crypto_1.randomUUID)();
|
|
122
243
|
const id = String(input.id || '').trim().slice(0, 200);
|
|
123
244
|
const text = String(input.text || '').trim();
|
|
124
245
|
if (!id || !text)
|
|
@@ -147,12 +268,18 @@ class ConversationKernel {
|
|
|
147
268
|
clientMessageId: id,
|
|
148
269
|
runId: runtime.runId,
|
|
149
270
|
createdAt,
|
|
271
|
+
images: input.images,
|
|
272
|
+
visibleUserInput: text,
|
|
273
|
+
visibleMode: String(input.requestedMode || 'build'),
|
|
274
|
+
goalObjective: input.goalObjective,
|
|
150
275
|
}]);
|
|
151
276
|
this.trackQueuedMessage(runtime, prompt, 'followUp');
|
|
152
277
|
this.emitQueueUpdate(runtime);
|
|
278
|
+
if (!runtime.activePromise && !runtime.externalOwner && !runtime.queuePaused)
|
|
279
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
153
280
|
return this.queueItems(runtime.target).find(item => item.id === id);
|
|
154
281
|
}
|
|
155
|
-
updateQueueItem(target, idInput, textInput) {
|
|
282
|
+
updateQueueItem(target, idInput, textInput, input = {}) {
|
|
156
283
|
const runtime = this.findRuntime(target);
|
|
157
284
|
if (!runtime)
|
|
158
285
|
throw new Error('Target conversation runtime is unavailable');
|
|
@@ -163,17 +290,26 @@ class ConversationKernel {
|
|
|
163
290
|
const pending = runtime.pendingNextTurn.find(item => typeof item.message !== 'string' && item.message.clientMessageId === id);
|
|
164
291
|
if (!pending || typeof pending.message === 'string')
|
|
165
292
|
throw new Error('Queue item is no longer editable');
|
|
166
|
-
const
|
|
293
|
+
const previous = runtime.runner.conversationContinuations().find(item => item.clientMessageId === id);
|
|
167
294
|
pending.message.text = `[Next queued while current turn is running]\n${text}`;
|
|
168
295
|
pending.message.visibleUserInput = text;
|
|
169
|
-
|
|
296
|
+
if (input.requestedMode !== undefined)
|
|
297
|
+
pending.message.visibleMode = input.requestedMode;
|
|
298
|
+
if (input.goalObjective !== undefined)
|
|
299
|
+
pending.message.goalObjective = input.goalObjective;
|
|
300
|
+
if (input.images !== undefined)
|
|
301
|
+
pending.message.images = input.images.map(image => ({ ...image }));
|
|
170
302
|
runtime.runner.retainConversationContinuations([{
|
|
303
|
+
...previous,
|
|
171
304
|
content: pending.message.text,
|
|
172
305
|
queueMode: pending.queueMode,
|
|
173
306
|
clientMessageId: id,
|
|
174
307
|
runId: pending.message.runId,
|
|
308
|
+
images: pending.message.images,
|
|
309
|
+
visibleUserInput: text,
|
|
310
|
+
visibleMode: pending.message.visibleMode,
|
|
311
|
+
goalObjective: pending.message.goalObjective,
|
|
175
312
|
}]);
|
|
176
|
-
this.replaceTrackedQueuedMessage(runtime, oldPrompt, pending.message.text, pending.queueMode);
|
|
177
313
|
this.emitQueueUpdate(runtime);
|
|
178
314
|
return this.queueItems(runtime.target).find(item => item.id === id);
|
|
179
315
|
}
|
|
@@ -188,7 +324,6 @@ class ConversationKernel {
|
|
|
188
324
|
const [removed] = runtime.pendingNextTurn.splice(index, 1);
|
|
189
325
|
if (typeof removed.message !== 'string') {
|
|
190
326
|
runtime.runner.consumeConversationContinuation({ content: removed.message.text, queueMode: removed.queueMode, clientMessageId: id });
|
|
191
|
-
this.consumeQueuedMessage(runtime, removed.message.text);
|
|
192
327
|
}
|
|
193
328
|
this.emitQueueUpdate(runtime);
|
|
194
329
|
return true;
|
|
@@ -237,6 +372,8 @@ class ConversationKernel {
|
|
|
237
372
|
const runtime = this.findRuntime(target);
|
|
238
373
|
if (!runtime)
|
|
239
374
|
throw new Error('Target conversation runtime is unavailable');
|
|
375
|
+
if (!paused && runtime.externalOwner)
|
|
376
|
+
throw new Error('Release the Flow owner before resuming its queue');
|
|
240
377
|
runtime.queuePaused = paused;
|
|
241
378
|
this.emitQueueUpdate(runtime);
|
|
242
379
|
if (!paused && !runtime.activePromise && runtime.pendingNextTurn.length) {
|
|
@@ -245,9 +382,12 @@ class ConversationKernel {
|
|
|
245
382
|
return runtime.queuePaused;
|
|
246
383
|
}
|
|
247
384
|
queueAction(target, action, input = {}) {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
385
|
+
const normalized = this.normalizeTarget(target);
|
|
386
|
+
const runtime = this.findRuntime(normalized) || this.runtime(normalized, {
|
|
387
|
+
mode: this.host.mode, model: this.host.modelSelectionValue(), intelligence: this.host.intelligence,
|
|
388
|
+
inputMode: this.host.inputMode, engine: this.host.engine,
|
|
389
|
+
});
|
|
390
|
+
runtime.runId ||= (0, crypto_1.randomUUID)();
|
|
251
391
|
let receipt;
|
|
252
392
|
if (action === 'enqueue') {
|
|
253
393
|
this.enqueueNext(runtime.target, {
|
|
@@ -260,7 +400,7 @@ class ConversationKernel {
|
|
|
260
400
|
});
|
|
261
401
|
}
|
|
262
402
|
else if (action === 'update') {
|
|
263
|
-
this.updateQueueItem(runtime.target, String(input.id || ''), String(input.text || ''));
|
|
403
|
+
this.updateQueueItem(runtime.target, String(input.id || ''), String(input.text || ''), input);
|
|
264
404
|
}
|
|
265
405
|
else if (action === 'delete') {
|
|
266
406
|
if (!this.deleteQueueItem(runtime.target, String(input.id || '')))
|
|
@@ -272,7 +412,12 @@ class ConversationKernel {
|
|
|
272
412
|
else if (action === 'toggle_pause') {
|
|
273
413
|
this.setQueuePaused(runtime.target, !runtime.queuePaused);
|
|
274
414
|
}
|
|
415
|
+
else if (action === 'set_pause') {
|
|
416
|
+
this.setQueuePaused(runtime.target, input.paused === true);
|
|
417
|
+
}
|
|
275
418
|
else if (action === 'guide') {
|
|
419
|
+
if (input.text !== undefined)
|
|
420
|
+
this.updateQueueItem(runtime.target, String(input.id || ''), input.text, input);
|
|
276
421
|
const item = this.queueItems(runtime.target).find(entry => entry.id === String(input.id || ''));
|
|
277
422
|
if (!item)
|
|
278
423
|
throw new Error('Queue item was not found');
|
|
@@ -284,6 +429,7 @@ class ConversationKernel {
|
|
|
284
429
|
deliveryMode: 'steer',
|
|
285
430
|
text: item.goalObjective ? `Goal for the current Build:\n${item.goalObjective}` : item.text,
|
|
286
431
|
goalObjective: item.goalObjective,
|
|
432
|
+
images: runtime.pendingNextTurn.find(entry => typeof entry.message !== 'string' && entry.message.clientMessageId === item.id)?.message?.images,
|
|
287
433
|
createdAt: item.createdAt || new Date().toISOString(),
|
|
288
434
|
});
|
|
289
435
|
if (receipt.status === 'rejected')
|
|
@@ -323,11 +469,18 @@ class ConversationKernel {
|
|
|
323
469
|
options: question.options.map(option => ({ ...option })),
|
|
324
470
|
})) : undefined;
|
|
325
471
|
}
|
|
326
|
-
snapshot(target) {
|
|
472
|
+
snapshot(target, options = {}) {
|
|
327
473
|
const normalized = this.normalizeTarget(target);
|
|
328
|
-
|
|
474
|
+
let runtime = this.findRuntime(normalized);
|
|
329
475
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
330
|
-
|
|
476
|
+
if (!runtime && runner.conversationContinuations().length) {
|
|
477
|
+
runtime = this.runtime(normalized, { mode: runner.mode, model: runner.modelSelectionValue(), intelligence: runner.intelligence, inputMode: runner.inputMode, engine: runner.engine }, runner);
|
|
478
|
+
// Recovered user input remains visible and manageable until its owner
|
|
479
|
+
// explicitly restores the persisted queue policy or resumes the queue.
|
|
480
|
+
runtime.queuePaused = true;
|
|
481
|
+
runtime.runId ||= (0, crypto_1.randomUUID)();
|
|
482
|
+
}
|
|
483
|
+
const conversationSnapshot = runner.getConversationSnapshot(normalized.conversationId, options);
|
|
331
484
|
return {
|
|
332
485
|
...conversationSnapshot,
|
|
333
486
|
workRuns: this.bindWorkRunsToRuntimeTarget(conversationSnapshot.workRuns, normalized),
|
|
@@ -338,7 +491,7 @@ class ConversationKernel {
|
|
|
338
491
|
workEvents: this.events(normalized),
|
|
339
492
|
runtime: this.runtimeState(normalized),
|
|
340
493
|
mode: runner.mode,
|
|
341
|
-
model: runner.
|
|
494
|
+
model: runner.modelSelectionValue(),
|
|
342
495
|
intelligence: runner.intelligence,
|
|
343
496
|
status: runner.status,
|
|
344
497
|
goal: conversationSnapshot.goal,
|
|
@@ -398,7 +551,7 @@ class ConversationKernel {
|
|
|
398
551
|
const canReactivateFinalizingRun = !runtime.activePromise
|
|
399
552
|
&& runtime.guideAcceptanceClosedRunId === runtime.runId
|
|
400
553
|
&& (!requestedRunId || requestedRunId === runtime.runId);
|
|
401
|
-
if (!runtime.activePromise && !canReactivateFinalizingRun) {
|
|
554
|
+
if (!runtime.activePromise && !runtime.externalOwner?.active && !canReactivateFinalizingRun) {
|
|
402
555
|
return { ...base, reason: 'Target conversation is not running' };
|
|
403
556
|
}
|
|
404
557
|
let safeImages = [];
|
|
@@ -511,6 +664,12 @@ class ConversationKernel {
|
|
|
511
664
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
512
665
|
return accepted;
|
|
513
666
|
}
|
|
667
|
+
if (runtime.externalOwner) {
|
|
668
|
+
const rejected = { ...accepted, status: 'rejected', reason: 'The current Flow Build is not accepting Guide input.', updatedAt: new Date().toISOString() };
|
|
669
|
+
runtime.guideEnvelopes.delete(clientMessageId);
|
|
670
|
+
runtime.guideReceipts.set(clientMessageId, rejected);
|
|
671
|
+
return runtime.runner.recordGuideReceipt(rejected);
|
|
672
|
+
}
|
|
514
673
|
const deferred = { ...accepted, status: 'deferred', updatedAt: new Date().toISOString() };
|
|
515
674
|
runtime.guideReceipts.set(clientMessageId, deferred);
|
|
516
675
|
runtime.runner.recordGuideReceipt(deferred);
|
|
@@ -561,7 +720,7 @@ class ConversationKernel {
|
|
|
561
720
|
async compressContext(target, options = {}) {
|
|
562
721
|
const normalized = this.normalizeTarget(target);
|
|
563
722
|
const runtime = this.findRuntime(normalized);
|
|
564
|
-
if (runtime?.activePromise) {
|
|
723
|
+
if (runtime?.activePromise || runtime?.externalOwner?.active) {
|
|
565
724
|
return { ok: false, error: 'Context compression is unavailable while this conversation is running.' };
|
|
566
725
|
}
|
|
567
726
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
@@ -613,8 +772,7 @@ class ConversationKernel {
|
|
|
613
772
|
const normalized = this.normalizeTarget(target);
|
|
614
773
|
const runtime = this.findRuntime(normalized);
|
|
615
774
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
616
|
-
runner.
|
|
617
|
-
runner.saveWorkspaceConversationState(true);
|
|
775
|
+
runner.selectConversationMode(mode);
|
|
618
776
|
if (runtime)
|
|
619
777
|
runtime.options.mode = mode;
|
|
620
778
|
return runner.mode;
|
|
@@ -625,7 +783,9 @@ class ConversationKernel {
|
|
|
625
783
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
626
784
|
if (!runtime || !runtime.activePromise) {
|
|
627
785
|
// No Build block is running: the selection applies immediately.
|
|
628
|
-
runner.setModel(model);
|
|
786
|
+
runner.setModel(model, true);
|
|
787
|
+
if (runtime)
|
|
788
|
+
runtime.options.model = runner.modelSelectionValue();
|
|
629
789
|
}
|
|
630
790
|
else {
|
|
631
791
|
// A Build block is running. The in-flight block keeps its current model
|
|
@@ -635,7 +795,7 @@ class ConversationKernel {
|
|
|
635
795
|
runtime.options.model = model;
|
|
636
796
|
}
|
|
637
797
|
runner.saveWorkspaceConversationState(true);
|
|
638
|
-
return runner.
|
|
798
|
+
return runner.modelSelectionValue();
|
|
639
799
|
}
|
|
640
800
|
async toggleGoalPause(target) {
|
|
641
801
|
const normalized = this.normalizeTarget(target);
|
|
@@ -644,7 +804,7 @@ class ConversationKernel {
|
|
|
644
804
|
const runner = this.createRunner(normalized);
|
|
645
805
|
runtime = this.runtime(normalized, {
|
|
646
806
|
mode: runner.mode,
|
|
647
|
-
model: runner.
|
|
807
|
+
model: runner.modelSelectionValue(),
|
|
648
808
|
intelligence: runner.intelligence,
|
|
649
809
|
inputMode: runner.inputMode,
|
|
650
810
|
engine: runner.engine,
|
|
@@ -658,6 +818,18 @@ class ConversationKernel {
|
|
|
658
818
|
this.mirrorHostIfTargetActive(runtime);
|
|
659
819
|
return paused;
|
|
660
820
|
}
|
|
821
|
+
updateGoal(target, objective) {
|
|
822
|
+
const normalized = this.normalizeTarget(target);
|
|
823
|
+
const runtime = this.findRuntime(normalized);
|
|
824
|
+
if (runtime?.activePromise || runtime?.externalOwner?.active)
|
|
825
|
+
throw new Error('Use Guide to change the Goal of a running conversation');
|
|
826
|
+
const runner = runtime?.runner || this.createRunner(normalized);
|
|
827
|
+
runner.updateGoal(objective);
|
|
828
|
+
runner.selectConversationMode('goal');
|
|
829
|
+
if (runtime)
|
|
830
|
+
runtime.options.mode = 'goal';
|
|
831
|
+
return runner.getConversationSnapshot(normalized.conversationId).goal;
|
|
832
|
+
}
|
|
661
833
|
clearGoal(target) {
|
|
662
834
|
const normalized = this.normalizeTarget(target);
|
|
663
835
|
const runtime = this.findRuntime(normalized);
|
|
@@ -688,7 +860,7 @@ class ConversationKernel {
|
|
|
688
860
|
runtimeKey: runtime.runtimeKey,
|
|
689
861
|
runId: runtime.runId,
|
|
690
862
|
generation: runtime.generation,
|
|
691
|
-
running: !!runtime.activePromise,
|
|
863
|
+
running: !!runtime.activePromise || runtime.externalOwner?.active === true,
|
|
692
864
|
stopRequested: !!runtime.runId && runtime.stopRequestedRunId === runtime.runId,
|
|
693
865
|
workRuns: this.bindWorkRunsToRuntimeTarget(runtime.runner.getConversationSnapshot(runtime.id).workRuns, runtime.target),
|
|
694
866
|
};
|
|
@@ -697,16 +869,20 @@ class ConversationKernel {
|
|
|
697
869
|
const normalized = this.normalizeTarget(target);
|
|
698
870
|
const runtime = this.findRuntime(target);
|
|
699
871
|
const runtimeKey = runtime?.runtimeKey || normalized.runtimeKey;
|
|
700
|
-
|
|
872
|
+
// A stale stop cannot pause another generation's peer scheduler or emit
|
|
873
|
+
// control messages before its target identity has been checked.
|
|
874
|
+
if (runtime && expectedRunId && expectedRunId !== runtime.runId) {
|
|
875
|
+
return { action: 'stale', runtimeKey, runId: runtime.runId, generation: runtime.generation, checkpointed: false };
|
|
876
|
+
}
|
|
877
|
+
const peerWork = !!runtime?.runner.subagents.hasPendingWork();
|
|
878
|
+
if (runtime && !runtime.activePromise && !runtime.runner.hasRunningSubagents() && runtime.runId && runtime.stopRequestedRunId === runtime.runId) {
|
|
701
879
|
this.settleCooperativeStop(runtime, runtime.runId);
|
|
702
880
|
}
|
|
703
|
-
if (!runtime
|
|
881
|
+
if (!runtime || (!runtime.activePromise && !peerWork) || !runtime.runId) {
|
|
704
882
|
return { action: 'not_running', runtimeKey, runId: runtime?.runId || undefined, generation: runtime?.generation || undefined, checkpointed: false };
|
|
705
883
|
}
|
|
706
|
-
if (expectedRunId && expectedRunId !== runtime.runId) {
|
|
707
|
-
return { action: 'stale', runtimeKey, runId: runtime.runId, generation: runtime.generation, checkpointed: false };
|
|
708
|
-
}
|
|
709
884
|
if (runtime.stopRequestedRunId === runtime.runId && runtime.forceStopArmedRunId === runtime.runId) {
|
|
885
|
+
runtime.runner.abortActiveKernelRun('force_stop');
|
|
710
886
|
runtime.runner.emitWorkEvent({
|
|
711
887
|
type: 'status',
|
|
712
888
|
content: 'Force stopping this conversation run.',
|
|
@@ -728,7 +904,7 @@ class ConversationKernel {
|
|
|
728
904
|
runtime.runner.abortActiveKernelRun('user_stop');
|
|
729
905
|
runtime.runner.emitWorkEvent({
|
|
730
906
|
type: 'status',
|
|
731
|
-
content: 'Stop requested. Saving progress and interrupting
|
|
907
|
+
content: 'Stop requested for this conversation and all of its subagents. Saving progress and interrupting execution.',
|
|
732
908
|
status: 'stopping',
|
|
733
909
|
runId: runtime.runId,
|
|
734
910
|
});
|
|
@@ -751,6 +927,12 @@ class ConversationKernel {
|
|
|
751
927
|
runtime.unsubscribePeer();
|
|
752
928
|
if (runtime?.unsubscribeRootInboxWake)
|
|
753
929
|
runtime.unsubscribeRootInboxWake();
|
|
930
|
+
runtime?.unsubscribeUserMessageStart?.();
|
|
931
|
+
if (runtime) {
|
|
932
|
+
runtime.preparingArchive = true;
|
|
933
|
+
if (runtime.goalContinuationTimer)
|
|
934
|
+
clearTimeout(runtime.goalContinuationTimer);
|
|
935
|
+
}
|
|
754
936
|
if (runtime)
|
|
755
937
|
this.runtimes.delete(runtime.runtimeKey);
|
|
756
938
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
@@ -759,6 +941,8 @@ class ConversationKernel {
|
|
|
759
941
|
async prompt(message, target, options, queueMode = 'followUp') {
|
|
760
942
|
const normalized = this.normalizeTarget(target);
|
|
761
943
|
const active = this.findRuntime(normalized);
|
|
944
|
+
if (active?.preparingArchive)
|
|
945
|
+
throw new Error('This conversation is being archived');
|
|
762
946
|
if (active?.activePromise) {
|
|
763
947
|
// A Build block is already running: queue this message. Queued messages
|
|
764
948
|
// carry no send-time model/mode; the running block keeps its settings and
|
|
@@ -819,7 +1003,7 @@ class ConversationKernel {
|
|
|
819
1003
|
stopped = true;
|
|
820
1004
|
this.settleCooperativeStop(runtime, runId);
|
|
821
1005
|
}
|
|
822
|
-
else if (!failed && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
|
|
1006
|
+
else if (!failed && !runtime.preparingArchive && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
|
|
823
1007
|
// A renderer/IPC Guide can arrive after the final-drain barrier's
|
|
824
1008
|
// last check but before this promise settles. Do not leave the
|
|
825
1009
|
// deferred continuation queued on an idle runtime.
|
|
@@ -827,7 +1011,7 @@ class ConversationKernel {
|
|
|
827
1011
|
}
|
|
828
1012
|
}
|
|
829
1013
|
}
|
|
830
|
-
if (!failed && !stopped && runtime.runId === runId)
|
|
1014
|
+
if (!failed && !stopped && !runtime.preparingArchive && runtime.runId === runId)
|
|
831
1015
|
this.scheduleGoalContinuation(runtime, runId);
|
|
832
1016
|
if (stopped) {
|
|
833
1017
|
const settled = this.result(runtime, []);
|
|
@@ -928,7 +1112,14 @@ class ConversationKernel {
|
|
|
928
1112
|
}
|
|
929
1113
|
else {
|
|
930
1114
|
const drained = this.drainQueuedFollowUpMessage(next.message);
|
|
931
|
-
lastTokens = await this.
|
|
1115
|
+
lastTokens = await this.runPendingQueueItem(runtime, next, async () => {
|
|
1116
|
+
if (typeof drained !== 'string' && drained.visibleMode && ['build', 'chat', 'plan', 'goal'].includes(drained.visibleMode)) {
|
|
1117
|
+
runtime.runner.setMode(drained.visibleMode);
|
|
1118
|
+
runtime.options.mode = drained.visibleMode;
|
|
1119
|
+
}
|
|
1120
|
+
this.activateAcceptedGoal(runtime, typeof drained === 'string' ? '' : drained.goalObjective);
|
|
1121
|
+
return this.runSingle(runtime, drained, next.queueMode, typeof next.message === 'string' ? undefined : next.message.clientMessageId);
|
|
1122
|
+
});
|
|
932
1123
|
if (this.repeatedAutomaticAssistant(runtime, drained, next.queueMode))
|
|
933
1124
|
break;
|
|
934
1125
|
}
|
|
@@ -1021,32 +1212,28 @@ class ConversationKernel {
|
|
|
1021
1212
|
runtime.runner.setModel(pending);
|
|
1022
1213
|
runtime.options.model = runtime.runner.modelSelectionValue();
|
|
1023
1214
|
}
|
|
1024
|
-
async runSingle(runtime, message, continuationMode) {
|
|
1025
|
-
|
|
1026
|
-
|
|
1215
|
+
async runSingle(runtime, message, continuationMode, continuationClientMessageId) {
|
|
1216
|
+
// Display payloads intentionally omit queue identity. Keep the exact
|
|
1217
|
+
// durable owner separately so equal text cannot consume another item.
|
|
1218
|
+
const clientMessageId = continuationClientMessageId || (typeof message === 'string' ? undefined : message.clientMessageId || message.userMessageId);
|
|
1219
|
+
const durable = clientMessageId
|
|
1220
|
+
? runtime.runner.conversationContinuations().find(item => item.clientMessageId === clientMessageId)
|
|
1221
|
+
: undefined;
|
|
1222
|
+
const consumedMode = continuationMode || durable?.queueMode;
|
|
1223
|
+
const text = typeof message === 'string' ? message : message.text;
|
|
1224
|
+
let accepted = false;
|
|
1225
|
+
const unsubscribe = durable?.queueMode === 'followUp' && clientMessageId
|
|
1226
|
+
? runtime.runner.subscribeAgentKernelUserMessageStart((content, acceptedId) => {
|
|
1227
|
+
if (acceptedId ? acceptedId === clientMessageId : content === text)
|
|
1228
|
+
accepted = true;
|
|
1229
|
+
}) : undefined;
|
|
1027
1230
|
const timeoutMs = this.processTimeoutMs(runtime);
|
|
1028
|
-
if (timeoutMs <= 0) {
|
|
1029
|
-
let tokens;
|
|
1030
|
-
try {
|
|
1031
|
-
tokens = await runtime.runner.process(message);
|
|
1032
|
-
}
|
|
1033
|
-
catch (error) {
|
|
1034
|
-
this.consumeFailedAutomaticContinuation(runtime, message, continuationMode);
|
|
1035
|
-
throw error;
|
|
1036
|
-
}
|
|
1037
|
-
if (continuationMode)
|
|
1038
|
-
runtime.runner.consumeConversationContinuation({
|
|
1039
|
-
content: typeof message === 'string' ? message : message.text,
|
|
1040
|
-
queueMode: continuationMode,
|
|
1041
|
-
clientMessageId: typeof message === 'string' ? undefined : message.clientMessageId,
|
|
1042
|
-
});
|
|
1043
|
-
return tokens;
|
|
1044
|
-
}
|
|
1045
1231
|
let timeout;
|
|
1046
1232
|
try {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1233
|
+
this.syncPendingModel(runtime);
|
|
1234
|
+
this.consumeQueuedMessage(runtime, text);
|
|
1235
|
+
const tokens = timeoutMs <= 0 ? await runtime.runner.process(message)
|
|
1236
|
+
: await Promise.race([
|
|
1050
1237
|
runtime.runner.process(message),
|
|
1051
1238
|
new Promise((_, reject) => {
|
|
1052
1239
|
timeout = setTimeout(() => {
|
|
@@ -1065,22 +1252,27 @@ class ConversationKernel {
|
|
|
1065
1252
|
}, timeoutMs);
|
|
1066
1253
|
}),
|
|
1067
1254
|
]);
|
|
1255
|
+
if (unsubscribe && !accepted) {
|
|
1256
|
+
// Workspace/attachment rejection can resolve error tokens without
|
|
1257
|
+
// accepting input. Resolution alone must not remove a queued row.
|
|
1258
|
+
throw new Error('Queued input was not accepted; it remains paused for correction or retry.');
|
|
1068
1259
|
}
|
|
1069
|
-
|
|
1070
|
-
this.consumeFailedAutomaticContinuation(runtime, message, continuationMode);
|
|
1071
|
-
throw error;
|
|
1072
|
-
}
|
|
1073
|
-
if (continuationMode)
|
|
1260
|
+
if (consumedMode)
|
|
1074
1261
|
runtime.runner.consumeConversationContinuation({
|
|
1075
1262
|
content: typeof message === 'string' ? message : message.text,
|
|
1076
|
-
queueMode:
|
|
1077
|
-
clientMessageId
|
|
1263
|
+
queueMode: consumedMode,
|
|
1264
|
+
clientMessageId,
|
|
1078
1265
|
});
|
|
1079
1266
|
return tokens;
|
|
1080
1267
|
}
|
|
1268
|
+
catch (error) {
|
|
1269
|
+
this.consumeFailedAutomaticContinuation(runtime, message, continuationMode);
|
|
1270
|
+
throw error;
|
|
1271
|
+
}
|
|
1081
1272
|
finally {
|
|
1082
1273
|
if (timeout)
|
|
1083
1274
|
clearTimeout(timeout);
|
|
1275
|
+
unsubscribe?.();
|
|
1084
1276
|
}
|
|
1085
1277
|
}
|
|
1086
1278
|
consumeFailedAutomaticContinuation(runtime, message, continuationMode) {
|
|
@@ -1141,13 +1333,18 @@ class ConversationKernel {
|
|
|
1141
1333
|
});
|
|
1142
1334
|
for (const continuation of runner.conversationContinuations()) {
|
|
1143
1335
|
runtime.pendingNextTurn.push({
|
|
1144
|
-
message: continuation.clientMessageId || continuation.images?.length
|
|
1336
|
+
message: continuation.clientMessageId || continuation.images?.length || continuation.hiddenUserInput
|
|
1145
1337
|
? {
|
|
1146
1338
|
text: continuation.content,
|
|
1147
1339
|
images: continuation.images,
|
|
1148
1340
|
attachments: continuation.attachments,
|
|
1149
1341
|
clientMessageId: continuation.clientMessageId,
|
|
1150
1342
|
runId: continuation.runId,
|
|
1343
|
+
visibleUserInput: continuation.visibleUserInput,
|
|
1344
|
+
visibleMode: continuation.visibleMode,
|
|
1345
|
+
goalObjective: continuation.goalObjective,
|
|
1346
|
+
createdAt: continuation.createdAt,
|
|
1347
|
+
hiddenUserInput: continuation.hiddenUserInput,
|
|
1151
1348
|
}
|
|
1152
1349
|
: continuation.content,
|
|
1153
1350
|
queueMode: continuation.queueMode,
|
|
@@ -1155,6 +1352,8 @@ class ConversationKernel {
|
|
|
1155
1352
|
this.trackQueuedMessage(runtime, continuation.content, continuation.queueMode);
|
|
1156
1353
|
}
|
|
1157
1354
|
runtime.unsubscribe = runner.subscribeWorkEvents(event => {
|
|
1355
|
+
if (runtime.externalOwner?.active && event.runId)
|
|
1356
|
+
runtime.runId = event.runId;
|
|
1158
1357
|
const routedEvent = {
|
|
1159
1358
|
...event,
|
|
1160
1359
|
conversationId: runtime.target.conversationId,
|
|
@@ -1177,7 +1376,7 @@ class ConversationKernel {
|
|
|
1177
1376
|
for (const listener of this.listeners)
|
|
1178
1377
|
listener(event);
|
|
1179
1378
|
});
|
|
1180
|
-
runner.subscribeAgentKernelUserMessageStart((content, clientMessageId) => {
|
|
1379
|
+
runtime.unsubscribeUserMessageStart = runner.subscribeAgentKernelUserMessageStart((content, clientMessageId) => {
|
|
1181
1380
|
this.consumeQueuedMessage(runtime, content);
|
|
1182
1381
|
if (!clientMessageId)
|
|
1183
1382
|
return;
|
|
@@ -1194,11 +1393,26 @@ class ConversationKernel {
|
|
|
1194
1393
|
runtime.runner.recordGuideReceipt(applied);
|
|
1195
1394
|
runtime.guideEnvelopes.delete(clientMessageId);
|
|
1196
1395
|
});
|
|
1197
|
-
runtime.unsubscribeRootInboxWake = runner.subscribeRootInboxWake(message => {
|
|
1198
|
-
this.enqueueRootInboxWake(runtime, message);
|
|
1396
|
+
runtime.unsubscribeRootInboxWake = runner.subscribeRootInboxWake((message, options) => {
|
|
1397
|
+
this.enqueueRootInboxWake(runtime, message, options?.wakeup !== false);
|
|
1199
1398
|
return true;
|
|
1399
|
+
}, ids => {
|
|
1400
|
+
const retired = new Set(ids);
|
|
1401
|
+
const before = runtime.pendingNextTurn.length;
|
|
1402
|
+
runtime.pendingNextTurn = runtime.pendingNextTurn.filter(item => {
|
|
1403
|
+
if (item.queueMode !== 'followUp' || typeof item.message === 'string'
|
|
1404
|
+
|| !item.message.hiddenUserInput || item.message.clientMessageId)
|
|
1405
|
+
return true;
|
|
1406
|
+
const id = item.message.text.match(/^\[Root subagent inbox id=([0-9a-f-]{36})\b/i)?.[1];
|
|
1407
|
+
return !id || !retired.has(id);
|
|
1408
|
+
});
|
|
1409
|
+
if (runtime.pendingNextTurn.length !== before)
|
|
1410
|
+
this.emitQueueUpdate(runtime);
|
|
1200
1411
|
});
|
|
1201
1412
|
this.runtimes.set(target.runtimeKey, runtime);
|
|
1413
|
+
// An already-read inbox entry is not replayed by the mailbox listener.
|
|
1414
|
+
// Reconcile saved result receipts after hydrating its continuation copy.
|
|
1415
|
+
runner.acknowledgeSubagentSettlementReceipts();
|
|
1202
1416
|
return runtime;
|
|
1203
1417
|
}
|
|
1204
1418
|
scheduleGoalContinuation(runtime, completedRunId) {
|
|
@@ -1207,7 +1421,7 @@ class ConversationKernel {
|
|
|
1207
1421
|
runtime.runner.flushWorkspaceConversationState();
|
|
1208
1422
|
runtime.goalContinuationTimer = setTimeout(() => {
|
|
1209
1423
|
runtime.goalContinuationTimer = undefined;
|
|
1210
|
-
if (runtime.runId !== completedRunId || runtime.activePromise)
|
|
1424
|
+
if (runtime.preparingArchive || runtime.runId !== completedRunId || runtime.activePromise)
|
|
1211
1425
|
return;
|
|
1212
1426
|
this.queueState(runtime);
|
|
1213
1427
|
if (runtime.pendingNextTurn.length > 0
|
|
@@ -1238,19 +1452,64 @@ class ConversationKernel {
|
|
|
1238
1452
|
}
|
|
1239
1453
|
setImmediate(() => {
|
|
1240
1454
|
runtime.pendingContinuationRunId = undefined;
|
|
1241
|
-
if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused)
|
|
1455
|
+
if (runtime.preparingArchive || runtime.runId !== runId || runtime.activePromise || runtime.externalOwner || runtime.stopRequestedRunId === runId || runtime.queuePaused)
|
|
1242
1456
|
return;
|
|
1243
1457
|
const next = runtime.pendingNextTurn.shift();
|
|
1244
1458
|
if (!next)
|
|
1245
1459
|
return;
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1460
|
+
const delivery = next.queueMode === 'followUp' ? this.drainQueuedFollowUpMessage(next.message) : next.message;
|
|
1461
|
+
const message = typeof delivery === 'string'
|
|
1462
|
+
? { text: delivery, runId }
|
|
1463
|
+
: { ...delivery, runId: delivery.runId || runId };
|
|
1464
|
+
void this.runPendingQueueItem(runtime, next, async () => {
|
|
1465
|
+
if (message.visibleMode && ['build', 'chat', 'plan', 'goal'].includes(message.visibleMode)) {
|
|
1466
|
+
this.setMode(runtime.target, message.visibleMode);
|
|
1467
|
+
}
|
|
1468
|
+
return this.prompt(message, runtime.target, runtime.options, next.queueMode);
|
|
1469
|
+
}).catch(() => {
|
|
1250
1470
|
// Agent.process and the work-run finalizer already publish the error.
|
|
1251
1471
|
});
|
|
1252
1472
|
});
|
|
1253
1473
|
}
|
|
1474
|
+
async runPendingQueueItem(runtime, next, execute) {
|
|
1475
|
+
const queuedMessage = typeof next.message === 'string' ? undefined : next.message;
|
|
1476
|
+
const id = queuedMessage?.clientMessageId;
|
|
1477
|
+
if (next.queueMode !== 'followUp' || !id)
|
|
1478
|
+
return execute();
|
|
1479
|
+
let accepted = false;
|
|
1480
|
+
const unsubscribe = runtime.runner.subscribeAgentKernelUserMessageStart((content, clientMessageId) => {
|
|
1481
|
+
// Normal in-run dequeues omit display identity, but retain the exact
|
|
1482
|
+
// execution text. Never let an unrelated Guide accept this queue row.
|
|
1483
|
+
if (clientMessageId ? clientMessageId === id : content === queuedMessage.text)
|
|
1484
|
+
accepted = true;
|
|
1485
|
+
});
|
|
1486
|
+
try {
|
|
1487
|
+
return await execute();
|
|
1488
|
+
}
|
|
1489
|
+
catch (error) {
|
|
1490
|
+
// Model/title/setup failures before user-message acceptance must leave
|
|
1491
|
+
// the same row manageable. A provider failure after acceptance must
|
|
1492
|
+
// never replay the already submitted user input after restart/resume.
|
|
1493
|
+
runtime.queuePaused = true;
|
|
1494
|
+
throw error;
|
|
1495
|
+
}
|
|
1496
|
+
finally {
|
|
1497
|
+
unsubscribe();
|
|
1498
|
+
if (accepted)
|
|
1499
|
+
runtime.runner.consumeConversationContinuation({
|
|
1500
|
+
content: queuedMessage.text, clientMessageId: id, queueMode: 'followUp',
|
|
1501
|
+
});
|
|
1502
|
+
else if (runtime.runner.conversationContinuations().some(item => item.clientMessageId === id && item.queueMode === 'followUp')) {
|
|
1503
|
+
// Cooperative Stop can turn a rejected process into a resolved outer
|
|
1504
|
+
// result. Restore on every unaccepted exit, including failed archive;
|
|
1505
|
+
// the pause/archive guards still prevent any automatic execution.
|
|
1506
|
+
if (!runtime.pendingNextTurn.some(item => typeof item.message !== 'string' && item.message.clientMessageId === id))
|
|
1507
|
+
runtime.pendingNextTurn.unshift(next);
|
|
1508
|
+
runtime.queuePaused = true;
|
|
1509
|
+
}
|
|
1510
|
+
this.emitQueueUpdate(runtime);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1254
1513
|
startGoalDrivenBuild(runtime) {
|
|
1255
1514
|
if (runtime.goalContinuationTimer) {
|
|
1256
1515
|
clearTimeout(runtime.goalContinuationTimer);
|
|
@@ -1294,17 +1553,22 @@ class ConversationKernel {
|
|
|
1294
1553
|
runner.ensureConversationSnapshot(target.conversationId);
|
|
1295
1554
|
return runner;
|
|
1296
1555
|
}
|
|
1297
|
-
enqueueRootInboxWake(runtime, message) {
|
|
1556
|
+
enqueueRootInboxWake(runtime, message, wakeup = false) {
|
|
1557
|
+
const acceptedWhileActive = !!runtime.activePromise;
|
|
1298
1558
|
queueMicrotask(() => {
|
|
1299
1559
|
const rootInboxId = message.match(/^\[Root subagent inbox id=([0-9a-f-]{36})\b/i)?.[1];
|
|
1300
1560
|
if (rootInboxId && !runtime.runner.subagents.readRootInbox().some(item => item.id === rootInboxId))
|
|
1301
1561
|
return;
|
|
1562
|
+
if (runtime.queuePaused || runtime.runner.subagents.isSchedulingPaused())
|
|
1563
|
+
return;
|
|
1302
1564
|
if (runtime.activePromise) {
|
|
1303
1565
|
if (!runtime.pendingNextTurn.some(item => typeof item.message === 'string' ? item.message === message : item.message.text === message)) {
|
|
1304
1566
|
runtime.pendingNextTurn.push({ message: { text: message, hiddenUserInput: true }, queueMode: 'followUp' });
|
|
1305
1567
|
}
|
|
1306
1568
|
return;
|
|
1307
1569
|
}
|
|
1570
|
+
if (!wakeup && !acceptedWhileActive)
|
|
1571
|
+
return;
|
|
1308
1572
|
void this.prompt({ text: message, hiddenUserInput: true }, runtime.target, runtime.options, 'followUp').catch(error => {
|
|
1309
1573
|
runtime.runner.recordWorkStatus(`Subagent result follow-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1310
1574
|
});
|
|
@@ -1315,7 +1579,12 @@ class ConversationKernel {
|
|
|
1315
1579
|
// Host-global state can belong to a different foreground conversation.
|
|
1316
1580
|
const restoredMode = agent.getConversationSnapshot(agent.activeConversationId).mode;
|
|
1317
1581
|
agent.setMode(restoredMode || options.mode);
|
|
1318
|
-
|
|
1582
|
+
// Older clients may echo a bare display name from a snapshot. Preserve
|
|
1583
|
+
// an already restored exact deployment in that case; never guess between
|
|
1584
|
+
// providers for an unbound or different ambiguous model name.
|
|
1585
|
+
if (options.model !== agent.model || !agent.activeDeployment()) {
|
|
1586
|
+
agent.setModel(options.model);
|
|
1587
|
+
}
|
|
1319
1588
|
agent.setIntelligence(options.intelligence);
|
|
1320
1589
|
agent.inputMode = options.inputMode;
|
|
1321
1590
|
agent.engine = options.engine;
|
|
@@ -1341,7 +1610,7 @@ class ConversationKernel {
|
|
|
1341
1610
|
newContent: d.newContent,
|
|
1342
1611
|
})),
|
|
1343
1612
|
mode: runtime.runner.mode,
|
|
1344
|
-
model: runtime.runner.
|
|
1613
|
+
model: runtime.runner.modelSelectionValue(),
|
|
1345
1614
|
status: runtime.runner.status,
|
|
1346
1615
|
goal: runtime.runner.goal ? { objective: runtime.runner.goal.objective, paused: runtime.runner.goal.paused } : null,
|
|
1347
1616
|
options: runtime.runner.pendingOptions,
|
|
@@ -1427,6 +1696,9 @@ class ConversationKernel {
|
|
|
1427
1696
|
images: queuedMessage.images?.map(image => ({ ...image })),
|
|
1428
1697
|
attachments: queuedMessage.attachments?.map(attachment => ({ ...attachment })),
|
|
1429
1698
|
createdAt: queuedMessage.createdAt,
|
|
1699
|
+
visibleUserInput: queuedMessage.visibleUserInput,
|
|
1700
|
+
visibleMode: queuedMessage.visibleMode,
|
|
1701
|
+
goalObjective: queuedMessage.goalObjective,
|
|
1430
1702
|
}]);
|
|
1431
1703
|
this.trackQueuedMessage(runtime, prompt, 'followUp');
|
|
1432
1704
|
runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId
|
|
@@ -1475,13 +1747,13 @@ class ConversationKernel {
|
|
|
1475
1747
|
}
|
|
1476
1748
|
trackQueuedMessage(runtime, message, queueMode) {
|
|
1477
1749
|
this.queueState(runtime);
|
|
1478
|
-
|
|
1479
|
-
|
|
1750
|
+
if (queueMode === 'steer')
|
|
1751
|
+
runtime.queued.steering.push(message);
|
|
1480
1752
|
}
|
|
1481
1753
|
consumeQueuedMessage(runtime, message) {
|
|
1482
1754
|
this.queueState(runtime);
|
|
1483
1755
|
let changed = false;
|
|
1484
|
-
for (const key of ['steering'
|
|
1756
|
+
for (const key of ['steering']) {
|
|
1485
1757
|
const index = runtime.queued[key].indexOf(message);
|
|
1486
1758
|
if (index >= 0) {
|
|
1487
1759
|
runtime.queued[key].splice(index, 1);
|
|
@@ -1491,13 +1763,6 @@ class ConversationKernel {
|
|
|
1491
1763
|
if (changed)
|
|
1492
1764
|
this.emitQueueUpdate(runtime);
|
|
1493
1765
|
}
|
|
1494
|
-
replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
|
|
1495
|
-
this.queueState(runtime);
|
|
1496
|
-
const list = queueMode === 'steer' ? runtime.queued.steering : runtime.queued.followUp;
|
|
1497
|
-
const index = list.indexOf(oldMessage);
|
|
1498
|
-
if (index >= 0)
|
|
1499
|
-
list[index] = nextMessage;
|
|
1500
|
-
}
|
|
1501
1766
|
emitQueueUpdate(runtime) {
|
|
1502
1767
|
this.queueState(runtime);
|
|
1503
1768
|
runtime.runner.emitWorkEvent({
|
|
@@ -1524,8 +1789,14 @@ class ConversationKernel {
|
|
|
1524
1789
|
runtime.queued = { steering: [], followUp: [] };
|
|
1525
1790
|
if (!Array.isArray(runtime.queued.steering))
|
|
1526
1791
|
runtime.queued.steering = [];
|
|
1527
|
-
|
|
1528
|
-
|
|
1792
|
+
// Follow-up order belongs to the pending entries, not a second text-only
|
|
1793
|
+
// ledger. Repeated text, edits and user-message-start callbacks must not
|
|
1794
|
+
// independently remove/reorder that projection. Internal auto-continuations
|
|
1795
|
+
// without user identity remain outside the visible user queue.
|
|
1796
|
+
runtime.queued.followUp = runtime.pendingNextTurn
|
|
1797
|
+
.filter(item => item.queueMode === 'followUp' && (typeof item.message === 'string'
|
|
1798
|
+
|| item.message.hiddenUserInput !== true || !!item.message.clientMessageId))
|
|
1799
|
+
.map(item => typeof item.message === 'string' ? item.message : item.message.text);
|
|
1529
1800
|
return runtime;
|
|
1530
1801
|
}
|
|
1531
1802
|
normalizeTarget(input) {
|