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.
Files changed (43) hide show
  1. package/assets/app-icon-dark.svg +6 -0
  2. package/dist/assets/app-icon-dark.svg +6 -0
  3. package/dist/cli-commands.js +2 -1
  4. package/dist/cli-discovery.js +2 -0
  5. package/dist/conversation-utility-host.bundle.cjs +577 -166
  6. package/dist/conversation-utility-host.js +3 -0
  7. package/dist/core/agent.d.ts +40 -4
  8. package/dist/core/agent.js +251 -49
  9. package/dist/core/agentKernelRunner.d.ts +3 -0
  10. package/dist/core/agentKernelRunner.js +74 -91
  11. package/dist/core/config.js +1 -1
  12. package/dist/core/conversationKernel.d.ts +40 -0
  13. package/dist/core/conversationKernel.js +219 -8
  14. package/dist/core/electronUtilityAgentClient.d.ts +8 -0
  15. package/dist/core/electronUtilityAgentClient.js +5 -1
  16. package/dist/core/electronUtilityRuntimePool.d.ts +15 -0
  17. package/dist/core/electronUtilityRuntimePool.js +11 -0
  18. package/dist/core/installUpdate.js +11 -8
  19. package/dist/core/mobilePairing.d.ts +1 -0
  20. package/dist/core/mobilePairing.js +15 -1
  21. package/dist/core/subagent.d.ts +10 -3
  22. package/dist/core/subagent.js +23 -8
  23. package/dist/core/toolPolicy.js +14 -3
  24. package/dist/core/utilityAgentProtocol.d.ts +29 -1
  25. package/dist/core/utilityHostToolRouter.js +18 -0
  26. package/dist/core/wslAgentClient.d.ts +8 -0
  27. package/dist/core/wslAgentClient.js +5 -1
  28. package/dist/core/wslAgentProtocol.d.ts +18 -1
  29. package/dist/core/wslAgentRuntimePool.d.ts +15 -0
  30. package/dist/core/wslAgentRuntimePool.js +11 -0
  31. package/dist/launcher.js +14 -11
  32. package/dist/main.js +207 -9
  33. package/dist/providers/chat-completions.adapter.js +6 -2
  34. package/dist/providers/responses.adapter.js +1 -0
  35. package/dist/server.d.ts +33 -1
  36. package/dist/server.js +696 -48
  37. package/dist/toolchain/registry-seeder.js +3 -1
  38. package/dist/tools/index.js +57 -6
  39. package/dist/tools/nativeTools.js +2 -1
  40. package/dist/ui/index.html +88 -101
  41. package/dist/wsl-agent-host.bundle.cjs +577 -166
  42. package/dist/wsl-agent-host.js +3 -0
  43. 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
- this.clearQueued(runtime);
824
+ if (!runtime.pendingNextTurn.length)
825
+ this.clearQueued(runtime);
656
826
  const completedRunId = runtime.runId;
657
827
  runtime.runner.finishConversationWorkRun(completedRunId, 'completed');
658
828
  // A Guide can be submitted synchronously by a consumer of the public
