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.
Files changed (68) hide show
  1. package/dist/conversation-utility-host.bundle.cjs +4565 -2998
  2. package/dist/conversation-utility-host.js +1 -1
  3. package/dist/core/agent.d.ts +108 -16
  4. package/dist/core/agent.js +698 -195
  5. package/dist/core/agentKernel/agent.d.ts +1 -0
  6. package/dist/core/agentKernel/agent.js +9 -0
  7. package/dist/core/agentKernelDiagnostics.d.ts +4 -6
  8. package/dist/core/agentKernelDiagnostics.js +12 -9
  9. package/dist/core/agentKernelRunner.d.ts +5 -0
  10. package/dist/core/agentKernelRunner.js +250 -107
  11. package/dist/core/autoRouter.js +17 -27
  12. package/dist/core/config.d.ts +2 -0
  13. package/dist/core/config.js +7 -1
  14. package/dist/core/conversationCommandState.d.ts +17 -0
  15. package/dist/core/conversationCommandState.js +140 -0
  16. package/dist/core/conversationKernel.d.ts +25 -4
  17. package/dist/core/conversationKernel.js +360 -89
  18. package/dist/core/conversationListEvent.d.ts +6 -0
  19. package/dist/core/conversationListEvent.js +14 -0
  20. package/dist/core/electronUtilityAgentClient.d.ts +4 -1
  21. package/dist/core/electronUtilityAgentClient.js +4 -4
  22. package/dist/core/electronUtilityRuntimePool.d.ts +8 -2
  23. package/dist/core/electronUtilityRuntimePool.js +11 -5
  24. package/dist/core/installUpdate.js +41 -29
  25. package/dist/core/modelResponseHealth.d.ts +17 -0
  26. package/dist/core/modelResponseHealth.js +193 -0
  27. package/dist/core/providerUsageAccounting.d.ts +47 -0
  28. package/dist/core/providerUsageAccounting.js +71 -0
  29. package/dist/core/requestContextEstimate.d.ts +18 -0
  30. package/dist/core/requestContextEstimate.js +54 -0
  31. package/dist/core/subagent.d.ts +89 -5
  32. package/dist/core/subagent.js +264 -41
  33. package/dist/core/subagentCommunication.d.ts +43 -0
  34. package/dist/core/subagentCommunication.js +167 -0
  35. package/dist/core/types.d.ts +34 -1
  36. package/dist/core/utilityAgentProtocol.d.ts +4 -0
  37. package/dist/core/workEventCoalescer.js +4 -1
  38. package/dist/core/wslAgentClient.d.ts +18 -5
  39. package/dist/core/wslAgentClient.js +79 -27
  40. package/dist/core/wslAgentProtocol.d.ts +7 -0
  41. package/dist/core/wslAgentRuntimePool.d.ts +8 -2
  42. package/dist/core/wslAgentRuntimePool.js +19 -6
  43. package/dist/core/wslRuntimeProcessTree.d.ts +10 -0
  44. package/dist/core/wslRuntimeProcessTree.js +104 -0
  45. package/dist/llm/provider.d.ts +5 -12
  46. package/dist/llm/provider.js +150 -269
  47. package/dist/main.js +582 -356
  48. package/dist/preload.js +3 -3
  49. package/dist/providers/chat-completions.adapter.d.ts +2 -0
  50. package/dist/providers/chat-completions.adapter.js +84 -36
  51. package/dist/providers/provider-adapter.d.ts +2 -0
  52. package/dist/providers/provider-events.d.ts +23 -14
  53. package/dist/providers/provider-events.js +148 -43
  54. package/dist/providers/provider-headers.d.ts +7 -3
  55. package/dist/providers/provider-headers.js +109 -39
  56. package/dist/providers/provider-request-compat.d.ts +6 -0
  57. package/dist/providers/provider-request-compat.js +37 -0
  58. package/dist/providers/responses.adapter.d.ts +2 -0
  59. package/dist/providers/responses.adapter.js +192 -126
  60. package/dist/server.d.ts +9 -2
  61. package/dist/server.js +100 -24
  62. package/dist/tools/index.js +17 -1
  63. package/dist/ui/index.html +2515 -693
  64. package/dist/ui/lucide-sprite.svg +7 -0
  65. package/dist/ui/startup.html +6 -6
  66. package/dist/wsl-agent-host.bundle.cjs +4575 -3006
  67. package/dist/wsl-agent-host.js +9 -4
  68. 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
- return !!this.findRuntime(target)?.activePromise;
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?.activePromise || !runtime.runId)
121
- throw new Error('Target conversation is not running');
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 oldPrompt = pending.message.text;
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
- runtime.runner.consumeConversationContinuation({ content: oldPrompt, queueMode: pending.queueMode, clientMessageId: id });
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 runtime = this.findRuntime(target);
249
- if (!runtime)
250
- throw new Error('Target conversation runtime is unavailable');
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
- const runtime = this.findRuntime(normalized);
474
+ let runtime = this.findRuntime(normalized);
329
475
  const runner = runtime?.runner || this.createRunner(normalized);
330
- const conversationSnapshot = runner.getConversationSnapshot(normalized.conversationId);
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.model,
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.setMode(mode);
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.model;
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.model,
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
- if (runtime && !runtime.activePromise && runtime.runId && runtime.stopRequestedRunId === runtime.runId) {
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?.activePromise || !runtime.runId) {
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 this conversation.',
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.runSingle(runtime, drained, next.queueMode);
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
- this.syncPendingModel(runtime);
1026
- this.consumeQueuedMessage(runtime, typeof message === 'string' ? message : message.text);
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
- let tokens;
1048
- try {
1049
- tokens = await Promise.race([
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
- catch (error) {
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: continuationMode,
1077
- clientMessageId: typeof message === 'string' ? undefined : message.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 message = typeof next.message === 'string'
1247
- ? { text: next.message, runId }
1248
- : { ...next.message, runId: next.message.runId || runId };
1249
- void this.prompt(message, runtime.target, runtime.options, next.queueMode).catch(() => {
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
- agent.setModel(options.model);
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.model,
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
- const list = queueMode === 'steer' ? runtime.queued.steering : runtime.queued.followUp;
1479
- list.push(message);
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', 'followUp']) {
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
- if (!Array.isArray(runtime.queued.followUp))
1528
- runtime.queued.followUp = [];
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) {