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.
- package/dist/conversation-utility-host.bundle.cjs +287 -18
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +2 -0
- package/dist/core/agent.js +20 -0
- package/dist/core/agentKernelRunner.js +27 -10
- package/dist/core/conversationKernel.d.ts +40 -0
- package/dist/core/conversationKernel.js +219 -8
- package/dist/core/electronUtilityAgentClient.d.ts +8 -0
- package/dist/core/electronUtilityAgentClient.js +5 -1
- package/dist/core/electronUtilityRuntimePool.d.ts +15 -0
- package/dist/core/electronUtilityRuntimePool.js +11 -0
- package/dist/core/toolPolicy.js +3 -0
- package/dist/core/utilityAgentProtocol.d.ts +29 -1
- package/dist/core/utilityHostToolRouter.js +18 -0
- package/dist/core/wslAgentClient.d.ts +8 -0
- package/dist/core/wslAgentClient.js +5 -1
- package/dist/core/wslAgentProtocol.d.ts +18 -1
- package/dist/core/wslAgentRuntimePool.d.ts +15 -0
- package/dist/core/wslAgentRuntimePool.js +11 -0
- package/dist/main.js +193 -7
- package/dist/server.d.ts +32 -1
- package/dist/server.js +157 -35
- package/dist/tools/index.js +46 -1
- package/dist/tools/nativeTools.js +1 -0
- package/dist/wsl-agent-host.bundle.cjs +287 -18
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +2 -2
|
@@ -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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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);
|
package/dist/core/toolPolicy.js
CHANGED
|
@@ -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
|
}
|