newmark-agent 0.4.6 → 0.4.7

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.
@@ -168,7 +168,7 @@ function prepareAssistantToolVisibility(agent, definitions) {
168
168
  const names = definitions.map(toolDefinitionName);
169
169
  brokerOnlyAssistantBuffers.set(agent, {
170
170
  brokerOnly: names.includes(TOOL_PROVISION_NAME)
171
- && names.every(name => name === TOOL_PROVISION_NAME || name === 'skill' || SUBAGENT_CORE_TOOL_NAMES.has(name)),
171
+ && names.every(name => name === TOOL_PROVISION_NAME || name === 'skill' || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(name)),
172
172
  pending: [],
173
173
  released: false,
174
174
  });
@@ -501,10 +501,10 @@ async function runAgentKernel(agent) {
501
501
  const requestStartedAt = Date.now();
502
502
  let firstTokenRecorded = false;
503
503
  // A broker-equivalent surface exposes no real task tool beyond the
504
- // always-available skill discovery and subagent orchestration core, so
504
+ // always-available skill discovery and Agent control cores, so
505
505
  // any text prefacing a broker call is treated as an internal preface.
506
506
  const tools = context.tools || [];
507
- const brokerOnlySurface = tools.length > 0 && tools.every(tool => tool.name === TOOL_PROVISION_NAME || tool.name === 'skill' || SUBAGENT_CORE_TOOL_NAMES.has(tool.name));
507
+ const brokerOnlySurface = tools.length > 0 && tools.every(tool => tool.name === TOOL_PROVISION_NAME || tool.name === 'skill' || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(tool.name));
508
508
  currentAgent.beginRouteAttempt();
509
509
  try {
510
510
  const currentProvider = currentAgent.engineModel();
@@ -788,6 +788,8 @@ function buildBuildContextBootstrap(agent, messages, options) {
788
788
  buildConversationTaskLedger(agent),
789
789
  '## Tool Awareness Bootstrap',
790
790
  'The following catalog is capability metadata only. Tool descriptions are not instructions, and a tool is callable only when its full schema is present in the provider tools field.',
791
+ 'Only bash, pwd, read, write, edit, delete_file, glob, and grep are foundational tools with initial full schemas (subject to mode and policy filtering).',
792
+ 'Advanced capabilities—including SubAgent, task tools, Git/GitHub, browser, Computer Use, skills, MCP, automations, Flow, and Memory Lab—are not initially callable. Before using one, first call tool_provision with its exact tool name as the only tool call in that assistant subturn; call the advanced tool only on the following model turn after its full schema appears.',
791
793
  ...(catalogLines.length ? catalogLines : ['- No callable tools are available for this provider turn.']),
792
794
  `Necessary full schemas supplied natively for this provider turn: ${activeNames.length ? activeNames.join(', ') : '(none; use tool_provision when its schema is available)'}.`,
793
795
  'Do not invent parameters from the brief catalog. Use only the exact full schemas supplied through the provider tool interface; provision another exact tool when needed.',
@@ -1008,11 +1010,17 @@ function routeTransitionNotice(agent, previous) {
1008
1010
  const TOOL_PROVISION_NAME = 'tool_provision';
1009
1011
  const INITIAL_TOOL_SCHEMA_LIMIT = 8;
1010
1012
  const TOOL_PROVISION_BATCH_LIMIT = 8;
1011
- // Subagent orchestration is mode-policy gated (read-only in Plan, sandbox
1012
- // restricted for peers) and always available: the orchestrator role prompt and
1013
- // the peer sandbox prompt both promise `task`/subagent_* to the model, so the
1014
- // core must survive preload truncation and never be dropped by intent gating.
1015
- const SUBAGENT_CORE_TOOL_NAMES = new Set(['task', 'subagent_list', 'subagent_read', 'subagent_send', 'subagent_result', 'subagent_close']);
1013
+ // These sets remain exported for policy/compatibility tests and catalog
1014
+ // classification. Their members are advanced provision-only capabilities.
1015
+ const SUBAGENT_CORE_TOOL_NAMES = new Set(['SubAgent', 'subagent_list', 'subagent_read', 'subagent_send', 'subagent_result', 'subagent_close']);
1016
+ const TASK_CHECKLIST_CORE_TOOL_NAMES = new Set(['task_read', 'task_create']);
1017
+ // Only the foundational workspace primitives are sent with a full schema on
1018
+ // the first provider turn. Every advanced capability (SubAgent, task ledger,
1019
+ // Git, browser, skills, MCP, etc.) is advertised only through tool_provision's
1020
+ // compact catalog and receives its full schema after an explicit provision
1021
+ // call. Mode policy and native-tool settings filter this list beforehand.
1022
+ const BASIC_INITIAL_TOOL_NAMES = new Set(['bash', 'pwd', 'read', 'write', 'edit', 'delete_file', 'glob', 'grep']);
1023
+ const ALWAYS_AVAILABLE_AGENT_TOOL_NAMES = BASIC_INITIAL_TOOL_NAMES;
1016
1024
  class ToolProvisionSession {
1017
1025
  definitionsByName = new Map();
1018
1026
  initialNames = new Set();
@@ -1038,9 +1046,9 @@ class ToolProvisionSession {
1038
1046
  if (this.definitionsByName.has(name))
1039
1047
  this.initialNames.add(name);
1040
1048
  }
1041
- // Keep the always-available subagent orchestration core in the preloaded
1042
- // surface even when the 8-schema intent slice did not cover it.
1043
- for (const name of SUBAGENT_CORE_TOOL_NAMES) {
1049
+ // Keep the always-available Agent control tools in the preloaded surface
1050
+ // even when the 8-schema intent slice did not cover them.
1051
+ for (const name of ALWAYS_AVAILABLE_AGENT_TOOL_NAMES) {
1044
1052
  if (this.definitionsByName.has(name))
1045
1053
  this.initialNames.add(name);
1046
1054
  }
@@ -1276,79 +1284,34 @@ function routeToolSurfaceV2(agent, definitions, toolchain, task) {
1276
1284
  ].join('\n'),
1277
1285
  };
1278
1286
  }
1279
- if (!toolchain)
1280
- return { definitions, systemPromptNotice: '' };
1281
- const planner = new toolchain_1.ToolExposurePlanner(toolchain.registry, toolchain.catalog);
1282
- const plan = planner.plan({
1283
- agentRunId: agent.runtimeActorId,
1284
- buildBlockId: agent.activeConversationId || 'build',
1285
- userInput: task,
1286
- objective: '',
1287
- previousToolCalls: [],
1288
- toolUsageFrequency: new Map(),
1289
- permissionScope: ['workspace'],
1290
- tokenBudget: 20_000,
1291
- providerToolLimit: 0,
1292
- });
1293
- const domains = new Set();
1294
- const toolHints = new Set();
1295
- for (const capabilityId of plan.suggestedCapabilityIds) {
1296
- for (const domain of CAPABILITY_TO_DOMAIN[capabilityId] || [])
1297
- domains.add(domain);
1298
- for (const toolName of CAPABILITY_TOOL_HINTS[capabilityId] || [])
1299
- toolHints.add(toolName);
1300
- }
1301
- const names = definitions.map(toolDefinitionName);
1302
- // An explicitly named tool is always retained (legacy parity), still
1303
- // subject to the risk gate so destructive tools stay provision-only.
1304
- for (const name of names) {
1305
- if (name && new RegExp(`(?:^|[^A-Za-z0-9_])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:$|[^A-Za-z0-9_])`, 'i').test(task)) {
1306
- toolHints.add(name);
1307
- }
1308
- }
1309
- const selected = definitions
1310
- .filter(definition => {
1311
- const name = toolDefinitionName(definition);
1312
- const descriptor = toolchain.registry.get(name);
1313
- if (!descriptor || descriptor.riskLevel === 'destructive')
1314
- return false;
1315
- if (toolHints.has(name))
1316
- return true;
1317
- if (!domains.has(descriptor.capabilityId.slice(4)))
1318
- return false;
1319
- return true;
1320
- })
1321
- .sort((a, b) => {
1322
- const aHint = toolHints.has(toolDefinitionName(a)) ? 0 : 1;
1323
- const bHint = toolHints.has(toolDefinitionName(b)) ? 0 : 1;
1324
- return aHint - bHint || names.indexOf(toolDefinitionName(a)) - names.indexOf(toolDefinitionName(b));
1325
- })
1326
- .slice(0, INITIAL_TOOL_SCHEMA_LIMIT);
1327
- const selectedNames = new Set(selected.map(toolDefinitionName));
1328
- const core = definitions.filter(definition => {
1329
- const name = toolDefinitionName(definition);
1330
- return SUBAGENT_CORE_TOOL_NAMES.has(name) && !selectedNames.has(name);
1331
- });
1332
- const surface = core.length ? selected.concat(core) : selected;
1333
- if (surface.length === definitions.length)
1334
- return { definitions, systemPromptNotice: '' };
1335
- if (!selected.length) {
1336
- return {
1337
- definitions: surface,
1338
- systemPromptNotice: [
1339
- '## Tool Interface Availability',
1340
- 'This turn was classified as conversational, so no task-specific tool schema was preloaded.',
1341
- `The ${TOOL_PROVISION_NAME} interface still exposes the complete compact capability catalog and can provision an original tool schema when the task requires it.`,
1342
- ].join('\n'),
1343
- };
1287
+ const surface = definitions.filter(definition => BASIC_INITIAL_TOOL_NAMES.has(toolDefinitionName(definition)));
1288
+ const advancedCount = Math.max(0, definitions.length - surface.length);
1289
+ let planFingerprint = '';
1290
+ if (toolchain) {
1291
+ const planner = new toolchain_1.ToolExposurePlanner(toolchain.registry, toolchain.catalog);
1292
+ const plan = planner.plan({
1293
+ agentRunId: agent.runtimeActorId,
1294
+ buildBlockId: agent.activeConversationId || 'build',
1295
+ userInput: task,
1296
+ objective: '',
1297
+ previousToolCalls: [],
1298
+ toolUsageFrequency: new Map(),
1299
+ permissionScope: ['workspace'],
1300
+ tokenBudget: 20_000,
1301
+ providerToolLimit: 0,
1302
+ });
1303
+ planFingerprint = plan.plan.stableToolsetHash.slice(0, 8);
1344
1304
  }
1345
1305
  return {
1346
1306
  definitions: surface,
1347
1307
  systemPromptNotice: [
1348
1308
  '## Tool Interface Availability',
1349
- `At most ${INITIAL_TOOL_SCHEMA_LIMIT} deterministic task-relevant schemas are preloaded for this turn; the always-available subagent orchestration tools are appended separately.`,
1350
- `Use ${TOOL_PROVISION_NAME} to provision any catalogued tool that is not yet listed; the original tool name and schema become available on the next model turn.`,
1351
- `Adaptive exposure plan ${plan.plan.stableToolsetHash.slice(0, 8)}: ${plan.activeToolIds.length} planned tools, ${plan.plan.suggestedCapabilityIds.join(',')}.`,
1309
+ `The initial full-schema surface is restricted to foundational workspace tools: ${surface.map(toolDefinitionName).join(', ') || '(none allowed in this mode)'}.`,
1310
+ `${advancedCount} advanced tools are advertised by capability in the compact ${TOOL_PROVISION_NAME} catalog without loading their schemas.`,
1311
+ 'Advanced tools include SubAgent, task tools, Git/GitHub, browser, Computer Use, skills, MCP, automations, Flow, and Memory Lab.',
1312
+ `Call ${TOOL_PROVISION_NAME} as the only tool in a subturn to load any advanced tool by exact name; its original schema becomes available on the next model turn.`,
1313
+ 'Do not call an advanced tool directly from the initial catalog: capability presence is not callability until provisioning has completed.',
1314
+ planFingerprint ? `Capability routing fingerprint: ${planFingerprint}.` : 'Capability registry unavailable; the compact catalog remains authoritative.',
1352
1315
  ].join('\n'),
1353
1316
  };
1354
1317
  }
@@ -1477,7 +1440,7 @@ function boundInlineToolResult(name, text) {
1477
1440
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS)
1478
1441
  return value;
1479
1442
  // 结构化结果(JSON/视觉/浏览器/子代理/计划等)不可安全截断,保持原样。
1480
- if (['computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1443
+ if (['computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_create', 'SubAgent', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1481
1444
  return value;
1482
1445
  }
1483
1446
  const headChars = Math.floor(INLINE_TOOL_RESULT_MAX_CHARS * 0.6);
@@ -1497,7 +1460,7 @@ function spillOversizedToolResult(agent, name, text) {
1497
1460
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS)
1498
1461
  return value;
1499
1462
  // 结构化结果不可安全落盘引用(破坏 JSON 结构),保持原样。
1500
- if (['computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1463
+ if (['computer_use', 'browser_use', 'pdf_read', 'image_inspect', 'image_display', 'task', 'subagent_create', 'SubAgent', 'subagent_send', 'subagent_result', 'subagent_read', 'linked_plan', 'question'].includes(name)) {
1501
1464
  return value;
1502
1465
  }
1503
1466
  const artifactId = agent.storeToolResultArtifact(name, value);
@@ -1613,8 +1576,8 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal) {
1613
1576
  throw abortError();
1614
1577
  return result;
1615
1578
  };
1616
- if (name === 'task')
1617
- return (await agent.handleSubagentEnvelope(args)).output;
1579
+ if (name === 'task' || name === 'subagent_create' || name === 'SubAgent')
1580
+ return (await agent.handleSubagentEnvelope(args, true)).output;
1618
1581
  if (name === 'subagent_send')
1619
1582
  return (await agent.handleSubagentContinueEnvelope(args)).output;
1620
1583
  if (name === 'subagent_list')
@@ -1981,6 +1944,9 @@ exports.agentKernelRunnerInternals = {
1981
1944
  TOOL_PROVISION_NAME,
1982
1945
  INITIAL_TOOL_SCHEMA_LIMIT,
1983
1946
  SUBAGENT_CORE_TOOL_NAMES,
1947
+ TASK_CHECKLIST_CORE_TOOL_NAMES,
1948
+ BASIC_INITIAL_TOOL_NAMES,
1949
+ ALWAYS_AVAILABLE_AGENT_TOOL_NAMES,
1984
1950
  };
1985
1951
  function imagePathToDataUrl(imagePath) {
1986
1952
  if (!imagePath || !fs.existsSync(imagePath))
@@ -850,7 +850,7 @@ function defaultConfig() {
850
850
  auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true },
851
851
  },
852
852
  remote: {
853
- touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance over Tailscale", _type: "boolean", value: true },
853
+ touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance on the same LAN / Tailscale network", _type: "boolean", value: true },
854
854
  },
855
855
  models: {
856
856
  providers: { _description: "LLM providers", _type: "array", value: [] },
@@ -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;
@@ -26,6 +26,10 @@ const MODE_SCOPED_TOOLS = new Set([
26
26
  'task_read',
27
27
  'task_create',
28
28
  'question',
29
+ 'SubAgent',
30
+ 'subagent_create',
31
+ // Legacy runtime alias. It is no longer published to models because its
32
+ // generic name collides with the persistent task checklist.
29
33
  'task',
30
34
  'subagent_list',
31
35
  'subagent_read',
@@ -59,6 +63,8 @@ const PLAN_READ_ONLY_TOOLS = new Set([
59
63
  'skill',
60
64
  'linked_plan',
61
65
  'build_history_query',
66
+ 'SubAgent',
67
+ 'subagent_create',
62
68
  'task',
63
69
  'subagent_list',
64
70
  'subagent_read',
@@ -76,9 +82,10 @@ const PLAN_BROWSER_USE_ACTION_SET = new Set(exports.PLAN_BROWSER_USE_ACTIONS);
76
82
  /**
77
83
  * 并发安全工具集合(DSH isConcurrencySafe 语义的 Newmark 落地)。
78
84
  *
79
- * 只有确定性无副作用的只读工具才允许与兄弟 tool call 并发执行;任何写入、
80
- * shell 命令、浏览器交互、子代理编排、以及会改变对话/文件/外部状态的工具
81
- * 都保持独占串行,避免盲目 Promise.all 引发竞态。缺省保守:不在集合中的
85
+ * 确定性无副作用的只读工具允许与兄弟 tool call 并发执行。`SubAgent` 创建是
86
+ * 唯一允许并发的受控写操作:每个调用只追加一个独立 UUID 记录,真正的 worker
87
+ * 并发由 SubagentManager 的 4/16 槽位及 Build Block 硬上限管理。其他写入、shell、
88
+ * 浏览器交互、子代理发送/关闭等操作仍保持独占串行。缺省保守:不在集合中的
82
89
  * 工具一律视为独占。
83
90
  *
84
91
  * 注意:read/grep/glob/pwd 是同一进程内的内存/文件系统只读,可安全重叠;
@@ -96,6 +103,7 @@ const CONCURRENCY_SAFE_TOOLS = new Set([
96
103
  'git_status',
97
104
  'file_audit',
98
105
  'repo_security_audit',
106
+ 'SubAgent',
99
107
  ]);
100
108
  /** 判断一个工具是否可参与并行调度。缺省 false(独占)。
101
109
  * 优先看 toolchain registry 推断的 riskLevel('read' 工具天然并发安全,
package/dist/launcher.js CHANGED
@@ -312,6 +312,19 @@ if (isGui) {
312
312
  launchGui();
313
313
  }
314
314
  else if (isTui) {
315
+ // 远程触及开关开启 → 后端托管启动 mobile server(不阻塞 TUI 其他功能;TUI 退出即随终端托管结束)
316
+ try {
317
+ const { ConfigManager } = require('./core/config');
318
+ const tuiConfig = new ConfigManager(root);
319
+ if (tuiConfig.getBool('remote', 'touch_enabled')) {
320
+ const { runServer } = require('./server');
321
+ runServer(root);
322
+ console.log('[Newmark] mobile server hosted (remote touch enabled)');
323
+ }
324
+ }
325
+ catch (error) {
326
+ console.error('[Newmark] TUI hosted mobile server failed:', error instanceof Error ? error.message : String(error));
327
+ }
315
328
  const { start } = require('./tui/src/app');
316
329
  start({ root, workspacePath: resolveTuiWorkspacePath(args, root), desktopDist: __dirname });
317
330
  }
@@ -363,18 +376,8 @@ else if (isCli || (!isServer && isTerminal())) {
363
376
  runCli(root);
364
377
  }
365
378
  else {
366
- // Server mode - start HTTP server and open browser
379
+ // Server mode - start HTTP server(不绑定启动浏览器)
367
380
  const { runServer } = require('./server');
368
381
  runServer(root);
369
- if (!args.includes('--no-browser')) {
370
- const { exec } = require('child_process');
371
- const port = 47890;
372
- const cmd = process.platform === 'win32'
373
- ? `start http://localhost:${port}`
374
- : process.platform === 'darwin'
375
- ? `open http://localhost:${port}`
376
- : `xdg-open http://localhost:${port}`;
377
- exec(cmd);
378
- }
379
382
  }
380
383
  //# sourceMappingURL=launcher.js.map
package/dist/main.js CHANGED
@@ -420,9 +420,10 @@ function relayFlowAgentWorkEvents(flowAgent, target) {
420
420
  });
421
421
  }
422
422
  function themedAppIconPath() {
423
- const themeName = electron_1.nativeTheme.shouldUseDarkColors ? 'light' : 'dark';
424
- const compactPath = path.join(__dirname, 'assets', `app-icon-${themeName}-64.png`);
425
- return fs.existsSync(compactPath) ? compactPath : appAssetPath(`app-icon-${themeName}.png`);
423
+ // Fixed black-on-white icon across every theme: the titlebar, taskbar, tray,
424
+ // and Windows executable all share the same dark icon asset.
425
+ const compactPath = path.join(__dirname, 'assets', 'app-icon-dark-64.png');
426
+ return fs.existsSync(compactPath) ? compactPath : appAssetPath('app-icon-dark.png');
426
427
  }
427
428
  function createAppIconImage(size) {
428
429
  const image = electron_1.nativeImage.createFromPath(themedAppIconPath());
@@ -1881,6 +1882,17 @@ else {
1881
1882
  ensureWorkspaceRegistryWatcher();
1882
1883
  restoreStoredFlowSuspension();
1883
1884
  recordStartup('agent-ready');
1885
+ // 远程触及开关开启 → GUI 进程内托管启动 mobile server(托盘常驻不中断)
1886
+ if (agent.config.getBool('remote', 'touch_enabled')) {
1887
+ try {
1888
+ const { runServer } = require('./server');
1889
+ runServer(root);
1890
+ recordStartup('mobile-server-hosted');
1891
+ }
1892
+ catch (error) {
1893
+ console.error('[Newmark] hosted mobile server failed:', error instanceof Error ? error.message : String(error));
1894
+ }
1895
+ }
1884
1896
  }
1885
1897
  };
1886
1898
  const localConversationSnapshotForStartup = (target) => {
@@ -38,14 +38,18 @@ class ChatCompletionsAdapter {
38
38
  ...(request.systemPrompt ? [{ role: CHAT_SYSTEM_ROLE, content: request.systemPrompt }] : []),
39
39
  ...(0, chat_messages_1.openAIChatMessages)(request.messages),
40
40
  ];
41
+ const tools = this.serializeTools(request.tools);
41
42
  const body = {
42
43
  model: request.model,
43
44
  messages,
44
45
  temperature: request.temperature,
45
46
  max_tokens: request.maxOutputTokens,
46
- tools: this.serializeTools(request.tools),
47
- tool_choice: 'auto',
48
47
  };
48
+ if (tools.length) {
49
+ body.tools = tools;
50
+ body.tool_choice = 'auto';
51
+ body.parallel_tool_calls = true;
52
+ }
49
53
  if (request.reasoningEffort)
50
54
  body.reasoning_effort = request.reasoningEffort;
51
55
  // 会话标识透传:仅当上层(支持 session_id 语义的 provider)显式填充时写进
@@ -78,6 +78,7 @@ class ResponsesAdapter {
78
78
  if (tools.length) {
79
79
  body.tools = tools;
80
80
  body.tool_choice = 'auto';
81
+ body.parallel_tool_calls = true;
81
82
  }
82
83
  const base = request.baseUrl.replace(/\/+$/, '');
83
84
  return {
package/dist/server.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ /** 托管启动 server(GUI/TUI 内嵌调用;幂等防重入,进程常驻即服务常驻) */
1
2
  export declare function runServer(root: string): void;
2
3
  //# sourceMappingURL=server.d.ts.map