newmark-agent 0.4.7 → 0.4.9

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.
@@ -60,6 +60,216 @@ 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
+ reorderQueueItems(target, orderedIdsInput) {
164
+ const runtime = this.findRuntime(target);
165
+ if (!runtime)
166
+ throw new Error('Target conversation runtime is unavailable');
167
+ const currentItems = this.queueItems(runtime.target);
168
+ const orderedIds = Array.isArray(orderedIdsInput)
169
+ ? orderedIdsInput.map(id => String(id || '').trim())
170
+ : [];
171
+ const currentIds = currentItems.map(item => item.id);
172
+ const completeOrder = orderedIds.length === currentIds.length
173
+ && new Set(orderedIds).size === orderedIds.length
174
+ && orderedIds.every(id => currentIds.includes(id));
175
+ if (!completeOrder)
176
+ throw new Error('A complete queue order with unique current item ids is required');
177
+ const persistedIds = runtime.runner.conversationContinuations()
178
+ .filter(item => item.queueMode === 'followUp' && !!item.clientMessageId)
179
+ .map(item => String(item.clientMessageId));
180
+ if (persistedIds.length !== currentIds.length
181
+ || new Set(persistedIds).size !== persistedIds.length
182
+ || persistedIds.some(id => !currentIds.includes(id))) {
183
+ throw new Error('Persisted queue does not match the complete queue order');
184
+ }
185
+ const pendingById = new Map(runtime.pendingNextTurn.flatMap(item => {
186
+ if (item.queueMode !== 'followUp' || typeof item.message === 'string' || !item.message.clientMessageId)
187
+ return [];
188
+ return [[String(item.message.clientMessageId), item]];
189
+ }));
190
+ const reorderedPending = orderedIds.map(id => pendingById.get(id));
191
+ let nextIndex = 0;
192
+ runtime.pendingNextTurn = runtime.pendingNextTurn.map(item => {
193
+ if (item.queueMode !== 'followUp' || typeof item.message === 'string' || !item.message.clientMessageId)
194
+ return item;
195
+ return reorderedPending[nextIndex++];
196
+ });
197
+ runtime.runner.reorderConversationContinuations(orderedIds);
198
+ runtime.queued.followUp = orderedIds.map(id => pendingById.get(id))
199
+ .map(item => typeof item.message === 'string' ? item.message : item.message.text);
200
+ this.emitQueueUpdate(runtime);
201
+ return this.queueItems(runtime.target);
202
+ }
203
+ setQueuePaused(target, paused) {
204
+ const runtime = this.findRuntime(target);
205
+ if (!runtime)
206
+ throw new Error('Target conversation runtime is unavailable');
207
+ runtime.queuePaused = paused;
208
+ this.emitQueueUpdate(runtime);
209
+ if (!paused && !runtime.activePromise && runtime.pendingNextTurn.length) {
210
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
211
+ }
212
+ return runtime.queuePaused;
213
+ }
214
+ queueAction(target, action, input = {}) {
215
+ const runtime = this.findRuntime(target);
216
+ if (!runtime)
217
+ throw new Error('Target conversation runtime is unavailable');
218
+ let receipt;
219
+ if (action === 'enqueue') {
220
+ this.enqueueNext(runtime.target, {
221
+ id: String(input.id || ''),
222
+ text: String(input.text || ''),
223
+ requestedMode: input.requestedMode,
224
+ goalObjective: input.goalObjective,
225
+ createdAt: input.createdAt,
226
+ });
227
+ }
228
+ else if (action === 'update') {
229
+ this.updateQueueItem(runtime.target, String(input.id || ''), String(input.text || ''));
230
+ }
231
+ else if (action === 'delete') {
232
+ if (!this.deleteQueueItem(runtime.target, String(input.id || '')))
233
+ throw new Error('Queue item was not found');
234
+ }
235
+ else if (action === 'reorder') {
236
+ this.reorderQueueItems(runtime.target, input.orderedIds || []);
237
+ }
238
+ else if (action === 'toggle_pause') {
239
+ this.setQueuePaused(runtime.target, !runtime.queuePaused);
240
+ }
241
+ else if (action === 'guide') {
242
+ const item = this.queueItems(runtime.target).find(entry => entry.id === String(input.id || ''));
243
+ if (!item)
244
+ throw new Error('Queue item was not found');
245
+ receipt = this.enqueueGuide({
246
+ clientMessageId: item.id,
247
+ guideId: (0, crypto_1.randomUUID)(),
248
+ target: runtime.target,
249
+ runId: runtime.runId,
250
+ deliveryMode: 'steer',
251
+ text: item.goalObjective ? `Goal for the current Build:\n${item.goalObjective}` : item.text,
252
+ goalObjective: item.goalObjective,
253
+ createdAt: item.createdAt || new Date().toISOString(),
254
+ });
255
+ if (receipt.status === 'rejected')
256
+ return {
257
+ ok: false,
258
+ queueItems: this.queueItems(runtime.target),
259
+ queuePaused: runtime.queuePaused,
260
+ queued: this.queued(runtime.target),
261
+ receipt,
262
+ };
263
+ this.deleteQueueItem(runtime.target, item.id);
264
+ }
265
+ return {
266
+ ok: true,
267
+ queueItems: this.queueItems(runtime.target),
268
+ queuePaused: runtime.queuePaused,
269
+ queued: this.queued(runtime.target),
270
+ receipt,
271
+ };
272
+ }
63
273
  events(target) {
64
274
  return this.findRuntime(target)?.events.slice() || [];
65
275
  }