@@ -662,6 +832,7 @@ class ConversationKernel {
662
832
  await new Promise(resolve => setImmediate(resolve));
663
833
  if (runtime.runId === completedRunId
664
834
  && runtime.stopRequestedRunId !== completedRunId
835
+ && !runtime.queuePaused
665
836
  && runtime.pendingNextTurn.length > 0) {
666
837
  runtime.guideAcceptanceClosedRunId = '';
667
838
  if (!runtime.runner.resumeConversationWorkRun(completedRunId)) {
@@ -757,6 +928,7 @@ class ConversationKernel {
757
928
  events: [],
758
929
  pendingNextTurn: [],
759
930
  queued: { steering: [], followUp: [] },
931
+ queuePaused: false,
760
932
  runId: '',
761
933
  generation: this.generations.get(target.runtimeKey) || 0,
762
934
  stopRequestedRunId: '',
@@ -874,7 +1046,7 @@ class ConversationKernel {
874
1046
  }
875
1047
  setImmediate(() => {
876
1048
  runtime.pendingContinuationRunId = undefined;
877
- if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId)
1049
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused)
878
1050
  return;
879
1051
  const next = runtime.pendingNextTurn.shift();
880
1052
  if (!next)
@@ -1040,8 +1212,39 @@ class ConversationKernel {
1040
1212
  const isSteer = queueMode === 'steer';
1041
1213
  const text = typeof message === 'string' ? message : message.text;
1042
1214
  const prompt = isSteer ? text : `[Next queued while current turn is running]\n${text}`;
1043
- if (!isSteer)
1044
- this.trackQueuedMessage(runtime, prompt, queueMode);
1215
+ if (!isSteer) {
1216
+ const structured = typeof message === 'string' ? null : message;
1217
+ const clientMessageId = String(structured?.clientMessageId || (0, crypto_1.randomUUID)());
1218
+ const queuedMessage = {
1219
+ ...(structured || {}),
1220
+ text: prompt,
1221
+ visibleUserInput: structured?.visibleUserInput || text,
1222
+ visibleMode: structured?.visibleMode || runtime.options.mode,
1223
+ clientMessageId,
1224
+ runId: structured?.runId || runtime.runId,
1225
+ createdAt: String(structured?.createdAt || new Date().toISOString()),
1226
+ };
1227
+ if (!runtime.pendingNextTurn.some(item => typeof item.message !== 'string' && item.message.clientMessageId === clientMessageId)) {
1228
+ runtime.pendingNextTurn.push({ message: queuedMessage, queueMode: 'followUp' });
1229
+ }
1230
+ runtime.runner.retainConversationContinuations([{
1231
+ content: prompt,
1232
+ queueMode: 'followUp',
1233
+ clientMessageId,
1234
+ runId: queuedMessage.runId,
1235
+ images: queuedMessage.images?.map(image => ({ ...image })),
1236
+ attachments: queuedMessage.attachments?.map(attachment => ({ ...attachment })),
1237
+ createdAt: queuedMessage.createdAt,
1238
+ }]);
1239
+ this.trackQueuedMessage(runtime, prompt, 'followUp');
1240
+ runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId
1241
+ ? 'Next message retained while stopping.'
1242
+ : 'Next message queued.');
1243
+ if (runtime.stopRequestedRunId === runtime.runId)
1244
+ runtime.forceStopArmedRunId = '';
1245
+ this.emitQueueUpdate(runtime);
1246
+ return;
1247
+ }
1045
1248
  if (runtime.stopRequestedRunId === runtime.runId) {
1046
1249
  runtime.forceStopArmedRunId = '';
1047
1250
  const structured = typeof message === 'string' ? undefined : message;
@@ -1096,6 +1299,13 @@ class ConversationKernel {
1096
1299
  if (changed)
1097
1300
  this.emitQueueUpdate(runtime);
1098
1301
  }
1302
+ replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
1303
+ this.queueState(runtime);
1304
+ const list = queueMode === 'steer' ? runtime.queued.steering : runtime.queued.followUp;
1305
+ const index = list.indexOf(oldMessage);
1306
+ if (index >= 0)
1307
+ list[index] = nextMessage;
1308
+ }
1099
1309
  emitQueueUpdate(runtime) {
1100
1310
  this.queueState(runtime);
1101
1311
  runtime.runner.emitWorkEvent({
@@ -1236,10 +1446,11 @@ class ConversationKernel {
1236
1446
  await Promise.resolve();
1237
1447
  if (runtime.runId !== runId || runtime.stopRequestedRunId === runId)
1238
1448
  return true;
1239
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0)
1449
+ const hasSteering = runtime.pendingNextTurn.some(item => item.queueMode === 'steer');
1450
+ if (hasSteering || runtime.runner.subagents.readRootInbox().length > 0)
1240
1451
  return false;
1241
1452
  runtime.guideAcceptanceClosedRunId = runId;
1242
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) {
1453
+ if (runtime.pendingNextTurn.some(item => item.queueMode === 'steer') || runtime.runner.subagents.readRootInbox().length > 0) {
1243
1454
  runtime.guideAcceptanceClosedRunId = '';
1244
1455
  return false;
1245
1456
  }
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { NormalizedConversationTarget } from './conversationTarget';
3
3
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
4
+ import { ConversationQueueAction } from './conversationKernel';
4
5
  import { UtilityAgentPromptResult, UtilityAutoRouteRatingResult, UtilityAgentSnapshotResult, UtilityAgentStopResult, UtilityConversationRewindResult, UtilityHostToolRequest, UtilityPromptRequest } from './utilityAgentProtocol';
5
6
  type WindowsProcessTreeHelperRuntime = {
6
7
  kind: 'precompiled' | 'runtime_compile';
@@ -94,6 +95,13 @@ export declare class ElectronUtilityAgentClient {
94
95
  rewind(messageIndex: number): Promise<UtilityConversationRewindResult>;
95
96
  requestStop(runId?: string): Promise<UtilityAgentStopResult>;
96
97
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
98
+ queueAction(action: ConversationQueueAction, input?: {
99
+ id?: string;
100
+ text?: string;
101
+ requestedMode?: string;
102
+ goalObjective?: string;
103
+ createdAt?: string;
104
+ }): Promise<Record<string, unknown>>;
97
105
  checkpoint(): Promise<Record<string, unknown>>;
98
106
  contextCompress(options?: {
99
107
  keepRecent?: number;
@@ -1023,6 +1023,10 @@ class ElectronUtilityAgentClient {
1023
1023
  await this.start();
1024
1024
  return await this.request('guide', { target: this.target, envelope }, 5_000);
1025
1025
  }
1026
+ async queueAction(action, input = {}) {
1027
+ await this.start();
1028
+ return await this.request('queue_action', { target: this.target, action, input }, 5_000);
1029
+ }
1026
1030
  async checkpoint() {
1027
1031
  await this.start();
1028
1032
  return await this.request('checkpoint', { target: this.target }, 5_000);
@@ -1334,7 +1338,7 @@ class ElectronUtilityAgentClient {
1334
1338
  let result;
1335
1339
  const controller = new AbortController();
1336
1340
  this.hostToolRuns.set(request.requestId, { generation, controller });
1337
- const allowed = new Set(['browser_control', 'browser_use', 'computer_use', 'automation', 'terminal_takeover']);
1341
+ const allowed = new Set(['browser_control', 'browser_use', 'screen_capture', 'computer_use', 'automation', 'terminal_takeover']);
1338
1342
  if (!allowed.has(request.tool)) {
1339
1343
  result = { requestId: request.requestId, ok: false, error: `Electron host tool is not allowed: ${String(request.tool)}` };
1340
1344
  }
@@ -1,4 +1,5 @@
1
1
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
2
+ import { ConversationQueueAction } from './conversationKernel';
2
3
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
3
4
  import { UtilityHostToolHandler } from './electronUtilityAgentClient';
4
5
  import { UtilityAgentPromptResult, UtilityAutoRouteRatingResult, UtilityConversationRewindResult, UtilityAgentSnapshotResult, UtilityAgentStopResult, UtilityPromptRequest } from './utilityAgentProtocol';
@@ -10,6 +11,13 @@ export interface ElectronTargetRuntimeClient {
10
11
  rewind(messageIndex: number): Promise<UtilityConversationRewindResult>;
11
12
  requestStop(runId?: string): Promise<UtilityAgentStopResult>;
12
13
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
14
+ queueAction?(action: ConversationQueueAction, input?: {
15
+ id?: string;
16
+ text?: string;
17
+ requestedMode?: string;
18
+ goalObjective?: string;
19
+ createdAt?: string;
20
+ }): Promise<Record<string, unknown>>;
13
21
  checkpoint(): Promise<Record<string, unknown>>;
14
22
  contextCompress?(options?: {
15
23
  keepRecent?: number;
@@ -71,6 +79,13 @@ export declare class ElectronUtilityRuntimePool {
71
79
  rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<UtilityConversationRewindResult>;
72
80
  requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<ElectronPoolStopResult>;
73
81
  enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
82
+ queueAction(target: ConversationRuntimeTarget, action: ConversationQueueAction, input?: {
83
+ id?: string;
84
+ text?: string;
85
+ requestedMode?: string;
86
+ goalObjective?: string;
87
+ createdAt?: string;
88
+ }): Promise<Record<string, unknown>>;
74
89
  checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
75
90
  contextCompress(target: ConversationRuntimeTarget, options?: {
76
91
  keepRecent?: number;
@@ -184,6 +184,17 @@ class ElectronUtilityRuntimePool {
184
184
  this.release(entry, true);
185
185
  }
186
186
  }
187
+ async queueAction(target, action, input = {}) {
188
+ const entry = await this.acquireExisting(target);
189
+ if (!entry || !entry.client.queueAction)
190
+ throw new Error('Target conversation is not running');
191
+ try {
192
+ return await entry.client.queueAction(action, input);
193
+ }
194
+ finally {
195
+ this.release(entry, true);
196
+ }
197
+ }
187
198
  async checkpoint(target) {
188
199
  const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
189
200
  const entry = await this.acquireExisting(normalized);
@@ -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 rows = runPowerShellJson(`Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq 'Newmark Agent.exe' -or $_.Name -eq 'Newmark.exe' } | Select-Object ProcessId,Name,ExecutablePath | ConvertTo-Json -Compress`);
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: result.exitCode === 0,
629
+ ok,
628
630
  exitCode: result.exitCode,
629
631
  logPath,
630
- error: result.exitCode === 0 ? undefined : `msiexec uninstall exited ${result.exitCode}`,
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: result.exitCode === 0,
643
+ ok,
641
644
  exitCode: result.exitCode,
642
645
  logPath,
643
- error: result.exitCode === 0 ? undefined : `msiexec install exited ${result.exitCode}`,
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({
@@ -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 readonly concurrency;
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;
@@ -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 Agent-facing name is the caller's stable, human-readable label and is
123
- // deliberately decoupled from the identity. The UUID and its short form are
124
- // the only identity-bearing fields; the UI renders the short-id-qualified
125
- // display name while Agent tool interactions accept both name and id.
126
- const displayName = `${slug}-${shortId}`;
127
- const qualifiedName = `${displayName}--${id}`;
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: slug,
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;
@@ -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
- * 只有确定性无副作用的只读工具才允许与兄弟 tool call 并发执行;任何写入、
80
- * shell 命令、浏览器交互、子代理编排、以及会改变对话/文件/外部状态的工具
81
- * 都保持独占串行,避免盲目 Promise.all 引发竞态。缺省保守:不在集合中的
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' 工具天然并发安全,