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.
- 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 +291 -149
- package/dist/core/agent.d.ts +38 -4
- package/dist/core/agent.js +231 -49
- package/dist/core/agentKernelRunner.d.ts +3 -0
- package/dist/core/agentKernelRunner.js +49 -83
- package/dist/core/config.js +1 -1
- 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 +11 -3
- package/dist/launcher.js +14 -11
- package/dist/main.js +15 -3
- package/dist/providers/chat-completions.adapter.js +6 -2
- package/dist/providers/responses.adapter.js +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +543 -17
- package/dist/toolchain/registry-seeder.js +3 -1
- package/dist/tools/index.js +11 -5
- package/dist/tools/nativeTools.js +1 -1
- package/dist/ui/index.html +88 -101
- package/dist/wsl-agent-host.bundle.cjs +291 -149
- package/package.json +3 -1
|
@@ -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' ||
|
|
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
|
|
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' ||
|
|
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
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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
|
|
1042
|
-
//
|
|
1043
|
-
for (const name of
|
|
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
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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
|
-
`
|
|
1350
|
-
|
|
1351
|
-
|
|
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))
|
package/dist/core/config.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
@@ -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
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
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
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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)显式填充时写进
|
package/dist/server.d.ts
CHANGED