newmark-agent 0.4.6 → 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/assets/app-icon-dark.svg +6 -0
- package/dist/assets/app-icon-dark.svg +6 -0
- package/dist/cli-commands.js +2 -1
- package/dist/cli-discovery.js +2 -0
- package/dist/conversation-utility-host.bundle.cjs +577 -166
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +40 -4
- package/dist/core/agent.js +251 -49
- package/dist/core/agentKernelRunner.d.ts +3 -0
- package/dist/core/agentKernelRunner.js +74 -91
- package/dist/core/config.js +1 -1
- 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/installUpdate.js +11 -8
- package/dist/core/mobilePairing.d.ts +1 -0
- package/dist/core/mobilePairing.js +15 -1
- package/dist/core/subagent.d.ts +10 -3
- package/dist/core/subagent.js +23 -8
- package/dist/core/toolPolicy.js +14 -3
- 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/launcher.js +14 -11
- package/dist/main.js +207 -9
- package/dist/providers/chat-completions.adapter.js +6 -2
- package/dist/providers/responses.adapter.js +1 -0
- package/dist/server.d.ts +33 -1
- package/dist/server.js +696 -48
- package/dist/toolchain/registry-seeder.js +3 -1
- package/dist/tools/index.js +57 -6
- package/dist/tools/nativeTools.js +2 -1
- package/dist/ui/index.html +88 -101
- package/dist/wsl-agent-host.bundle.cjs +577 -166
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +4 -2
|
@@ -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);
|
|
@@ -527,7 +527,7 @@ async function applyGitHubUpdate(options) {
|
|
|
527
527
|
};
|
|
528
528
|
}
|
|
529
529
|
}
|
|
530
|
-
const NEWMARK_PROCESS_NAMES = ['Newmark Agent.exe', 'Newmark.exe'];
|
|
530
|
+
const NEWMARK_PROCESS_NAMES = ['Newmark Agent.exe', 'Newmark.exe', 'Newmark Console Runtime.exe'];
|
|
531
531
|
const NEWMARK_LEGACY_EXECUTABLES = new Set(['newmark.exe', 'newmark agent.exe']);
|
|
532
532
|
function normalizeWindowsPathForCompare(value) {
|
|
533
533
|
return path.resolve(String(value)).toLowerCase();
|
|
@@ -547,7 +547,8 @@ function runPowerShellJson(script) {
|
|
|
547
547
|
return Array.isArray(parsed) ? parsed.map(item => item) : [parsed];
|
|
548
548
|
}
|
|
549
549
|
function listRunningNewmarkProcesses() {
|
|
550
|
-
const
|
|
550
|
+
const nameFilter = NEWMARK_PROCESS_NAMES.map(name => `$_.Name -eq '${name.replace(/'/g, "''")}'`).join(' -or ');
|
|
551
|
+
const rows = runPowerShellJson(`Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { ${nameFilter} } | Select-Object ProcessId,Name,ExecutablePath | ConvertTo-Json -Compress`);
|
|
551
552
|
const skipPid = Number(process.env.NEWMARK_SKIP_PROCESS_PID || process.pid);
|
|
552
553
|
return rows
|
|
553
554
|
.map(row => ({
|
|
@@ -621,26 +622,28 @@ function runElevatedMsiExec(args) {
|
|
|
621
622
|
function uninstallNewmarkProduct(productCode, logPath) {
|
|
622
623
|
const args = ['/x', productCode, '/qn', '/norestart', '/l*v', logPath];
|
|
623
624
|
let result = runMsiExec(args);
|
|
624
|
-
if (result.exitCode !== 0)
|
|
625
|
+
if (result.exitCode !== 0 && result.exitCode !== 3010)
|
|
625
626
|
result = runElevatedMsiExec(args);
|
|
627
|
+
const ok = result.exitCode === 0 || result.exitCode === 3010;
|
|
626
628
|
return {
|
|
627
|
-
ok
|
|
629
|
+
ok,
|
|
628
630
|
exitCode: result.exitCode,
|
|
629
631
|
logPath,
|
|
630
|
-
error:
|
|
632
|
+
error: ok ? undefined : `msiexec uninstall exited ${result.exitCode}`,
|
|
631
633
|
};
|
|
632
634
|
}
|
|
633
635
|
function installMsiPackage(msiPath, options = {}) {
|
|
634
636
|
const logPath = path.join(options.logDir || os.tmpdir(), `newmark-msi-install-${process.pid}-${Date.now()}.log`);
|
|
635
637
|
const args = ['/i', path.resolve(msiPath), '/qn', '/norestart', '/l*v', logPath];
|
|
636
638
|
let result = runMsiExec(args);
|
|
637
|
-
if (result.exitCode !== 0 && options.allowElevate !== false)
|
|
639
|
+
if (result.exitCode !== 0 && result.exitCode !== 3010 && options.allowElevate !== false)
|
|
638
640
|
result = runElevatedMsiExec(args);
|
|
641
|
+
const ok = result.exitCode === 0 || result.exitCode === 3010;
|
|
639
642
|
return {
|
|
640
|
-
ok
|
|
643
|
+
ok,
|
|
641
644
|
exitCode: result.exitCode,
|
|
642
645
|
logPath,
|
|
643
|
-
error:
|
|
646
|
+
error: ok ? undefined : `msiexec install exited ${result.exitCode}`,
|
|
644
647
|
};
|
|
645
648
|
}
|
|
646
649
|
function findLegacyNewmarkExecutables(excludeRoots = []) {
|
|
@@ -25,6 +25,7 @@ export interface PairingStatus {
|
|
|
25
25
|
}
|
|
26
26
|
export declare function ensureMobileToken(root: string): string;
|
|
27
27
|
export declare function tailscaleIpv4(): string | null;
|
|
28
|
+
export declare function lanIpv4(): string | null;
|
|
28
29
|
export declare function pairingHost(): string;
|
|
29
30
|
export declare function createPairingSession(root: string, ttlMs?: number): PairingSession;
|
|
30
31
|
export declare function pairingUrl(root: string): string;
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.MOBILE_PAIRING_TTL_MS = exports.MOBILE_PORT = exports.MOBILE_PAIRING_FILENAME = exports.MOBILE_TOKEN_FILENAME = void 0;
|
|
37
37
|
exports.ensureMobileToken = ensureMobileToken;
|
|
38
38
|
exports.tailscaleIpv4 = tailscaleIpv4;
|
|
39
|
+
exports.lanIpv4 = lanIpv4;
|
|
39
40
|
exports.pairingHost = pairingHost;
|
|
40
41
|
exports.createPairingSession = createPairingSession;
|
|
41
42
|
exports.pairingUrl = pairingUrl;
|
|
@@ -105,8 +106,21 @@ function tailscaleIpv4() {
|
|
|
105
106
|
return null;
|
|
106
107
|
}
|
|
107
108
|
}
|
|
109
|
+
function lanIpv4() {
|
|
110
|
+
const interfaces = os.networkInterfaces();
|
|
111
|
+
const candidates = [];
|
|
112
|
+
for (const name of Object.keys(interfaces)) {
|
|
113
|
+
for (const info of interfaces[name] || []) {
|
|
114
|
+
if (info.family !== 'IPv4' || info.internal)
|
|
115
|
+
continue;
|
|
116
|
+
if (/^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(info.address))
|
|
117
|
+
candidates.push(info.address);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return candidates.sort()[0] || null;
|
|
121
|
+
}
|
|
108
122
|
function pairingHost() {
|
|
109
|
-
return tailscaleIpv4() || '127.0.0.1';
|
|
123
|
+
return tailscaleIpv4() || lanIpv4() || '127.0.0.1';
|
|
110
124
|
}
|
|
111
125
|
function buildPairingUrl(session) {
|
|
112
126
|
const query = new URLSearchParams({
|
package/dist/core/subagent.d.ts
CHANGED
|
@@ -54,6 +54,10 @@ export interface SubagentInstance {
|
|
|
54
54
|
name: string;
|
|
55
55
|
conversationId: string;
|
|
56
56
|
createdByAgentId: string;
|
|
57
|
+
/** Root Build Block that created this peer. Empty only for legacy/direct API records. */
|
|
58
|
+
buildRunId?: string;
|
|
59
|
+
/** Intelligence tier captured at creation so the enforced 4/16 ceiling is auditable. */
|
|
60
|
+
intelligenceTier?: string;
|
|
57
61
|
prompt: string;
|
|
58
62
|
model: string;
|
|
59
63
|
inputMode: string;
|
|
@@ -172,7 +176,7 @@ export declare class SubagentManager {
|
|
|
172
176
|
private running;
|
|
173
177
|
private schedulingPaused;
|
|
174
178
|
private nextSequence;
|
|
175
|
-
private
|
|
179
|
+
private concurrency;
|
|
176
180
|
private executor?;
|
|
177
181
|
private onChange?;
|
|
178
182
|
private persist?;
|
|
@@ -186,9 +190,9 @@ export declare class SubagentManager {
|
|
|
186
190
|
hasRecords(): boolean;
|
|
187
191
|
reset(): void;
|
|
188
192
|
constructor(options?: SubagentManagerOptions);
|
|
189
|
-
bind(options: Pick<SubagentManagerOptions, 'executor' | 'onChange' | 'persist' | 'onMailboxMessage' | 'onRootInboxMessage' | 'onSettled'>): void;
|
|
193
|
+
bind(options: Pick<SubagentManagerOptions, 'concurrency' | 'executor' | 'onChange' | 'persist' | 'onMailboxMessage' | 'onRootInboxMessage' | 'onSettled'>): void;
|
|
190
194
|
removeRootInboxListener(listener: (message: SubagentRootMessage) => boolean): void;
|
|
191
|
-
create(name: string, prompt: string, model?: string, inputMode?: string, agentMode?: AgentMode, createdByAgentId?: string, flowName?: string, goalObjective?: string, flowPc?: number): string;
|
|
195
|
+
create(name: string, prompt: string, model?: string, inputMode?: string, agentMode?: AgentMode, createdByAgentId?: string, flowName?: string, goalObjective?: string, flowPc?: number, buildRunId?: string, intelligenceTier?: string): string;
|
|
192
196
|
get(id: string): SubagentInstance | undefined;
|
|
193
197
|
send(id: string, prompt: string): boolean;
|
|
194
198
|
sendMessage(fromAgentId: string, toAgentId: string, body: string, kind?: SubagentMessageKind, details?: {
|
|
@@ -231,6 +235,9 @@ export declare class SubagentManager {
|
|
|
231
235
|
boundedResultTranscript(idOrName: string): string;
|
|
232
236
|
listActive(): SubagentInstance[];
|
|
233
237
|
listAll(): SubagentInstance[];
|
|
238
|
+
activeCountForBuild(buildRunId: string): number;
|
|
239
|
+
setConcurrencyLimit(value: number): void;
|
|
240
|
+
concurrencyLimit(): number;
|
|
234
241
|
pauseScheduling(): void;
|
|
235
242
|
resumeScheduling(): void;
|
|
236
243
|
isSchedulingPaused(): boolean;
|
package/dist/core/subagent.js
CHANGED
|
@@ -93,6 +93,8 @@ class SubagentManager {
|
|
|
93
93
|
queueMicrotask(() => this.pump());
|
|
94
94
|
}
|
|
95
95
|
bind(options) {
|
|
96
|
+
if (options.concurrency !== undefined)
|
|
97
|
+
this.setConcurrencyLimit(options.concurrency);
|
|
96
98
|
if (options.executor)
|
|
97
99
|
this.executor = options.executor;
|
|
98
100
|
if (options.onChange)
|
|
@@ -115,16 +117,16 @@ class SubagentManager {
|
|
|
115
117
|
removeRootInboxListener(listener) {
|
|
116
118
|
this.rootInboxListeners.delete(listener);
|
|
117
119
|
}
|
|
118
|
-
create(name, prompt, model, inputMode, agentMode = 'build', createdByAgentId = this.rootAgentId, flowName = '', goalObjective = '', flowPc = 0) {
|
|
120
|
+
create(name, prompt, model, inputMode, agentMode = 'build', createdByAgentId = this.rootAgentId, flowName = '', goalObjective = '', flowPc = 0, buildRunId = '', intelligenceTier = '') {
|
|
119
121
|
const id = (0, crypto_1.randomUUID)();
|
|
120
122
|
const shortId = id.replace(/-/g, '').slice(0, 8);
|
|
121
123
|
const slug = natureSlug(name);
|
|
122
|
-
// The
|
|
123
|
-
//
|
|
124
|
-
// the
|
|
125
|
-
|
|
126
|
-
const displayName =
|
|
127
|
-
const qualifiedName = `${
|
|
124
|
+
// The monitoring label is exactly the caller-created human-readable name.
|
|
125
|
+
// UUID-bearing identity stays in id/qualifiedName and is never appended to
|
|
126
|
+
// the right-sidebar title.
|
|
127
|
+
const createdName = String(name || 'SubAgent').replace(/\s+/g, ' ').trim().slice(0, 160) || 'SubAgent';
|
|
128
|
+
const displayName = createdName;
|
|
129
|
+
const qualifiedName = `${slug}--${id}`;
|
|
128
130
|
const stamp = now();
|
|
129
131
|
const record = {
|
|
130
132
|
id,
|
|
@@ -132,9 +134,11 @@ class SubagentManager {
|
|
|
132
134
|
natureSlug: slug,
|
|
133
135
|
displayName,
|
|
134
136
|
qualifiedName,
|
|
135
|
-
name:
|
|
137
|
+
name: createdName,
|
|
136
138
|
conversationId: this.conversationId,
|
|
137
139
|
createdByAgentId,
|
|
140
|
+
buildRunId: String(buildRunId || '').trim() || undefined,
|
|
141
|
+
intelligenceTier: String(intelligenceTier || '').trim() || undefined,
|
|
138
142
|
prompt,
|
|
139
143
|
model: model || 'default',
|
|
140
144
|
inputMode: inputMode || 'guide',
|
|
@@ -504,6 +508,17 @@ class SubagentManager {
|
|
|
504
508
|
}
|
|
505
509
|
listActive() { return this.listAll().filter(item => item.status !== 'closed'); }
|
|
506
510
|
listAll() { return [...this.subs.values()].map(cloneRecord); }
|
|
511
|
+
activeCountForBuild(buildRunId) {
|
|
512
|
+
const target = String(buildRunId || '').trim();
|
|
513
|
+
if (!target)
|
|
514
|
+
return 0;
|
|
515
|
+
return [...this.subs.values()].filter(record => record.buildRunId === target && (record.status === 'queued' || record.status === 'working')).length;
|
|
516
|
+
}
|
|
517
|
+
setConcurrencyLimit(value) {
|
|
518
|
+
this.concurrency = Math.max(1, Math.min(16, Math.floor(Number(value) || 4)));
|
|
519
|
+
this.pump();
|
|
520
|
+
}
|
|
521
|
+
concurrencyLimit() { return this.concurrency; }
|
|
507
522
|
pauseScheduling() {
|
|
508
523
|
if (this.schedulingPaused)
|
|
509
524
|
return;
|
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',
|
|
@@ -26,6 +27,10 @@ const MODE_SCOPED_TOOLS = new Set([
|
|
|
26
27
|
'task_read',
|
|
27
28
|
'task_create',
|
|
28
29
|
'question',
|
|
30
|
+
'SubAgent',
|
|
31
|
+
'subagent_create',
|
|
32
|
+
// Legacy runtime alias. It is no longer published to models because its
|
|
33
|
+
// generic name collides with the persistent task checklist.
|
|
29
34
|
'task',
|
|
30
35
|
'subagent_list',
|
|
31
36
|
'subagent_read',
|
|
@@ -38,6 +43,7 @@ const MODE_SCOPED_TOOLS = new Set([
|
|
|
38
43
|
'branch_create',
|
|
39
44
|
]);
|
|
40
45
|
const PLAN_READ_ONLY_TOOLS = new Set([
|
|
46
|
+
'screen_capture',
|
|
41
47
|
'task_read',
|
|
42
48
|
'pwd',
|
|
43
49
|
'read',
|
|
@@ -59,6 +65,8 @@ const PLAN_READ_ONLY_TOOLS = new Set([
|
|
|
59
65
|
'skill',
|
|
60
66
|
'linked_plan',
|
|
61
67
|
'build_history_query',
|
|
68
|
+
'SubAgent',
|
|
69
|
+
'subagent_create',
|
|
62
70
|
'task',
|
|
63
71
|
'subagent_list',
|
|
64
72
|
'subagent_read',
|
|
@@ -76,9 +84,10 @@ const PLAN_BROWSER_USE_ACTION_SET = new Set(exports.PLAN_BROWSER_USE_ACTIONS);
|
|
|
76
84
|
/**
|
|
77
85
|
* 并发安全工具集合(DSH isConcurrencySafe 语义的 Newmark 落地)。
|
|
78
86
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
87
|
+
* 确定性无副作用的只读工具允许与兄弟 tool call 并发执行。`SubAgent` 创建是
|
|
88
|
+
* 唯一允许并发的受控写操作:每个调用只追加一个独立 UUID 记录,真正的 worker
|
|
89
|
+
* 并发由 SubagentManager 的 4/16 槽位及 Build Block 硬上限管理。其他写入、shell、
|
|
90
|
+
* 浏览器交互、子代理发送/关闭等操作仍保持独占串行。缺省保守:不在集合中的
|
|
82
91
|
* 工具一律视为独占。
|
|
83
92
|
*
|
|
84
93
|
* 注意:read/grep/glob/pwd 是同一进程内的内存/文件系统只读,可安全重叠;
|
|
@@ -96,6 +105,8 @@ const CONCURRENCY_SAFE_TOOLS = new Set([
|
|
|
96
105
|
'git_status',
|
|
97
106
|
'file_audit',
|
|
98
107
|
'repo_security_audit',
|
|
108
|
+
'screen_capture',
|
|
109
|
+
'SubAgent',
|
|
99
110
|
]);
|
|
100
111
|
/** 判断一个工具是否可参与并行调度。缺省 false(独占)。
|
|
101
112
|
* 优先看 toolchain registry 推断的 riskLevel('read' 工具天然并发安全,
|