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.
- package/dist/conversation-utility-host.bundle.cjs +407 -36
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +3 -0
- package/dist/core/agent.js +122 -10
- package/dist/core/agentKernelRunner.js +28 -11
- package/dist/core/autoRouter.d.ts +1 -1
- package/dist/core/autoRouter.js +16 -7
- package/dist/core/conversationKernel.d.ts +43 -0
- package/dist/core/conversationKernel.js +262 -8
- package/dist/core/electronUtilityAgentClient.d.ts +2 -0
- package/dist/core/electronUtilityAgentClient.js +5 -1
- package/dist/core/electronUtilityRuntimePool.d.ts +3 -0
- package/dist/core/electronUtilityRuntimePool.js +11 -0
- package/dist/core/toolPolicy.js +3 -0
- package/dist/core/utilityAgentProtocol.d.ts +23 -1
- package/dist/core/utilityHostToolRouter.js +18 -0
- package/dist/core/wslAgentClient.d.ts +2 -0
- package/dist/core/wslAgentClient.js +5 -1
- package/dist/core/wslAgentProtocol.d.ts +12 -1
- package/dist/core/wslAgentRuntimePool.d.ts +3 -0
- package/dist/core/wslAgentRuntimePool.js +11 -0
- package/dist/main.js +226 -13
- package/dist/preload.js +2 -0
- package/dist/server.d.ts +47 -1
- package/dist/server.js +304 -38
- package/dist/tools/index.js +46 -1
- package/dist/tools/nativeTools.js +1 -0
- package/dist/ui/index.html +150 -14
- package/dist/wsl-agent-host.bundle.cjs +407 -36
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +5 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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);
|
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, 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);
|