newmark-agent 0.4.7 → 0.4.8

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.
@@ -1387,10 +1387,13 @@ function toKernelTools(agent, definitions, provisioning) {
1387
1387
  throw new Error(rawText);
1388
1388
  }
1389
1389
  const visionImage = visualFallbackImageInput(agent, name, rawText);
1390
+ const capturedInput = name === 'screen_capture' ? agent.registerCapturedImageInput(visionImage.image || '', 'active-screenshot.jpg') : null;
1390
1391
  const directImage = imageInspectDataUrl(name, rawText);
1391
- const text = spillOversizedToolResult(agent, name, sanitizeVisualToolText(name, rawText));
1392
+ const text = spillOversizedToolResult(agent, name, capturedImageToolText(name, sanitizeVisualToolText(name, rawText), capturedInput?.id));
1392
1393
  const content = [{ type: 'text', text }];
1393
- if (visionImage.imagePath)
1394
+ if (name === 'screen_capture' && capturedInput?.dataUrl)
1395
+ content.push({ type: 'image', image: capturedInput.dataUrl, mimeType: capturedInput.mimeType });
1396
+ else if (visionImage.imagePath)
1394
1397
  content.push({ type: 'image', imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
1395
1398
  else if (visionImage.image)
1396
1399
  content.push({ type: 'image', image: visionImage.image, mimeType: visionImage.mimeType });
@@ -1407,7 +1410,7 @@ function toKernelTools(agent, definitions, provisioning) {
1407
1410
  }
1408
1411
  catch { }
1409
1412
  }
1410
- return { content, details: { tool: name, ok: true, terminate, ...(launchReceipt ? { launchReceipt } : {}), visionImagePath: visionImage.imagePath || undefined, ephemeralVisionImage: !!visionImage.image, displayImage }, terminate };
1413
+ return { content, details: { tool: name, ok: true, terminate, ...(launchReceipt ? { launchReceipt } : {}), visionImagePath: visionImage.imagePath || undefined, ephemeralVisionImage: !!visionImage.image, capturedAttachmentId: capturedInput?.id, displayImage }, terminate };
1411
1414
  },
1412
1415
  };
1413
1416
  }).filter((tool) => !!tool.name);
@@ -1440,7 +1443,7 @@ function boundInlineToolResult(name, text) {
1440
1443
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS)
1441
1444
  return value;
1442
1445
  // 结构化结果(JSON/视觉/浏览器/子代理/计划等)不可安全截断,保持原样。
