newmark-agent 0.5.14 → 0.5.15
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 +3373 -1900
- package/dist/conversation-utility-host.js +1 -1
- package/dist/core/agent.d.ts +105 -14
- package/dist/core/agent.js +658 -104
- 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 +236 -85
- package/dist/core/autoRouter.js +16 -3
- 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 +347 -83
- 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/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 +32 -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 +148 -267
- 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 +2374 -616
- package/dist/ui/lucide-sprite.svg +7 -0
- package/dist/ui/startup.html +6 -6
- package/dist/wsl-agent-host.bundle.cjs +3381 -1906
- 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.model, 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.model, 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),
|
|
@@ -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;
|
|
@@ -658,6 +816,18 @@ class ConversationKernel {
|
|
|
658
816
|
this.mirrorHostIfTargetActive(runtime);
|
|
659
817
|
return paused;
|
|
660
818
|
}
|
|
819
|
+
updateGoal(target, objective) {
|
|
820
|
+
const normalized = this.normalizeTarget(target);
|
|
821
|
+
const runtime = this.findRuntime(normalized);
|
|
822
|
+
if (runtime?.activePromise || runtime?.externalOwner?.active)
|
|
823
|
+
throw new Error('Use Guide to change the Goal of a running conversation');
|
|
824
|
+
const runner = runtime?.runner || this.createRunner(normalized);
|
|
825
|
+
runner.updateGoal(objective);
|
|
826
|
+
runner.selectConversationMode('goal');
|
|
827
|
+
if (runtime)
|
|
828
|
+
runtime.options.mode = 'goal';
|
|
829
|
+
return runner.getConversationSnapshot(normalized.conversationId).goal;
|
|
830
|
+
}
|
|
661
831
|
clearGoal(target) {
|
|
662
832
|
const normalized = this.normalizeTarget(target);
|
|
663
833
|
const runtime = this.findRuntime(normalized);
|
|
@@ -688,7 +858,7 @@ class ConversationKernel {
|
|
|
688
858
|
runtimeKey: runtime.runtimeKey,
|
|
689
859
|
runId: runtime.runId,
|
|
690
860
|
generation: runtime.generation,
|
|
691
|
-
running: !!runtime.activePromise,
|
|
861
|
+
running: !!runtime.activePromise || runtime.externalOwner?.active === true,
|
|
692
862
|
stopRequested: !!runtime.runId && runtime.stopRequestedRunId === runtime.runId,
|
|
693
863
|
workRuns: this.bindWorkRunsToRuntimeTarget(runtime.runner.getConversationSnapshot(runtime.id).workRuns, runtime.target),
|
|
694
864
|
};
|
|
@@ -697,16 +867,20 @@ class ConversationKernel {
|
|
|
697
867
|
const normalized = this.normalizeTarget(target);
|
|
698
868
|
const runtime = this.findRuntime(target);
|
|
699
869
|
const runtimeKey = runtime?.runtimeKey || normalized.runtimeKey;
|
|
700
|
-
|
|
870
|
+
// A stale stop cannot pause another generation's peer scheduler or emit
|
|
871
|
+
// control messages before its target identity has been checked.
|
|
872
|
+
if (runtime && expectedRunId && expectedRunId !== runtime.runId) {
|
|
873
|
+
return { action: 'stale', runtimeKey, runId: runtime.runId, generation: runtime.generation, checkpointed: false };
|
|
874
|
+
}
|
|
875
|
+
const peerWork = !!runtime?.runner.subagents.hasPendingWork();
|
|
876
|
+
if (runtime && !runtime.activePromise && !runtime.runner.hasRunningSubagents() && runtime.runId && runtime.stopRequestedRunId === runtime.runId) {
|
|
701
877
|
this.settleCooperativeStop(runtime, runtime.runId);
|
|
702
878
|
}
|
|
703
|
-
if (!runtime
|
|
879
|
+
if (!runtime || (!runtime.activePromise && !peerWork) || !runtime.runId) {
|
|
704
880
|
return { action: 'not_running', runtimeKey, runId: runtime?.runId || undefined, generation: runtime?.generation || undefined, checkpointed: false };
|
|
705
881
|
}
|
|
706
|
-
if (expectedRunId && expectedRunId !== runtime.runId) {
|
|
707
|
-
return { action: 'stale', runtimeKey, runId: runtime.runId, generation: runtime.generation, checkpointed: false };
|
|
708
|
-
}
|
|
709
882
|
if (runtime.stopRequestedRunId === runtime.runId && runtime.forceStopArmedRunId === runtime.runId) {
|
|
883
|
+
runtime.runner.abortActiveKernelRun('force_stop');
|
|
710
884
|
runtime.runner.emitWorkEvent({
|
|
711
885
|
type: 'status',
|
|
712
886
|
content: 'Force stopping this conversation run.',
|
|
@@ -728,7 +902,7 @@ class ConversationKernel {
|
|
|
728
902
|
runtime.runner.abortActiveKernelRun('user_stop');
|
|
729
903
|
runtime.runner.emitWorkEvent({
|
|
730
904
|
type: 'status',
|
|
731
|
-
content: 'Stop requested. Saving progress and interrupting
|
|
905
|
+
content: 'Stop requested for this conversation and all of its subagents. Saving progress and interrupting execution.',
|
|
732
906
|
status: 'stopping',
|
|
733
907
|
runId: runtime.runId,
|
|
734
908
|
});
|
|
@@ -751,6 +925,12 @@ class ConversationKernel {
|
|
|
751
925
|
runtime.unsubscribePeer();
|
|
752
926
|
if (runtime?.unsubscribeRootInboxWake)
|
|
753
927
|
runtime.unsubscribeRootInboxWake();
|
|
928
|
+
runtime?.unsubscribeUserMessageStart?.();
|
|
929
|
+
if (runtime) {
|
|
930
|
+
runtime.preparingArchive = true;
|
|
931
|
+
if (runtime.goalContinuationTimer)
|
|
932
|
+
clearTimeout(runtime.goalContinuationTimer);
|
|
933
|
+
}
|
|
754
934
|
if (runtime)
|
|
755
935
|
this.runtimes.delete(runtime.runtimeKey);
|
|
756
936
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
@@ -759,6 +939,8 @@ class ConversationKernel {
|
|
|
759
939
|
async prompt(message, target, options, queueMode = 'followUp') {
|
|
760
940
|
const normalized = this.normalizeTarget(target);
|
|
761
941
|
const active = this.findRuntime(normalized);
|
|
942
|
+
if (active?.preparingArchive)
|
|
943
|
+
throw new Error('This conversation is being archived');
|
|
762
944
|
if (active?.activePromise) {
|
|
763
945
|
// A Build block is already running: queue this message. Queued messages
|
|
764
946
|
// carry no send-time model/mode; the running block keeps its settings and
|
|
@@ -819,7 +1001,7 @@ class ConversationKernel {
|
|
|
819
1001
|
stopped = true;
|
|
820
1002
|
this.settleCooperativeStop(runtime, runId);
|
|
821
1003
|
}
|
|
822
|
-
else if (!failed && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
|
|
1004
|
+
else if (!failed && !runtime.preparingArchive && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
|
|
823
1005
|
// A renderer/IPC Guide can arrive after the final-drain barrier's
|
|
824
1006
|
// last check but before this promise settles. Do not leave the
|
|
825
1007
|
// deferred continuation queued on an idle runtime.
|
|
@@ -827,7 +1009,7 @@ class ConversationKernel {
|
|
|
827
1009
|
}
|
|
828
1010
|
}
|
|
829
1011
|
}
|
|
830
|
-
if (!failed && !stopped && runtime.runId === runId)
|
|
1012
|
+
if (!failed && !stopped && !runtime.preparingArchive && runtime.runId === runId)
|
|
831
1013
|
this.scheduleGoalContinuation(runtime, runId);
|
|
832
1014
|
if (stopped) {
|
|
833
1015
|
const settled = this.result(runtime, []);
|
|
@@ -928,7 +1110,14 @@ class ConversationKernel {
|
|
|
928
1110
|
}
|
|
929
1111
|
else {
|
|
930
1112
|
const drained = this.drainQueuedFollowUpMessage(next.message);
|
|
931
|
-
lastTokens = await this.
|
|
1113
|
+
lastTokens = await this.runPendingQueueItem(runtime, next, async () => {
|
|
1114
|
+
if (typeof drained !== 'string' && drained.visibleMode && ['build', 'chat', 'plan', 'goal'].includes(drained.visibleMode)) {
|
|
1115
|
+
runtime.runner.setMode(drained.visibleMode);
|
|
1116
|
+
runtime.options.mode = drained.visibleMode;
|
|
1117
|
+
}
|
|
1118
|
+
this.activateAcceptedGoal(runtime, typeof drained === 'string' ? '' : drained.goalObjective);
|
|
1119
|
+
return this.runSingle(runtime, drained, next.queueMode, typeof next.message === 'string' ? undefined : next.message.clientMessageId);
|
|
1120
|
+
});
|
|
932
1121
|
if (this.repeatedAutomaticAssistant(runtime, drained, next.queueMode))
|
|
933
1122
|
break;
|
|
934
1123
|
}
|
|
@@ -1021,32 +1210,28 @@ class ConversationKernel {
|
|
|
1021
1210
|
runtime.runner.setModel(pending);
|
|
1022
1211
|
runtime.options.model = runtime.runner.modelSelectionValue();
|
|
1023
1212
|
}
|
|
1024
|
-
async runSingle(runtime, message, continuationMode) {
|
|
1025
|
-
|
|
1026
|
-
|
|
1213
|
+
async runSingle(runtime, message, continuationMode, continuationClientMessageId) {
|
|
1214
|
+
// Display payloads intentionally omit queue identity. Keep the exact
|
|
1215
|
+
// durable owner separately so equal text cannot consume another item.
|
|
1216
|
+
const clientMessageId = continuationClientMessageId || (typeof message === 'string' ? undefined : message.clientMessageId || message.userMessageId);
|
|
1217
|
+
const durable = clientMessageId
|
|
1218
|
+
? runtime.runner.conversationContinuations().find(item => item.clientMessageId === clientMessageId)
|
|
1219
|
+
: undefined;
|
|
1220
|
+
const consumedMode = continuationMode || durable?.queueMode;
|
|
1221
|
+
const text = typeof message === 'string' ? message : message.text;
|
|
1222
|
+
let accepted = false;
|
|
1223
|
+
const unsubscribe = durable?.queueMode === 'followUp' && clientMessageId
|
|
1224
|
+
? runtime.runner.subscribeAgentKernelUserMessageStart((content, acceptedId) => {
|
|
1225
|
+
if (acceptedId ? acceptedId === clientMessageId : content === text)
|
|
1226
|
+
accepted = true;
|
|
1227
|
+
}) : undefined;
|
|
1027
1228
|
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
1229
|
let timeout;
|
|
1046
1230
|
try {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1231
|
+
this.syncPendingModel(runtime);
|
|
1232
|
+
this.consumeQueuedMessage(runtime, text);
|
|
1233
|
+
const tokens = timeoutMs <= 0 ? await runtime.runner.process(message)
|
|
1234
|
+
: await Promise.race([
|
|
1050
1235
|
runtime.runner.process(message),
|
|
1051
1236
|
new Promise((_, reject) => {
|
|
1052
1237
|
timeout = setTimeout(() => {
|
|
@@ -1065,22 +1250,27 @@ class ConversationKernel {
|
|
|
1065
1250
|
}, timeoutMs);
|
|
1066
1251
|
}),
|
|
1067
1252
|
]);
|
|
1253
|
+
if (unsubscribe && !accepted) {
|
|
1254
|
+
// Workspace/attachment rejection can resolve error tokens without
|
|
1255
|
+
// accepting input. Resolution alone must not remove a queued row.
|
|
1256
|
+
throw new Error('Queued input was not accepted; it remains paused for correction or retry.');
|
|
1068
1257
|
}
|
|
1069
|
-
|
|
1070
|
-
this.consumeFailedAutomaticContinuation(runtime, message, continuationMode);
|
|
1071
|
-
throw error;
|
|
1072
|
-
}
|
|
1073
|
-
if (continuationMode)
|
|
1258
|
+
if (consumedMode)
|
|
1074
1259
|
runtime.runner.consumeConversationContinuation({
|
|
1075
1260
|
content: typeof message === 'string' ? message : message.text,
|
|
1076
|
-
queueMode:
|
|
1077
|
-
clientMessageId
|
|
1261
|
+
queueMode: consumedMode,
|
|
1262
|
+
clientMessageId,
|
|
1078
1263
|
});
|
|
1079
1264
|
return tokens;
|
|
1080
1265
|
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
this.consumeFailedAutomaticContinuation(runtime, message, continuationMode);
|
|
1268
|
+
throw error;
|
|
1269
|
+
}
|
|
1081
1270
|
finally {
|
|
1082
1271
|
if (timeout)
|
|
1083
1272
|
clearTimeout(timeout);
|
|
1273
|
+
unsubscribe?.();
|
|
1084
1274
|
}
|
|
1085
1275
|
}
|
|
1086
1276
|
consumeFailedAutomaticContinuation(runtime, message, continuationMode) {
|
|
@@ -1141,13 +1331,18 @@ class ConversationKernel {
|
|
|
1141
1331
|
});
|
|
1142
1332
|
for (const continuation of runner.conversationContinuations()) {
|
|
1143
1333
|
runtime.pendingNextTurn.push({
|
|
1144
|
-
message: continuation.clientMessageId || continuation.images?.length
|
|
1334
|
+
message: continuation.clientMessageId || continuation.images?.length || continuation.hiddenUserInput
|
|
1145
1335
|
? {
|
|
1146
1336
|
text: continuation.content,
|
|
1147
1337
|
images: continuation.images,
|
|
1148
1338
|
attachments: continuation.attachments,
|
|
1149
1339
|
clientMessageId: continuation.clientMessageId,
|
|
1150
1340
|
runId: continuation.runId,
|
|
1341
|
+
visibleUserInput: continuation.visibleUserInput,
|
|
1342
|
+
visibleMode: continuation.visibleMode,
|
|
1343
|
+
goalObjective: continuation.goalObjective,
|
|
1344
|
+
createdAt: continuation.createdAt,
|
|
1345
|
+
hiddenUserInput: continuation.hiddenUserInput,
|
|
1151
1346
|
}
|
|
1152
1347
|
: continuation.content,
|
|
1153
1348
|
queueMode: continuation.queueMode,
|
|
@@ -1155,6 +1350,8 @@ class ConversationKernel {
|
|
|
1155
1350
|
this.trackQueuedMessage(runtime, continuation.content, continuation.queueMode);
|
|
1156
1351
|
}
|
|
1157
1352
|
runtime.unsubscribe = runner.subscribeWorkEvents(event => {
|
|
1353
|
+
if (runtime.externalOwner?.active && event.runId)
|
|
1354
|
+
runtime.runId = event.runId;
|
|
1158
1355
|
const routedEvent = {
|
|
1159
1356
|
...event,
|
|
1160
1357
|
conversationId: runtime.target.conversationId,
|
|
@@ -1177,7 +1374,7 @@ class ConversationKernel {
|
|
|
1177
1374
|
for (const listener of this.listeners)
|
|
1178
1375
|
listener(event);
|
|
1179
1376
|
});
|
|
1180
|
-
runner.subscribeAgentKernelUserMessageStart((content, clientMessageId) => {
|
|
1377
|
+
runtime.unsubscribeUserMessageStart = runner.subscribeAgentKernelUserMessageStart((content, clientMessageId) => {
|
|
1181
1378
|
this.consumeQueuedMessage(runtime, content);
|
|
1182
1379
|
if (!clientMessageId)
|
|
1183
1380
|
return;
|
|
@@ -1194,11 +1391,26 @@ class ConversationKernel {
|
|
|
1194
1391
|
runtime.runner.recordGuideReceipt(applied);
|
|
1195
1392
|
runtime.guideEnvelopes.delete(clientMessageId);
|
|
1196
1393
|
});
|
|
1197
|
-
runtime.unsubscribeRootInboxWake = runner.subscribeRootInboxWake(message => {
|
|
1198
|
-
this.enqueueRootInboxWake(runtime, message);
|
|
1394
|
+
runtime.unsubscribeRootInboxWake = runner.subscribeRootInboxWake((message, options) => {
|
|
1395
|
+
this.enqueueRootInboxWake(runtime, message, options?.wakeup !== false);
|
|
1199
1396
|
return true;
|
|
1397
|
+
}, ids => {
|
|
1398
|
+
const retired = new Set(ids);
|
|
1399
|
+
const before = runtime.pendingNextTurn.length;
|
|
1400
|
+
runtime.pendingNextTurn = runtime.pendingNextTurn.filter(item => {
|
|
1401
|
+
if (item.queueMode !== 'followUp' || typeof item.message === 'string'
|
|
1402
|
+
|| !item.message.hiddenUserInput || item.message.clientMessageId)
|
|
1403
|
+
return true;
|
|
1404
|
+
const id = item.message.text.match(/^\[Root subagent inbox id=([0-9a-f-]{36})\b/i)?.[1];
|
|
1405
|
+
return !id || !retired.has(id);
|
|
1406
|
+
});
|
|
1407
|
+
if (runtime.pendingNextTurn.length !== before)
|
|
1408
|
+
this.emitQueueUpdate(runtime);
|
|
1200
1409
|
});
|
|
1201
1410
|
this.runtimes.set(target.runtimeKey, runtime);
|
|
1411
|
+
// An already-read inbox entry is not replayed by the mailbox listener.
|
|
1412
|
+
// Reconcile saved result receipts after hydrating its continuation copy.
|
|
1413
|
+
runner.acknowledgeSubagentSettlementReceipts();
|
|
1202
1414
|
return runtime;
|
|
1203
1415
|
}
|
|
1204
1416
|
scheduleGoalContinuation(runtime, completedRunId) {
|
|
@@ -1207,7 +1419,7 @@ class ConversationKernel {
|
|
|
1207
1419
|
runtime.runner.flushWorkspaceConversationState();
|
|
1208
1420
|
runtime.goalContinuationTimer = setTimeout(() => {
|
|
1209
1421
|
runtime.goalContinuationTimer = undefined;
|
|
1210
|
-
if (runtime.runId !== completedRunId || runtime.activePromise)
|
|
1422
|
+
if (runtime.preparingArchive || runtime.runId !== completedRunId || runtime.activePromise)
|
|
1211
1423
|
return;
|
|
1212
1424
|
this.queueState(runtime);
|
|
1213
1425
|
if (runtime.pendingNextTurn.length > 0
|
|
@@ -1238,19 +1450,64 @@ class ConversationKernel {
|
|
|
1238
1450
|
}
|
|
1239
1451
|
setImmediate(() => {
|
|
1240
1452
|
runtime.pendingContinuationRunId = undefined;
|
|
1241
|
-
if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused)
|
|
1453
|
+
if (runtime.preparingArchive || runtime.runId !== runId || runtime.activePromise || runtime.externalOwner || runtime.stopRequestedRunId === runId || runtime.queuePaused)
|
|
1242
1454
|
return;
|
|
1243
1455
|
const next = runtime.pendingNextTurn.shift();
|
|
1244
1456
|
if (!next)
|
|
1245
1457
|
return;
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1458
|
+
const delivery = next.queueMode === 'followUp' ? this.drainQueuedFollowUpMessage(next.message) : next.message;
|
|
1459
|
+
const message = typeof delivery === 'string'
|
|
1460
|
+
? { text: delivery, runId }
|
|
1461
|
+
: { ...delivery, runId: delivery.runId || runId };
|
|
1462
|
+
void this.runPendingQueueItem(runtime, next, async () => {
|
|
1463
|
+
if (message.visibleMode && ['build', 'chat', 'plan', 'goal'].includes(message.visibleMode)) {
|
|
1464
|
+
this.setMode(runtime.target, message.visibleMode);
|
|
1465
|
+
}
|
|
1466
|
+
return this.prompt(message, runtime.target, runtime.options, next.queueMode);
|
|
1467
|
+
}).catch(() => {
|
|
1250
1468
|
// Agent.process and the work-run finalizer already publish the error.
|
|
1251
1469
|
});
|
|
1252
1470
|
});
|
|
1253
1471
|
}
|
|
1472
|
+
async runPendingQueueItem(runtime, next, execute) {
|
|
1473
|
+
const queuedMessage = typeof next.message === 'string' ? undefined : next.message;
|
|
1474
|
+
const id = queuedMessage?.clientMessageId;
|
|
1475
|
+
if (next.queueMode !== 'followUp' || !id)
|
|
1476
|
+
return execute();
|
|
1477
|
+
let accepted = false;
|
|
1478
|
+
const unsubscribe = runtime.runner.subscribeAgentKernelUserMessageStart((content, clientMessageId) => {
|
|
1479
|
+
// Normal in-run dequeues omit display identity, but retain the exact
|
|
1480
|
+
// execution text. Never let an unrelated Guide accept this queue row.
|
|
1481
|
+
if (clientMessageId ? clientMessageId === id : content === queuedMessage.text)
|
|
1482
|
+
accepted = true;
|
|
1483
|
+
});
|
|
1484
|
+
try {
|
|
1485
|
+
return await execute();
|
|
1486
|
+
}
|
|
1487
|
+
catch (error) {
|
|
1488
|
+
// Model/title/setup failures before user-message acceptance must leave
|
|
1489
|
+
// the same row manageable. A provider failure after acceptance must
|
|
1490
|
+
// never replay the already submitted user input after restart/resume.
|
|
1491
|
+
runtime.queuePaused = true;
|
|
1492
|
+
throw error;
|
|
1493
|
+
}
|
|
1494
|
+
finally {
|
|
1495
|
+
unsubscribe();
|
|
1496
|
+
if (accepted)
|
|
1497
|
+
runtime.runner.consumeConversationContinuation({
|
|
1498
|
+
content: queuedMessage.text, clientMessageId: id, queueMode: 'followUp',
|
|
1499
|
+
});
|
|
1500
|
+
else if (runtime.runner.conversationContinuations().some(item => item.clientMessageId === id && item.queueMode === 'followUp')) {
|
|
1501
|
+
// Cooperative Stop can turn a rejected process into a resolved outer
|
|
1502
|
+
// result. Restore on every unaccepted exit, including failed archive;
|
|
1503
|
+
// the pause/archive guards still prevent any automatic execution.
|
|
1504
|
+
if (!runtime.pendingNextTurn.some(item => typeof item.message !== 'string' && item.message.clientMessageId === id))
|
|
1505
|
+
runtime.pendingNextTurn.unshift(next);
|
|
1506
|
+
runtime.queuePaused = true;
|
|
1507
|
+
}
|
|
1508
|
+
this.emitQueueUpdate(runtime);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1254
1511
|
startGoalDrivenBuild(runtime) {
|
|
1255
1512
|
if (runtime.goalContinuationTimer) {
|
|
1256
1513
|
clearTimeout(runtime.goalContinuationTimer);
|
|
@@ -1294,17 +1551,22 @@ class ConversationKernel {
|
|
|
1294
1551
|
runner.ensureConversationSnapshot(target.conversationId);
|
|
1295
1552
|
return runner;
|
|
1296
1553
|
}
|
|
1297
|
-
enqueueRootInboxWake(runtime, message) {
|
|
1554
|
+
enqueueRootInboxWake(runtime, message, wakeup = false) {
|
|
1555
|
+
const acceptedWhileActive = !!runtime.activePromise;
|
|
1298
1556
|
queueMicrotask(() => {
|
|
1299
1557
|
const rootInboxId = message.match(/^\[Root subagent inbox id=([0-9a-f-]{36})\b/i)?.[1];
|
|
1300
1558
|
if (rootInboxId && !runtime.runner.subagents.readRootInbox().some(item => item.id === rootInboxId))
|
|
1301
1559
|
return;
|
|
1560
|
+
if (runtime.queuePaused || runtime.runner.subagents.isSchedulingPaused())
|
|
1561
|
+
return;
|
|
1302
1562
|
if (runtime.activePromise) {
|
|
1303
1563
|
if (!runtime.pendingNextTurn.some(item => typeof item.message === 'string' ? item.message === message : item.message.text === message)) {
|
|
1304
1564
|
runtime.pendingNextTurn.push({ message: { text: message, hiddenUserInput: true }, queueMode: 'followUp' });
|
|
1305
1565
|
}
|
|
1306
1566
|
return;
|
|
1307
1567
|
}
|
|
1568
|
+
if (!wakeup && !acceptedWhileActive)
|
|
1569
|
+
return;
|
|
1308
1570
|
void this.prompt({ text: message, hiddenUserInput: true }, runtime.target, runtime.options, 'followUp').catch(error => {
|
|
1309
1571
|
runtime.runner.recordWorkStatus(`Subagent result follow-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1310
1572
|
});
|
|
@@ -1427,6 +1689,9 @@ class ConversationKernel {
|
|
|
1427
1689
|
images: queuedMessage.images?.map(image => ({ ...image })),
|
|
1428
1690
|
attachments: queuedMessage.attachments?.map(attachment => ({ ...attachment })),
|
|
1429
1691
|
createdAt: queuedMessage.createdAt,
|
|
1692
|
+
visibleUserInput: queuedMessage.visibleUserInput,
|
|
1693
|
+
visibleMode: queuedMessage.visibleMode,
|
|
1694
|
+
goalObjective: queuedMessage.goalObjective,
|
|
1430
1695
|
}]);
|
|
1431
1696
|
this.trackQueuedMessage(runtime, prompt, 'followUp');
|
|
1432
1697
|
runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId
|
|
@@ -1475,13 +1740,13 @@ class ConversationKernel {
|
|
|
1475
1740
|
}
|
|
1476
1741
|
trackQueuedMessage(runtime, message, queueMode) {
|
|
1477
1742
|
this.queueState(runtime);
|
|
1478
|
-
|
|
1479
|
-
|
|
1743
|
+
if (queueMode === 'steer')
|
|
1744
|
+
runtime.queued.steering.push(message);
|
|
1480
1745
|
}
|
|
1481
1746
|
consumeQueuedMessage(runtime, message) {
|
|
1482
1747
|
this.queueState(runtime);
|
|
1483
1748
|
let changed = false;
|
|
1484
|
-
for (const key of ['steering'
|
|
1749
|
+
for (const key of ['steering']) {
|
|
1485
1750
|
const index = runtime.queued[key].indexOf(message);
|
|
1486
1751
|
if (index >= 0) {
|
|
1487
1752
|
runtime.queued[key].splice(index, 1);
|
|
@@ -1491,13 +1756,6 @@ class ConversationKernel {
|
|
|
1491
1756
|
if (changed)
|
|
1492
1757
|
this.emitQueueUpdate(runtime);
|
|
1493
1758
|
}
|
|
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
1759
|
emitQueueUpdate(runtime) {
|
|
1502
1760
|
this.queueState(runtime);
|
|
1503
1761
|
runtime.runner.emitWorkEvent({
|
|
@@ -1524,8 +1782,14 @@ class ConversationKernel {
|
|
|
1524
1782
|
runtime.queued = { steering: [], followUp: [] };
|
|
1525
1783
|
if (!Array.isArray(runtime.queued.steering))
|
|
1526
1784
|
runtime.queued.steering = [];
|
|
1527
|
-
|
|
1528
|
-
|
|
1785
|
+
// Follow-up order belongs to the pending entries, not a second text-only
|
|
1786
|
+
// ledger. Repeated text, edits and user-message-start callbacks must not
|
|
1787
|
+
// independently remove/reorder that projection. Internal auto-continuations
|
|
1788
|
+
// without user identity remain outside the visible user queue.
|
|
1789
|
+
runtime.queued.followUp = runtime.pendingNextTurn
|
|
1790
|
+
.filter(item => item.queueMode === 'followUp' && (typeof item.message === 'string'
|
|
1791
|
+
|| item.message.hiddenUserInput !== true || !!item.message.clientMessageId))
|
|
1792
|
+
.map(item => typeof item.message === 'string' ? item.message : item.message.text);
|
|
1529
1793
|
return runtime;
|
|
1530
1794
|
}
|
|
1531
1795
|
normalizeTarget(input) {
|