@@ -89,6 +299,8 @@ class ConversationKernel {
89
299
  workRuns: this.bindWorkRunsToRuntimeTarget(conversationSnapshot.workRuns, normalized),
90
300
  target: normalized,
91
301
  queued: this.queued(normalized),
302
+ queueItems: this.queueItems(normalized),
303
+ queuePaused: runtime?.queuePaused === true,
92
304
  workEvents: this.events(normalized),
93
305
  runtime: this.runtimeState(normalized),
94
306
  mode: runner.mode,
@@ -562,7 +774,7 @@ class ConversationKernel {
562
774
  stopped = true;
563
775
  this.settleCooperativeStop(runtime, runId);
564
776
  }
565
- else if (runtime.pendingNextTurn.length > 0) {
777
+ else if (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
566
778
  // A renderer/IPC Guide can arrive after the final-drain barrier's
567
779
  // last check but before this promise settles. Do not leave the
568
780
  // deferred continuation queued on an idle runtime.
@@ -605,7 +817,7 @@ class ConversationKernel {
605
817
  return this.result(runtime, lastTokens);
606
818
  }
607
819
  for (;;) {
608
- while (runtime.pendingNextTurn.length > 0) {
820
+ while (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
609
821
  if (runtime.stopRequestedRunId === runtime.runId)
610
822
  return this.result(runtime, lastTokens);
611
823
  const next = runtime.pendingNextTurn.shift();
@@ -652,7 +864,8 @@ class ConversationKernel {
652
864
  this.mirrorHostIfTargetActive(runtime);
653
865
  return this.result(runtime, lastTokens);
654
866
  }
655
- this.clearQueued(runtime);
867
+ if (!runtime.pendingNextTurn.length)
868
+ this.clearQueued(runtime);
656
869
  const completedRunId = runtime.runId;
657
870
  runtime.runner.finishConversationWorkRun(completedRunId, 'completed');
658
871
  // A Guide can be submitted synchronously by a consumer of the public
@@ -662,6 +875,7 @@ class ConversationKernel {
662
875
  await new Promise(resolve => setImmediate(resolve));
663
876
  if (runtime.runId === completedRunId
664
877
  && runtime.stopRequestedRunId !== completedRunId
878
+ && !runtime.queuePaused
665
879
  && runtime.pendingNextTurn.length > 0) {
666
880
  runtime.guideAcceptanceClosedRunId = '';
667
881
  if (!runtime.runner.resumeConversationWorkRun(completedRunId)) {
@@ -757,6 +971,7 @@ class ConversationKernel {
757
971
  events: [],
758
972
  pendingNextTurn: [],
759
973
  queued: { steering: [], followUp: [] },
974
+ queuePaused: false,
760
975
  runId: '',
761
976
  generation: this.generations.get(target.runtimeKey) || 0,
762
977
  stopRequestedRunId: '',
@@ -874,7 +1089,7 @@ class ConversationKernel {
874
1089
  }
875
1090
  setImmediate(() => {
876
1091
  runtime.pendingContinuationRunId = undefined;
877
- if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId)
1092
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused)
878
1093
  return;
879
1094
  const next = runtime.pendingNextTurn.shift();
880
1095
  if (!next)
@@ -1040,8 +1255,39 @@ class ConversationKernel {
1040
1255
  const isSteer = queueMode === 'steer';
1041
1256
  const text = typeof message === 'string' ? message : message.text;
1042
1257
  const prompt = isSteer ? text : `[Next queued while current turn is running]\n${text}`;
1043
- if (!isSteer)
1044
- this.trackQueuedMessage(runtime, prompt, queueMode);
1258
+ if (!isSteer) {
1259
+ const structured = typeof message === 'string' ? null : message;
1260
+ const clientMessageId = String(structured?.clientMessageId || (0, crypto_1.randomUUID)());
1261
+ const queuedMessage = {
1262
+ ...(structured || {}),
1263
+ text: prompt,
1264
+ visibleUserInput: structured?.visibleUserInput || text,
1265
+ visibleMode: structured?.visibleMode || runtime.options.mode,
1266
+ clientMessageId,
1267
+ runId: structured?.runId || runtime.runId,
1268
+ createdAt: String(structured?.createdAt || new Date().toISOString()),
1269
+ };
1270
+ if (!runtime.pendingNextTurn.some(item => typeof item.message !== 'string' && item.message.clientMessageId === clientMessageId)) {
1271
+ runtime.pendingNextTurn.push({ message: queuedMessage, queueMode: 'followUp' });
1272
+ }
1273
+ runtime.runner.retainConversationContinuations([{
1274
+ content: prompt,
1275
+ queueMode: 'followUp',
1276
+ clientMessageId,
1277
+ runId: queuedMessage.runId,
1278
+ images: queuedMessage.images?.map(image => ({ ...image })),
1279
+ attachments: queuedMessage.attachments?.map(attachment => ({ ...attachment })),
1280
+ createdAt: queuedMessage.createdAt,
1281
+ }]);
1282
+ this.trackQueuedMessage(runtime, prompt, 'followUp');
1283
+ runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId
1284
+ ? 'Next message retained while stopping.'
1285
+ : 'Next message queued.');
1286
+ if (runtime.stopRequestedRunId === runtime.runId)
1287
+ runtime.forceStopArmedRunId = '';
1288
+ this.emitQueueUpdate(runtime);
1289
+ return;
1290
+ }
1045
1291
  if (runtime.stopRequestedRunId === runtime.runId) {
1046
1292
  runtime.forceStopArmedRunId = '';
1047
1293
  const structured = typeof message === 'string' ? undefined : message;
@@ -1096,6 +1342,13 @@ class ConversationKernel {
1096
1342
  if (changed)
1097
1343
  this.emitQueueUpdate(runtime);
1098
1344
  }
1345
+ replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
1346
+ this.queueState(runtime);
1347
+ const list = queueMode === 'steer' ? runtime.queued.steering : runtime.queued.followUp;
1348
+ const index = list.indexOf(oldMessage);
1349
+ if (index >= 0)
1350
+ list[index] = nextMessage;
1351
+ }
1099
1352
  emitQueueUpdate(runtime) {
1100
1353
  this.queueState(runtime);
1101
1354
  runtime.runner.emitWorkEvent({
@@ -1236,10 +1489,11 @@ class ConversationKernel {
1236
1489
  await Promise.resolve();
1237
1490
  if (runtime.runId !== runId || runtime.stopRequestedRunId === runId)
1238
1491
  return true;
1239
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0)
1492
+ const hasSteering = runtime.pendingNextTurn.some(item => item.queueMode === 'steer');
1493
+ if (hasSteering || runtime.runner.subagents.readRootInbox().length > 0)
1240
1494
  return false;
1241
1495
  runtime.guideAcceptanceClosedRunId = runId;
1242
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) {
1496
+ if (runtime.pendingNextTurn.some(item => item.queueMode === 'steer') || runtime.runner.subagents.readRootInbox().length > 0) {
1243
1497
  runtime.guideAcceptanceClosedRunId = '';
1244
1498
  return false;
1245
1499
  }
@@ -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, ConversationQueueActionInput } 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,7 @@ 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?: ConversationQueueActionInput): Promise<Record<string, unknown>>;
97
99
  checkpoint(): Promise<Record<string, unknown>>;
98
100
  contextCompress(options?: {
99
101
  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, ConversationQueueActionInput } 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,7 @@ 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?: ConversationQueueActionInput): Promise<Record<string, unknown>>;
13
15
  checkpoint(): Promise<Record<string, unknown>>;
14
16
  contextCompress?(options?: {
15
17
  keepRecent?: number;
@@ -71,6 +73,7 @@ export declare class ElectronUtilityRuntimePool {
71
73
  rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<UtilityConversationRewindResult>;
72
74
  requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<ElectronPoolStopResult>;
73
75
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
76
+ queueAction(target: ConversationRuntimeTarget, action: ConversationQueueAction, input?: ConversationQueueActionInput): Promise<Record<string, unknown>>;
74
77
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
75
78
  contextCompress(target: ConversationRuntimeTarget, options?: {
76
79
  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, ConversationQueueActionInput, 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,14 @@ 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?: ConversationQueueActionInput;
110
+ };
99
111
  } | {
100
112
  id: string;
101
113
  method: 'checkpoint';
@@ -210,6 +222,16 @@ export interface UtilityAgentSnapshotResult {
210
222
  steering: string[];
211
223
  followUp: string[];
212
224
  };
225
+ queueItems?: Array<{
226
+ id: string;
227
+ text: string;
228
+ queueMode: 'steer' | 'followUp';
229
+ requestedMode?: string;
230
+ goalObjective?: string;
231
+ runId?: string;
232
+ createdAt: string;
233
+ }>;
234
+ queuePaused?: boolean;
213
235
  workEvents: AgentWorkEvent[];
214
236
  [key: string]: unknown;
215
237
  }
@@ -126,6 +126,24 @@ function createUtilityHostToolHandler(options) {
126
126
  throwIfAborted(signal);
127
127
  return result;
128
128
  }
129
+ if (request.tool === 'screen_capture') {
130
+ const args = request.args || {};
131
+ const target = String(args.target || '').toLowerCase() === 'application' ? 'application' : 'desktop';
132
+ const result = await (options.runComputer || computerUse_1.runComputerUse)({
133
+ action: target === 'application' ? 'app_observe' : 'observe',
134
+ appTarget: String(args.app_target || args.appTarget || ''),
135
+ windowHandle: target === 'application' ? String(args.app_target || args.appTarget || '') : '',
136
+ maxChars: Number(args.max_chars || args.maxChars || 30_000),
137
+ captureMaxWidth: Number(args.capture_max_width || args.captureMaxWidth),
138
+ captureMaxHeight: Number(args.capture_max_height || args.captureMaxHeight),
139
+ allowEphemeralVisionImage: request.context.allowEphemeralVisionImage === true,
140
+ workspacePath: request.target.workspacePath,
141
+ invocation: 'agent',
142
+ ownerId: `screen-capture:${request.target.runtimeKey}:${String(request.context.actorId || ROOT_AGENT_ACTOR_ID)}`,
143
+ });
144
+ throwIfAborted(signal);
145
+ return result;
146
+ }
129
147
  const args = request.args || {};
130
148
  const action = String(args.action || '').trim().toLowerCase();
131
149
  const trustedComputerUseContext = request.context;
@@ -1,4 +1,5 @@
1
1
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
2
+ import { ConversationQueueAction, ConversationQueueActionInput } from './conversationKernel';
2
3
  import { TerminalTakeoverEvent, TerminalTakeoverOwnerFilter, TerminalTakeoverState } from '../tools/terminalTakeover';
3
4
  import { WslAgentPromptRequest, WslAgentPromptResult, WslAutoRouteRatingResult, WslAgentWorkspace, WslAgentStopResult, WslConversationRewindResult, WslHostToolRequest } from './wslAgentProtocol';
4
5
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
@@ -71,6 +72,7 @@ export declare class WslAgentClient {
71
72
  snapshotTarget(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
72
73
  rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<WslConversationRewindResult>;
73
74
  enqueueGuide(target: ConversationRuntimeTarget, envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
75
+ queueAction(target: ConversationRuntimeTarget, action: ConversationQueueAction, input?: ConversationQueueActionInput): Promise<Record<string, unknown>>;
74
76
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
75
77
  contextCompress(target: ConversationRuntimeTarget, options?: {
76
78
  keepRecent?: number;
@@ -313,6 +313,10 @@ class WslAgentClient {
313
313
  await this.start();
314
314
  return await this.request('guide', { target: await this.mapTarget(target), envelope }, 5_000);
315
315
  }
316
+ async queueAction(target, action, input = {}) {
317
+ await this.start();
318
+ return await this.request('queue_action', { target: await this.mapTarget(target), action, input }, 5_000);
319
+ }
316
320
  async checkpoint(target) {
317
321
  await this.start();
318
322
  return await this.request('checkpoint', { target: await this.mapTarget(target) }, 5_000);
@@ -536,7 +540,7 @@ class WslAgentClient {
536
540
  else if (!this.hostToolHandler) {
537
541
  result = { requestId: request.requestId, ok: false, error: 'No Windows host tool handler is registered' };
538
542
  }
539
- else if (!['browser_control', 'computer_use', 'browser_use', 'automation', 'terminal_takeover'].includes(request.tool)) {
543
+ else if (!['browser_control', 'screen_capture', 'computer_use', 'browser_use', 'automation', 'terminal_takeover'].includes(request.tool)) {
540
544
  result = { requestId: request.requestId, ok: false, error: `WSL host tool is not allowed: ${String(request.tool)}` };
541
545
  }
542
546
  else {
@@ -1,5 +1,5 @@
1
1
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
2
- import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationStopResult } from './conversationKernel';
2
+ import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueAction, ConversationQueueActionInput, ConversationQueueMode, ConversationStopResult } from './conversationKernel';
3
3
  import { ConversationRuntimeTarget } from './conversationTarget';
4
4
  import { TerminalTakeoverEvent, TerminalTakeoverOwnerFilter, TerminalTakeoverState } from '../tools/terminalTakeover';
5
5
  import { BrowserUseRequest } from './browserUse';
@@ -42,6 +42,9 @@ interface WslHostToolRequestBase {
42
42
  export type WslHostToolRequest = (WslHostToolRequestBase & {
43
43
  tool: 'browser_control';
44
44
  args: BrowserControlRequest;
45
+ }) | (WslHostToolRequestBase & {
46
+ tool: 'screen_capture';
47
+ args: Record<string, unknown>;
45
48
  }) | (WslHostToolRequestBase & {
46
49
  tool: 'computer_use';
47
50
  args: Record<string, unknown>;
@@ -108,6 +111,14 @@ export type WslAgentRequest = {
108
111
  target: ConversationRuntimeTarget;
109
112
  envelope: ConversationInputEnvelope;
110
113
  };
114
+ } | {
115
+ id: string;
116
+ method: 'queue_action';
117
+ params: {
118
+ target: ConversationRuntimeTarget;
119
+ action: ConversationQueueAction;
120
+ input?: ConversationQueueActionInput;
121
+ };
111
122
  } | {
112
123
  id: string;
113
124
  method: 'checkpoint';
@@ -1,4 +1,5 @@
1
1
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
2
+ import { ConversationQueueAction, ConversationQueueActionInput } from './conversationKernel';
2
3
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
3
4
  import { WslHostToolHandler } from './wslAgentClient';
4
5
  import { WslAgentPromptRequest, WslAgentPromptResult, WslAutoRouteRatingResult, WslAgentStopResult, WslConversationRewindResult } from './wslAgentProtocol';
@@ -10,6 +11,7 @@ export interface WslTargetRuntimeClient {
10
11
  rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<WslConversationRewindResult>;
11
12
  requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<WslAgentStopResult>;
12
13
  enqueueGuide(target: ConversationRuntimeTarget, envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
14
+ queueAction?(target: ConversationRuntimeTarget, action: ConversationQueueAction, input?: ConversationQueueActionInput): Promise<Record<string, unknown>>;
13
15
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
14
16
  contextCompress?(target: ConversationRuntimeTarget, options?: {
15
17
  keepRecent?: number;
@@ -73,6 +75,7 @@ export declare class WslAgentRuntimePool {
73
75
  rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<WslConversationRewindResult>;
74
76
  requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<WslPoolStopResult>;
75
77
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
78
+ queueAction(target: ConversationRuntimeTarget, action: ConversationQueueAction, input?: ConversationQueueActionInput): Promise<Record<string, unknown>>;
76
79
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
77
80
  contextCompress(target: ConversationRuntimeTarget, options?: {
78
81
  keepRecent?: number;
@@ -195,6 +195,17 @@ class WslAgentRuntimePool {
195
195
  this.release(entry, true);
196
196
  }
197
197
  }
198
+ async queueAction(target, action, input = {}) {
199
+ const entry = await this.acquireExisting(target);
200
+ if (!entry || !entry.client.queueAction)
201
+ throw new Error('Target conversation is not running');
202
+ try {
203
+ return await entry.client.queueAction(entry.target, action, input);
204
+ }
205
+ finally {
206
+ this.release(entry, true);
207
+ }
208
+ }
198
209
  async checkpoint(target) {
199
210
  const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
200
211
  const entry = await this.acquireExisting(normalized);