1443
- if (['computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_create', 'SubAgent', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1446
+ if (['screen_capture', 'computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_create', 'SubAgent', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1444
1447
  return value;
1445
1448
  }
1446
1449
  const headChars = Math.floor(INLINE_TOOL_RESULT_MAX_CHARS * 0.6);
@@ -1460,7 +1463,7 @@ function spillOversizedToolResult(agent, name, text) {
1460
1463
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS)
1461
1464
  return value;
1462
1465
  // 结构化结果不可安全落盘引用(破坏 JSON 结构),保持原样。
1463
- if (['computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_create', 'SubAgent', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1466
+ if (['screen_capture', 'computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_create', 'SubAgent', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1464
1467
  return value;
1465
1468
  }
1466
1469
  const artifactId = agent.storeToolResultArtifact(name, value);
@@ -1475,11 +1478,11 @@ function spillOversizedToolResult(agent, name, text) {
1475
1478
  ].join('\n');
1476
1479
  }
1477
1480
  function sanitizeVisualToolText(name, text) {
1478
- if (name !== 'computer_use' && name !== 'browser_use' && name !== 'pdf_read' && name !== 'image_inspect')
1481
+ if (name !== 'screen_capture' && name !== 'computer_use' && name !== 'browser_use' && name !== 'pdf_read' && name !== 'image_inspect')
1479
1482
  return text;
1480
1483
  try {
1481
1484
  const parsed = JSON.parse(text);
1482
- if (name === 'computer_use' || name === 'browser_use' || name === 'pdf_read') {
1485
+ if (name === 'screen_capture' || name === 'computer_use' || name === 'browser_use' || name === 'pdf_read') {
1483
1486
  delete parsed.vision_image_path;
1484
1487
  delete parsed.vision_image_data_url;
1485
1488
  if (name === 'pdf_read' && parsed.result && typeof parsed.result === 'object') {
@@ -1497,7 +1500,7 @@ function sanitizeVisualToolText(name, text) {
1497
1500
  }
1498
1501
  }
1499
1502
  function discardComputerUseVisionImage(name, text) {
1500
- if (name !== 'computer_use')
1503
+ if (name !== 'screen_capture' && name !== 'computer_use')
1501
1504
  return;
1502
1505
  try {
1503
1506
  const parsed = JSON.parse(text);
@@ -1519,8 +1522,22 @@ function imageInspectDataUrl(name, text) {
1519
1522
  return '';
1520
1523
  }
1521
1524
  }
1525
+ function capturedImageToolText(name, text, attachmentId) {
1526
+ if (name !== 'screen_capture' || !attachmentId)
1527
+ return text;
1528
+ try {
1529
+ const parsed = JSON.parse(text);
1530
+ parsed.attachment_id = attachmentId;
1531
+ parsed.image_input_channel = 'user-image';
1532
+ parsed.inspect_next = { tool: 'image_inspect', actions: ['source_info', 'crop'], max_scale: 4 };
1533
+ return JSON.stringify(parsed, null, 2);
1534
+ }
1535
+ catch {
1536
+ return text;
1537
+ }
1538
+ }
1522
1539
  function visualFallbackImageInput(agent, name, text) {
1523
- if (name !== 'computer_use' && name !== 'browser_use' && name !== 'pdf_read')
1540
+ if (name !== 'screen_capture' && name !== 'computer_use' && name !== 'browser_use' && name !== 'pdf_read')
1524
1541
  return {};
1525
1542
  const model = agent.activeModelConfig();
1526
1543
  if (!model?.vision)
@@ -1685,7 +1702,7 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal) {
1685
1702
  actorId: agent.runtimeActorId,
1686
1703
  workspaceId: (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(wsDir),
1687
1704
  backend: process.env.NEWMARK_WSL_DISTRO ? 'wsl' : (process.platform === 'win32' ? 'windows' : process.platform),
1688
- allowEphemeralVisionImage: (name === 'computer_use' || name === 'browser_use' || name === 'pdf_read' || name === 'ocr_read')
1705
+ allowEphemeralVisionImage: (name === 'screen_capture' || name === 'computer_use' || name === 'browser_use' || name === 'pdf_read' || name === 'ocr_read')
1689
1706
  && !!agent.activeModelConfig()?.vision,
1690
1707
  signal,
1691
1708
  });
@@ -3,6 +3,16 @@ import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, ConversationImage
3
3
  import { AutomationManager } from './automation';
4
4
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
5
5
  export type ConversationQueueMode = 'steer' | 'followUp';
6
+ export interface ConversationQueueItemSnapshot {
7
+ id: string;
8
+ text: string;
9
+ queueMode: ConversationQueueMode;
10
+ requestedMode?: string;
11
+ goalObjective?: string;
12
+ runId?: string;
13
+ createdAt: string;
14
+ }
15
+ export type ConversationQueueAction = 'enqueue' | 'update' | 'delete' | 'toggle_pause' | 'guide';
6
16
  export interface AgentPromptMessage {
7
17
  text: string;
8
18
  /** Public transcript text when the execution prompt contains hidden orchestration instructions. */
@@ -176,6 +186,33 @@ export declare class ConversationKernel {
176
186
  steering: string[];
177
187
  followUp: string[];
178
188
  };
189
+ queueItems(target: ConversationTargetInput): ConversationQueueItemSnapshot[];
190
+ enqueueNext(target: ConversationTargetInput, input: {
191
+ id: string;
192
+ text: string;
193
+ requestedMode?: string;
194
+ goalObjective?: string;
195
+ createdAt?: string;
196
+ }): ConversationQueueItemSnapshot;
197
+ updateQueueItem(target: ConversationTargetInput, idInput: string, textInput: string): ConversationQueueItemSnapshot;
198
+ deleteQueueItem(target: ConversationTargetInput, idInput: string): boolean;
199
+ setQueuePaused(target: ConversationTargetInput, paused: boolean): boolean;
200
+ queueAction(target: ConversationTargetInput, action: ConversationQueueAction, input?: {
201
+ id?: string;
202
+ text?: string;
203
+ requestedMode?: string;
204
+ goalObjective?: string;
205
+ createdAt?: string;
206
+ }): {
207
+ ok: boolean;
208
+ queueItems: ConversationQueueItemSnapshot[];
209
+ queuePaused: boolean;
210
+ queued: {
211
+ steering: string[];
212
+ followUp: string[];
213
+ };
214
+ receipt?: GuideReceipt;
215
+ };
179
216
  events(target: ConversationTargetInput): AgentWorkEvent[];
180
217
  waitForIdle(target: ConversationTargetInput): Promise<void>;
181
218
  pendingOptions(target: ConversationTargetInput): OptionQuestion[] | undefined;
@@ -185,6 +222,8 @@ export declare class ConversationKernel {
185
222
  steering: string[];
186
223
  followUp: string[];
187
224
  };
225
+ queueItems: ConversationQueueItemSnapshot[];
226
+ queuePaused: boolean;
188
227
  workEvents: AgentWorkEvent[];
189
228
  runtime: ConversationRuntimeState | null;
190
229
  mode: Agent['mode'];
@@ -258,6 +297,7 @@ export declare class ConversationKernel {
258
297
  private enqueueSameSession;
259
298
  private trackQueuedMessage;
260
299
  private consumeQueuedMessage;
300
+ private replaceTrackedQueuedMessage;
261
301
  private emitQueueUpdate;
262
302
  private clearQueued;
263
303
  private queueState;
@@ -60,6 +60,173 @@ class ConversationKernel {
60
60
  followUp: queued?.followUp.slice() || [],
61
61
  };
62
62
  }
63
+ queueItems(target) {
64
+ const runtime = this.findRuntime(target);
65
+ if (!runtime)
66
+ return [];
67
+ return runtime.pendingNextTurn.flatMap(item => {
68
+ if (item.queueMode !== 'followUp')
69
+ return [];
70
+ if (typeof item.message === 'string')
71
+ return [];
72
+ const id = String(item.message.clientMessageId || '');
73
+ if (!id)
74
+ return [];
75
+ return [{
76
+ id,
77
+ text: String(item.message.visibleUserInput || item.message.text || '').replace(/^\[Next queued while current turn is running\]\n/, ''),
78
+ queueMode: item.queueMode,
79
+ requestedMode: item.message.visibleMode,
80
+ goalObjective: item.message.goalObjective,
81
+ runId: item.message.runId,
82
+ createdAt: String(item.message.createdAt || ''),
83
+ }];
84
+ });
85
+ }
86
+ enqueueNext(target, input) {
87
+ const runtime = this.findRuntime(target);
88
+ if (!runtime?.activePromise || !runtime.runId)
89
+ throw new Error('Target conversation is not running');
90
+ const id = String(input.id || '').trim().slice(0, 200);
91
+ const text = String(input.text || '').trim();
92
+ if (!id || !text)
93
+ throw new Error('Queue item id and text are required');
94
+ const existing = this.queueItems(runtime.target).find(item => item.id === id);
95
+ if (existing)
96
+ return existing;
97
+ const prompt = `[Next queued while current turn is running]\n${text}`;
98
+ const createdAt = String(input.createdAt || new Date().toISOString());
99
+ runtime.pendingNextTurn.push({
100
+ message: {
101
+ text: prompt,
102
+ visibleUserInput: text,
103
+ visibleMode: String(input.requestedMode || 'build'),
104
+ goalObjective: String(input.goalObjective || '') || undefined,
105
+ clientMessageId: id,
106
+ runId: runtime.runId,
107
+ createdAt,
108
+ },
109
+ queueMode: 'followUp',
110
+ });
111
+ runtime.runner.retainConversationContinuations([{
112
+ content: prompt,
113
+ queueMode: 'followUp',
114
+ clientMessageId: id,
115
+ runId: runtime.runId,
116
+ createdAt,
117
+ }]);
118
+ this.trackQueuedMessage(runtime, prompt, 'followUp');
119
+ this.emitQueueUpdate(runtime);
120
+ return this.queueItems(runtime.target).find(item => item.id === id);
121
+ }
122
+ updateQueueItem(target, idInput, textInput) {
123
+ const runtime = this.findRuntime(target);
124
+ if (!runtime)
125
+ throw new Error('Target conversation runtime is unavailable');
126
+ const id = String(idInput || '').trim();
127
+ const text = String(textInput || '').trim();
128
+ if (!id || !text)
129
+ throw new Error('Queue item id and text are required');
130
+ const pending = runtime.pendingNextTurn.find(item => typeof item.message !== 'string' && item.message.clientMessageId === id);
131
+ if (!pending || typeof pending.message === 'string')
132
+ throw new Error('Queue item is no longer editable');
133
+ const oldPrompt = pending.message.text;
134
+ pending.message.text = `[Next queued while current turn is running]\n${text}`;
135
+ pending.message.visibleUserInput = text;
136
+ runtime.runner.consumeConversationContinuation({ content: oldPrompt, queueMode: pending.queueMode, clientMessageId: id });
137
+ runtime.runner.retainConversationContinuations([{
138
+ content: pending.message.text,
139
+ queueMode: pending.queueMode,
140
+ clientMessageId: id,
141
+ runId: pending.message.runId,
142
+ }]);
143
+ this.replaceTrackedQueuedMessage(runtime, oldPrompt, pending.message.text, pending.queueMode);
144
+ this.emitQueueUpdate(runtime);
145
+ return this.queueItems(runtime.target).find(item => item.id === id);
146
+ }
147
+ deleteQueueItem(target, idInput) {
148
+ const runtime = this.findRuntime(target);
149
+ if (!runtime)
150
+ return false;
151
+ const id = String(idInput || '').trim();
152
+ const index = runtime.pendingNextTurn.findIndex(item => typeof item.message !== 'string' && item.message.clientMessageId === id);
153
+ if (index < 0)
154
+ return false;
155
+ const [removed] = runtime.pendingNextTurn.splice(index, 1);
156
+ if (typeof removed.message !== 'string') {
157
+ runtime.runner.consumeConversationContinuation({ content: removed.message.text, queueMode: removed.queueMode, clientMessageId: id });
158
+ this.consumeQueuedMessage(runtime, removed.message.text);
159
+ }
160
+ this.emitQueueUpdate(runtime);
161
+ return true;
162
+ }
163
+ setQueuePaused(target, paused) {
164
+ const runtime = this.findRuntime(target);
165
+ if (!runtime)
166
+ throw new Error('Target conversation runtime is unavailable');
167
+ runtime.queuePaused = paused;
168
+ this.emitQueueUpdate(runtime);
169
+ if (!paused && !runtime.activePromise && runtime.pendingNextTurn.length) {
170
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
171
+ }
172
+ return runtime.queuePaused;
173
+ }
174
+ queueAction(target, action, input = {}) {
175
+ const runtime = this.findRuntime(target);
176
+ if (!runtime)
177
+ throw new Error('Target conversation runtime is unavailable');
178
+ let receipt;
179
+ if (action === 'enqueue') {
180
+ this.enqueueNext(runtime.target, {
181
+ id: String(input.id || ''),
182
+ text: String(input.text || ''),
183
+ requestedMode: input.requestedMode,
184
+ goalObjective: input.goalObjective,
185
+ createdAt: input.createdAt,
186
+ });
187
+ }
188
+ else if (action === 'update') {
189
+ this.updateQueueItem(runtime.target, String(input.id || ''), String(input.text || ''));
190
+ }
191
+ else if (action === 'delete') {
192
+ if (!this.deleteQueueItem(runtime.target, String(input.id || '')))
193
+ throw new Error('Queue item was not found');
194
+ }
195
+ else if (action === 'toggle_pause') {
196
+ this.setQueuePaused(runtime.target, !runtime.queuePaused);
197
+ }
198
+ else if (action === 'guide') {
199
+ const item = this.queueItems(runtime.target).find(entry => entry.id === String(input.id || ''));
200
+ if (!item)
201
+ throw new Error('Queue item was not found');
202
+ receipt = this.enqueueGuide({
203
+ clientMessageId: item.id,
204
+ guideId: (0, crypto_1.randomUUID)(),
205
+ target: runtime.target,
206
+ runId: runtime.runId,
207
+ deliveryMode: 'steer',
208
+ text: item.goalObjective ? `Goal for the current Build:\n${item.goalObjective}` : item.text,
209
+ goalObjective: item.goalObjective,
210
+ createdAt: item.createdAt || new Date().toISOString(),
211
+ });
212
+ if (receipt.status === 'rejected')
213
+ return {
214
+ ok: false,
215
+ queueItems: this.queueItems(runtime.target),
216
+ queuePaused: runtime.queuePaused,
217
+ queued: this.queued(runtime.target),
218
+ receipt,
219
+ };
220
+ this.deleteQueueItem(runtime.target, item.id);
221
+ }
222
+ return {
223
+ ok: true,
224
+ queueItems: this.queueItems(runtime.target),
225
+ queuePaused: runtime.queuePaused,
226
+ queued: this.queued(runtime.target),
227
+ receipt,
228
+ };
229
+ }
63
230
  events(target) {
64
231
  return this.findRuntime(target)?.events.slice() || [];
65
232
  }
@@ -89,6 +256,8 @@ class ConversationKernel {
89
256
  workRuns: this.bindWorkRunsToRuntimeTarget(conversationSnapshot.workRuns, normalized),
90
257
  target: normalized,
91
258
  queued: this.queued(normalized),
259
+ queueItems: this.queueItems(normalized),
260
+ queuePaused: runtime?.queuePaused === true,
92
261
  workEvents: this.events(normalized),
93
262
  runtime: this.runtimeState(normalized),
94
263
  mode: runner.mode,
@@ -562,7 +731,7 @@ class ConversationKernel {
562
731
  stopped = true;
563
732
  this.settleCooperativeStop(runtime, runId);
564
733
  }
565
- else if (runtime.pendingNextTurn.length > 0) {
734
+ else if (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
566
735
  // A renderer/IPC Guide can arrive after the final-drain barrier's
567
736
  // last check but before this promise settles. Do not leave the
568
737
  // deferred continuation queued on an idle runtime.
@@ -605,7 +774,7 @@ class ConversationKernel {
605
774
  return this.result(runtime, lastTokens);
606
775
  }
607
776
  for (;;) {
608
- while (runtime.pendingNextTurn.length > 0) {
777
+ while (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
609
778
  if (runtime.stopRequestedRunId === runtime.runId)
610
779
  return this.result(runtime, lastTokens);
611
780
  const next = runtime.pendingNextTurn.shift();
@@ -652,7 +821,8 @@ class ConversationKernel {
652
821
  this.mirrorHostIfTargetActive(runtime);
653
822
  return this.result(runtime, lastTokens);
654
823
  }
655
- this.clearQueued(runtime);
824
+ if (!runtime.pendingNextTurn.length)
825
+ this.clearQueued(runtime);
656
826
  const completedRunId = runtime.runId;
657
827
  runtime.runner.finishConversationWorkRun(completedRunId, 'completed');
658
828
  // A Guide can be submitted synchronously by a consumer of the public
@@ -662,6 +832,7 @@ class ConversationKernel {
662
832
  await new Promise(resolve => setImmediate(resolve));
663
833
  if (runtime.runId === completedRunId
664
834
  && runtime.stopRequestedRunId !== completedRunId
835
+ && !runtime.queuePaused
665
836
  && runtime.pendingNextTurn.length > 0) {
666
837
  runtime.guideAcceptanceClosedRunId = '';
667
838
  if (!runtime.runner.resumeConversationWorkRun(completedRunId)) {
@@ -757,6 +928,7 @@ class ConversationKernel {
757
928
  events: [],
758
929
  pendingNextTurn: [],
759
930
  queued: { steering: [], followUp: [] },
931
+ queuePaused: false,
760
932
  runId: '',
761
933
  generation: this.generations.get(target.runtimeKey) || 0,
762
934
  stopRequestedRunId: '',
@@ -874,7 +1046,7 @@ class ConversationKernel {
874
1046
  }
875
1047
  setImmediate(() => {
876
1048
  runtime.pendingContinuationRunId = undefined;
877
- if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId)
1049
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused)
878
1050
  return;
879
1051
  const next = runtime.pendingNextTurn.shift();
880
1052
  if (!next)
@@ -1040,8 +1212,39 @@ class ConversationKernel {
1040
1212
  const isSteer = queueMode === 'steer';
1041
1213
  const text = typeof message === 'string' ? message : message.text;
1042
1214
  const prompt = isSteer ? text : `[Next queued while current turn is running]\n${text}`;
1043
- if (!isSteer)
1044
- this.trackQueuedMessage(runtime, prompt, queueMode);
1215
+ if (!isSteer) {
1216
+ const structured = typeof message === 'string' ? null : message;
1217
+ const clientMessageId = String(structured?.clientMessageId || (0, crypto_1.randomUUID)());
1218
+ const queuedMessage = {
1219
+ ...(structured || {}),
1220
+ text: prompt,
1221
+ visibleUserInput: structured?.visibleUserInput || text,
1222
+ visibleMode: structured?.visibleMode || runtime.options.mode,
1223
+ clientMessageId,
1224
+ runId: structured?.runId || runtime.runId,
1225
+ createdAt: String(structured?.createdAt || new Date().toISOString()),
1226
+ };
1227
+ if (!runtime.pendingNextTurn.some(item => typeof item.message !== 'string' && item.message.clientMessageId === clientMessageId)) {
1228
+ runtime.pendingNextTurn.push({ message: queuedMessage, queueMode: 'followUp' });
1229
+ }
1230
+ runtime.runner.retainConversationContinuations([{
1231
+ content: prompt,
1232
+ queueMode: 'followUp',
1233
+ clientMessageId,
1234
+ runId: queuedMessage.runId,
1235
+ images: queuedMessage.images?.map(image => ({ ...image })),
1236
+ attachments: queuedMessage.attachments?.map(attachment => ({ ...attachment })),
1237
+ createdAt: queuedMessage.createdAt,
1238
+ }]);
1239
+ this.trackQueuedMessage(runtime, prompt, 'followUp');
1240
+ runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId
1241
+ ? 'Next message retained while stopping.'
1242
+ : 'Next message queued.');
1243
+ if (runtime.stopRequestedRunId === runtime.runId)
1244
+ runtime.forceStopArmedRunId = '';
1245
+ this.emitQueueUpdate(runtime);
1246
+ return;
1247
+ }
1045
1248
  if (runtime.stopRequestedRunId === runtime.runId) {
1046
1249
  runtime.forceStopArmedRunId = '';
1047
1250
  const structured = typeof message === 'string' ? undefined : message;
@@ -1096,6 +1299,13 @@ class ConversationKernel {
1096
1299
  if (changed)
1097
1300
  this.emitQueueUpdate(runtime);
1098
1301
  }
1302
+ replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
1303
+ this.queueState(runtime);
1304
+ const list = queueMode === 'steer' ? runtime.queued.steering : runtime.queued.followUp;
1305
+ const index = list.indexOf(oldMessage);
1306
+ if (index >= 0)
1307
+ list[index] = nextMessage;
1308
+ }
1099
1309
  emitQueueUpdate(runtime) {
1100
1310
  this.queueState(runtime);
1101
1311
  runtime.runner.emitWorkEvent({
@@ -1236,10 +1446,11 @@ class ConversationKernel {
1236
1446
  await Promise.resolve();
1237
1447
  if (runtime.runId !== runId || runtime.stopRequestedRunId === runId)
1238
1448
  return true;
1239
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0)
1449
+ const hasSteering = runtime.pendingNextTurn.some(item => item.queueMode === 'steer');
1450
+ if (hasSteering || runtime.runner.subagents.readRootInbox().length > 0)
1240
1451
  return false;
1241
1452
  runtime.guideAcceptanceClosedRunId = runId;
1242
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) {
1453
+ if (runtime.pendingNextTurn.some(item => item.queueMode === 'steer') || runtime.runner.subagents.readRootInbox().length > 0) {
1243
1454
  runtime.guideAcceptanceClosedRunId = '';
1244
1455
  return false;
1245
1456
  }
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { NormalizedConversationTarget } from './conversationTarget';
3
3
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
4
+ import { ConversationQueueAction } from './conversationKernel';
4
5
  import { UtilityAgentPromptResult, UtilityAutoRouteRatingResult, UtilityAgentSnapshotResult, UtilityAgentStopResult, UtilityConversationRewindResult, UtilityHostToolRequest, UtilityPromptRequest } from './utilityAgentProtocol';
5
6
  type WindowsProcessTreeHelperRuntime = {
6
7
  kind: 'precompiled' | 'runtime_compile';
@@ -94,6 +95,13 @@ export declare class ElectronUtilityAgentClient {
94
95
  rewind(messageIndex: number): Promise<UtilityConversationRewindResult>;
95
96
  requestStop(runId?: string): Promise<UtilityAgentStopResult>;
96
97
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
98
+ queueAction(action: ConversationQueueAction, input?: {
99
+ id?: string;
100
+ text?: string;
101
+ requestedMode?: string;
102
+ goalObjective?: string;
103
+ createdAt?: string;
104
+ }): Promise<Record<string, unknown>>;
97
105
  checkpoint(): Promise<Record<string, unknown>>;
98
106
  contextCompress(options?: {
99
107
  keepRecent?: number;
@@ -1023,6 +1023,10 @@ class ElectronUtilityAgentClient {
1023
1023
  await this.start();
1024
1024
  return await this.request('guide', { target: this.target, envelope }, 5_000);
1025
1025
  }
1026
+ async queueAction(action, input = {}) {
1027
+ await this.start();
1028
+ return await this.request('queue_action', { target: this.target, action, input }, 5_000);
1029
+ }
1026
1030
  async checkpoint() {
1027
1031
  await this.start();
1028
1032
  return await this.request('checkpoint', { target: this.target }, 5_000);
@@ -1334,7 +1338,7 @@ class ElectronUtilityAgentClient {
1334
1338
  let result;
1335
1339
  const controller = new AbortController();
1336
1340
  this.hostToolRuns.set(request.requestId, { generation, controller });
1337
- const allowed = new Set(['browser_control', 'browser_use', 'computer_use', 'automation', 'terminal_takeover']);
1341
+ const allowed = new Set(['browser_control', 'browser_use', 'screen_capture', 'computer_use', 'automation', 'terminal_takeover']);
1338
1342
  if (!allowed.has(request.tool)) {
1339
1343
  result = { requestId: request.requestId, ok: false, error: `Electron host tool is not allowed: ${String(request.tool)}` };
1340
1344
  }
@@ -1,4 +1,5 @@
1
1
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
2
+ import { ConversationQueueAction } from './conversationKernel';
2
3
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
3
4
  import { UtilityHostToolHandler } from './electronUtilityAgentClient';
4
5
  import { UtilityAgentPromptResult, UtilityAutoRouteRatingResult, UtilityConversationRewindResult, UtilityAgentSnapshotResult, UtilityAgentStopResult, UtilityPromptRequest } from './utilityAgentProtocol';
@@ -10,6 +11,13 @@ export interface ElectronTargetRuntimeClient {
10
11
  rewind(messageIndex: number): Promise<UtilityConversationRewindResult>;
11
12
  requestStop(runId?: string): Promise<UtilityAgentStopResult>;
12
13
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
14
+ queueAction?(action: ConversationQueueAction, input?: {
15
+ id?: string;
16
+ text?: string;
17
+ requestedMode?: string;
18
+ goalObjective?: string;
19
+ createdAt?: string;
20
+ }): Promise<Record<string, unknown>>;
13
21
  checkpoint(): Promise<Record<string, unknown>>;
14
22
  contextCompress?(options?: {
15
23
  keepRecent?: number;
@@ -71,6 +79,13 @@ export declare class ElectronUtilityRuntimePool {
71
79
  rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<UtilityConversationRewindResult>;
72
80
  requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<ElectronPoolStopResult>;
73
81
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
82
+ queueAction(target: ConversationRuntimeTarget, action: ConversationQueueAction, input?: {
83
+ id?: string;
84
+ text?: string;
85
+ requestedMode?: string;
86
+ goalObjective?: string;
87
+ createdAt?: string;
88
+ }): Promise<Record<string, unknown>>;
74
89
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
75
90
  contextCompress(target: ConversationRuntimeTarget, options?: {
76
91
  keepRecent?: number;
@@ -184,6 +184,17 @@ class ElectronUtilityRuntimePool {
184
184
  this.release(entry, true);
185
185
  }
186
186
  }
187
+ async queueAction(target, action, input = {}) {
188
+ const entry = await this.acquireExisting(target);
189
+ if (!entry || !entry.client.queueAction)
190
+ throw new Error('Target conversation is not running');
191
+ try {
192
+ return await entry.client.queueAction(action, input);
193
+ }
194
+ finally {
195
+ this.release(entry, true);
196
+ }
197
+ }
187
198
  async checkpoint(target) {
188
199
  const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
189
200
  const entry = await this.acquireExisting(normalized);
@@ -10,6 +10,7 @@ exports.planModePolicyPrompt = planModePolicyPrompt;
10
10
  exports.evaluateDeletionGuard = evaluateDeletionGuard;
11
11
  const REQUIRED_TOOLS = new Set(['pwd', 'read', 'glob', 'grep']);
12
12
  const MODE_SCOPED_TOOLS = new Set([
13
+ 'screen_capture',
13
14
  'image_inspect',
14
15
  'image_display',
15
16
  'ocr_read',
@@ -42,6 +43,7 @@ const MODE_SCOPED_TOOLS = new Set([
42
43
  'branch_create',
43
44
  ]);
44
45
  const PLAN_READ_ONLY_TOOLS = new Set([
46
+ 'screen_capture',
45
47
  'task_read',
46
48
  'pwd',
47
49
  'read',
@@ -103,6 +105,7 @@ const CONCURRENCY_SAFE_TOOLS = new Set([
103
105
  'git_status',
104
106
  'file_audit',
105
107
  'repo_security_audit',
108
+ 'screen_capture',
106
109
  'SubAgent',
107
110
  ]);
108
111
  /** 判断一个工具是否可参与并行调度。缺省 false(独占)。
@@ -1,6 +1,6 @@
1
1
  import { BrowserControlRequest, BrowserControlResult } from './browserControl';
2
2
  import { BrowserUseRequest } from './browserUse';
3
- import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationRuntimeState, ConversationStopResult } from './conversationKernel';
3
+ import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueAction, ConversationQueueMode, ConversationRuntimeState, ConversationStopResult } from './conversationKernel';
4
4
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
5
5
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
6
6
  import type { AutoRouteRatingResult, ConversationSnapshot } from './agent';
@@ -40,6 +40,10 @@ export type UtilityHostToolRequest = (UtilityHostToolRequestBase & {
40
40
  tool: 'browser_use';
41
41
  args: BrowserUseRequest;
42
42
  context: UtilityHostToolContext;
43
+ }) | (UtilityHostToolRequestBase & {
44
+ tool: 'screen_capture';
45
+ args: Record<string, unknown>;
46
+ context: UtilityHostToolContext;
43
47
  }) | (UtilityHostToolRequestBase & {
44
48
  tool: 'computer_use';
45
49
  args: Record<string, unknown>;
@@ -96,6 +100,20 @@ export type UtilityAgentRequest = {
96
100
  target: ConversationRuntimeTarget;
97
101
  envelope: ConversationInputEnvelope;
98
102
  };
103
+ } | {
104
+ id: string;
105
+ method: 'queue_action';
106
+ params: {
107
+ target: ConversationRuntimeTarget;
108
+ action: ConversationQueueAction;
109
+ input?: {
110
+ id?: string;
111
+ text?: string;
112
+ requestedMode?: string;
113
+ goalObjective?: string;
114
+ createdAt?: string;
115
+ };
116
+ };
99
117
  } | {
100
118
  id: string;
101
119
  method: 'checkpoint';
@@ -210,6 +228,16 @@ export interface UtilityAgentSnapshotResult {
210
228
  steering: string[];
211
229
  followUp: string[];
212
230
  };
231
+ queueItems?: Array<{
232
+ id: string;
233
+ text: string;
234
+ queueMode: 'steer' | 'followUp';
235
+ requestedMode?: string;
236
+ goalObjective?: string;
237
+ runId?: string;
238
+ createdAt: string;
239
+ }>;
240
+ queuePaused?: boolean;
213
241
  workEvents: AgentWorkEvent[];
214
242
  [key: string]: unknown;
215
243
  }