newmark-agent 0.4.5 → 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.
Files changed (40) 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.d.ts +1 -1
  4. package/dist/cli-commands.js +90 -7
  5. package/dist/cli-discovery.js +10 -0
  6. package/dist/conversation-utility-host.bundle.cjs +293 -148
  7. package/dist/core/agent.d.ts +38 -4
  8. package/dist/core/agent.js +231 -49
  9. package/dist/core/agentKernelRunner.d.ts +3 -0
  10. package/dist/core/agentKernelRunner.js +49 -83
  11. package/dist/core/config.js +3 -0
  12. package/dist/core/dshCompatibility.d.ts +23 -6
  13. package/dist/core/dshCompatibility.js +99 -1
  14. package/dist/core/installUpdate.d.ts +67 -0
  15. package/dist/core/installUpdate.js +268 -0
  16. package/dist/core/mobilePairing.d.ts +47 -0
  17. package/dist/core/mobilePairing.js +221 -0
  18. package/dist/core/subagent.d.ts +10 -3
  19. package/dist/core/subagent.js +23 -8
  20. package/dist/core/toolPolicy.js +11 -3
  21. package/dist/launcher.js +14 -11
  22. package/dist/main.js +64 -3
  23. package/dist/preload.js +5 -0
  24. package/dist/providers/chat-completions.adapter.js +6 -2
  25. package/dist/providers/responses.adapter.js +1 -0
  26. package/dist/server.d.ts +1 -0
  27. package/dist/server.js +721 -3
  28. package/dist/toolchain/registry-seeder.js +3 -1
  29. package/dist/tools/index.js +11 -5
  30. package/dist/tools/nativeTools.js +1 -1
  31. package/dist/tui/src/adapters/core-runtime-adapter.js +17 -1
  32. package/dist/tui/src/app.js +41 -0
  33. package/dist/tui/src/data.js +1 -0
  34. package/dist/tui/src/render.js +11 -0
  35. package/dist/tui/src/settings-schema.js +3 -1
  36. package/dist/tui/src/state.js +21 -1
  37. package/dist/ui/index.html +530 -101
  38. package/dist/ui/lucide-sprite.svg +10 -0
  39. package/dist/wsl-agent-host.bundle.cjs +293 -148
  40. package/package.json +14 -8
@@ -39,6 +39,15 @@ exports.normalizeReleaseVersion = normalizeReleaseVersion;
39
39
  exports.compareSemver = compareSemver;
40
40
  exports.checkGitHubUpdate = checkGitHubUpdate;
41
41
  exports.applyGitHubUpdate = applyGitHubUpdate;
42
+ exports.listRunningNewmarkProcesses = listRunningNewmarkProcesses;
43
+ exports.stopNewmarkProcesses = stopNewmarkProcesses;
44
+ exports.listInstalledNewmarkProducts = listInstalledNewmarkProducts;
45
+ exports.uninstallNewmarkProduct = uninstallNewmarkProduct;
46
+ exports.installMsiPackage = installMsiPackage;
47
+ exports.findLegacyNewmarkExecutables = findLegacyNewmarkExecutables;
48
+ exports.removeLegacyNewmarkExecutables = removeLegacyNewmarkExecutables;
49
+ exports.planManagedMsiInstall = planManagedMsiInstall;
50
+ exports.executeManagedMsiInstall = executeManagedMsiInstall;
42
51
  const fs = __importStar(require("fs"));
43
52
  const path = __importStar(require("path"));
44
53
  const os = __importStar(require("os"));
@@ -518,4 +527,263 @@ async function applyGitHubUpdate(options) {
518
527
  };
519
528
  }
520
529
  }
530
+ const NEWMARK_PROCESS_NAMES = ['Newmark Agent.exe', 'Newmark.exe', 'Newmark Console Runtime.exe'];
531
+ const NEWMARK_LEGACY_EXECUTABLES = new Set(['newmark.exe', 'newmark agent.exe']);
532
+ function normalizeWindowsPathForCompare(value) {
533
+ return path.resolve(String(value)).toLowerCase();
534
+ }
535
+ function isWithinOrEqual(candidate, parent) {
536
+ const child = normalizeWindowsPathForCompare(candidate);
537
+ const rootPath = normalizeWindowsPathForCompare(parent);
538
+ return child === rootPath || child.startsWith(rootPath.endsWith(path.sep) ? rootPath : rootPath + path.sep);
539
+ }
540
+ function runPowerShellJson(script) {
541
+ const args = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script];
542
+ const stdout = (0, child_process_1.execFileSync)('powershell.exe', args, { encoding: 'utf-8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
543
+ const trimmed = String(stdout || '').replace(/^\uFEFF/, '').trim();
544
+ if (!trimmed)
545
+ return [];
546
+ const parsed = JSON.parse(trimmed);
547
+ return Array.isArray(parsed) ? parsed.map(item => item) : [parsed];
548
+ }
549
+ function listRunningNewmarkProcesses() {
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`);
552
+ const skipPid = Number(process.env.NEWMARK_SKIP_PROCESS_PID || process.pid);
553
+ return rows
554
+ .map(row => ({
555
+ pid: Number(row.ProcessId || 0),
556
+ name: String(row.Name || ''),
557
+ executablePath: String(row.ExecutablePath || ''),
558
+ }))
559
+ .filter(proc => proc.pid > 0 && proc.pid !== skipPid);
560
+ }
561
+ function stopNewmarkProcesses(pids) {
562
+ const ids = Array.from(new Set((pids || []).map(Number).filter(pid => Number.isFinite(pid) && pid > 0)));
563
+ if (!ids.length)
564
+ return { stopped: [], errors: [] };
565
+ const script = `$ids = @(${ids.join(',')}); foreach ($id in $ids) { try { Stop-Process -Id $id -Force -ErrorAction Stop; Write-Output ('stopped:' + $id) } catch { Write-Output ('error:' + $id + ':' + $_.Exception.Message) } }`;
566
+ const args = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script];
567
+ const stdout = String((0, child_process_1.execFileSync)('powershell.exe', args, { encoding: 'utf-8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }) || '');
568
+ const stopped = [];
569
+ const errors = [];
570
+ for (const line of stdout.split(/\r?\n/)) {
571
+ const trimmed = line.trim();
572
+ if (!trimmed)
573
+ continue;
574
+ const stoppedMatch = trimmed.match(/^stopped:(\d+)$/);
575
+ const errorMatch = trimmed.match(/^error:(\d+):(.*)$/);
576
+ if (stoppedMatch)
577
+ stopped.push(Number(stoppedMatch[1]));
578
+ else if (errorMatch)
579
+ errors.push(`PID ${errorMatch[1]}: ${errorMatch[2]}`);
580
+ }
581
+ return { stopped, errors };
582
+ }
583
+ function listInstalledNewmarkProducts() {
584
+ const rows = runPowerShellJson(`$paths = @('HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'); Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like '*Newmark Agent*' } | Select-Object PSChildName,DisplayName,InstallLocation,UninstallString,QuietUninstallString | ConvertTo-Json -Compress`);
585
+ return rows
586
+ .map(row => ({
587
+ productCode: String(row.PSChildName || ''),
588
+ displayName: String(row.DisplayName || ''),
589
+ installLocation: String(row.InstallLocation || ''),
590
+ uninstallString: String(row.UninstallString || row.QuietUninstallString || ''),
591
+ }))
592
+ .filter(product => product.productCode);
593
+ }
594
+ function runMsiExec(args) {
595
+ const result = (0, child_process_1.spawnSync)('msiexec.exe', args, { encoding: 'utf-8', windowsHide: true });
596
+ return {
597
+ exitCode: result.status === null ? -1 : result.status,
598
+ stdout: String(result.stdout || ''),
599
+ stderr: String(result.stderr || ''),
600
+ };
601
+ }
602
+ function windowsCommandQuote(value) {
603
+ const text = String(value);
604
+ return /\s/.test(text) ? `"${text.replace(/"/g, '\\"')}"` : text;
605
+ }
606
+ function runElevatedMsiExec(args) {
607
+ const argumentString = args.map(arg => windowsCommandQuote(arg)).join(' ');
608
+ const script = [
609
+ `$argumentList = '${argumentString.replace(/'/g, "''")}'`,
610
+ '$process = Start-Process -FilePath "msiexec.exe" -ArgumentList $argumentList -Verb RunAs -Wait -PassThru',
611
+ 'Write-Output ("EXITCODE:" + $process.ExitCode)',
612
+ ].join('; ');
613
+ const result = (0, child_process_1.spawnSync)('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], { encoding: 'utf-8', windowsHide: true });
614
+ const stdout = String(result.stdout || '');
615
+ const match = stdout.match(/EXITCODE:(\d+)/);
616
+ return {
617
+ exitCode: match ? Number(match[1]) : (result.status === null ? -1 : result.status),
618
+ stdout,
619
+ stderr: String(result.stderr || ''),
620
+ };
621
+ }
622
+ function uninstallNewmarkProduct(productCode, logPath) {
623
+ const args = ['/x', productCode, '/qn', '/norestart', '/l*v', logPath];
624
+ let result = runMsiExec(args);
625
+ if (result.exitCode !== 0 && result.exitCode !== 3010)
626
+ result = runElevatedMsiExec(args);
627
+ const ok = result.exitCode === 0 || result.exitCode === 3010;
628
+ return {
629
+ ok,
630
+ exitCode: result.exitCode,
631
+ logPath,
632
+ error: ok ? undefined : `msiexec uninstall exited ${result.exitCode}`,
633
+ };
634
+ }
635
+ function installMsiPackage(msiPath, options = {}) {
636
+ const logPath = path.join(options.logDir || os.tmpdir(), `newmark-msi-install-${process.pid}-${Date.now()}.log`);
637
+ const args = ['/i', path.resolve(msiPath), '/qn', '/norestart', '/l*v', logPath];
638
+ let result = runMsiExec(args);
639
+ if (result.exitCode !== 0 && result.exitCode !== 3010 && options.allowElevate !== false)
640
+ result = runElevatedMsiExec(args);
641
+ const ok = result.exitCode === 0 || result.exitCode === 3010;
642
+ return {
643
+ ok,
644
+ exitCode: result.exitCode,
645
+ logPath,
646
+ error: ok ? undefined : `msiexec install exited ${result.exitCode}`,
647
+ };
648
+ }
649
+ function findLegacyNewmarkExecutables(excludeRoots = []) {
650
+ const found = new Map();
651
+ for (const name of NEWMARK_PROCESS_NAMES) {
652
+ try {
653
+ const stdout = String((0, child_process_1.execFileSync)('where.exe', [name], { encoding: 'utf-8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }) || '');
654
+ for (const line of stdout.split(/\r?\n/)) {
655
+ const candidate = String(line || '').trim();
656
+ if (!candidate)
657
+ continue;
658
+ const key = normalizeWindowsPathForCompare(candidate);
659
+ if (NEWMARK_LEGACY_EXECUTABLES.has(path.basename(candidate).toLowerCase()) && !found.has(key))
660
+ found.set(key, candidate);
661
+ }
662
+ }
663
+ catch {
664
+ // where.exe returns non-zero when an executable is not found on PATH.
665
+ }
666
+ }
667
+ const exclude = (excludeRoots || []).filter(Boolean).map(root => normalizeWindowsPathForCompare(root));
668
+ return Array.from(found.values())
669
+ .filter(candidate => !exclude.some(root => isWithinOrEqual(candidate, root)))
670
+ .sort();
671
+ }
672
+ function removeLegacyNewmarkExecutables(paths) {
673
+ const removed = [];
674
+ const errors = [];
675
+ for (const candidate of Array.from(new Set(paths || []))) {
676
+ const full = path.resolve(candidate);
677
+ if (!NEWMARK_LEGACY_EXECUTABLES.has(path.basename(full).toLowerCase())) {
678
+ errors.push(`refusing non-Newmark executable: ${full}`);
679
+ continue;
680
+ }
681
+ try {
682
+ fs.unlinkSync(full);
683
+ removed.push(full);
684
+ }
685
+ catch (e) {
686
+ errors.push(`${full}: ${e instanceof Error ? e.message : String(e)}`);
687
+ }
688
+ }
689
+ return { removed, errors };
690
+ }
691
+ function planManagedMsiInstall(msiPath, options = {}) {
692
+ const fullPath = path.resolve(msiPath);
693
+ try {
694
+ if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile() || path.extname(fullPath).toLowerCase() !== '.msi') {
695
+ return {
696
+ ok: false,
697
+ msiPath: fullPath,
698
+ runningProcesses: [],
699
+ installedProducts: [],
700
+ legacyExecutables: [],
701
+ needsStopConfirmation: false,
702
+ needsLegacyRemovalConfirmation: false,
703
+ error: `MSI package does not exist or is not a .msi file: ${fullPath}`,
704
+ };
705
+ }
706
+ const runningProcesses = listRunningNewmarkProcesses();
707
+ const installedProducts = listInstalledNewmarkProducts();
708
+ const excludeRoots = [
709
+ ...(options.excludeRoots || []),
710
+ process.cwd(),
711
+ path.dirname(process.execPath || ''),
712
+ path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Newmark Agent'),
713
+ path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Newmark Agent'),
714
+ ...installedProducts.map(product => product.installLocation),
715
+ ];
716
+ const legacyExecutables = findLegacyNewmarkExecutables(excludeRoots);
717
+ return {
718
+ ok: true,
719
+ msiPath: fullPath,
720
+ runningProcesses,
721
+ installedProducts,
722
+ legacyExecutables,
723
+ needsStopConfirmation: runningProcesses.length > 0 && options.stopConfirmed !== true,
724
+ needsLegacyRemovalConfirmation: legacyExecutables.length > 0 && options.removeLegacyConfirmed !== true,
725
+ };
726
+ }
727
+ catch (e) {
728
+ return {
729
+ ok: false,
730
+ msiPath: fullPath,
731
+ runningProcesses: [],
732
+ installedProducts: [],
733
+ legacyExecutables: [],
734
+ needsStopConfirmation: false,
735
+ needsLegacyRemovalConfirmation: false,
736
+ error: e instanceof Error ? e.message : String(e),
737
+ };
738
+ }
739
+ }
740
+ function executeManagedMsiInstall(msiPath, options = {}) {
741
+ const plan = planManagedMsiInstall(msiPath, options);
742
+ if (!plan.ok) {
743
+ return { ok: false, plan, stopped: [], uninstalled: [], removedLegacy: [], error: plan.error };
744
+ }
745
+ if (plan.needsStopConfirmation) {
746
+ return { ok: false, plan, stopped: [], uninstalled: [], removedLegacy: [], error: 'Running Newmark processes require confirmation before they can be stopped.' };
747
+ }
748
+ if (plan.needsLegacyRemovalConfirmation) {
749
+ return { ok: false, plan, stopped: [], uninstalled: [], removedLegacy: [], error: 'Legacy Newmark executables require confirmation before they can be removed.' };
750
+ }
751
+ const stopResult = plan.runningProcesses.length
752
+ ? stopNewmarkProcesses(plan.runningProcesses.map(process => process.pid))
753
+ : { stopped: [], errors: [] };
754
+ const uninstalled = [];
755
+ if (options.uninstallPrevious !== false) {
756
+ for (const product of plan.installedProducts) {
757
+ const logPath = path.join(options.logDir || os.tmpdir(), `newmark-msi-uninstall-${product.productCode}-${process.pid}-${Date.now()}.log`);
758
+ const uninstallResult = uninstallNewmarkProduct(product.productCode, logPath);
759
+ if (!uninstallResult.ok) {
760
+ return {
761
+ ok: false,
762
+ plan,
763
+ stopped: stopResult.stopped,
764
+ uninstalled,
765
+ removedLegacy: [],
766
+ exitCode: uninstallResult.exitCode,
767
+ logPath: uninstallResult.logPath,
768
+ error: `Failed to uninstall previous Newmark version ${product.productCode}: ${uninstallResult.error}`,
769
+ };
770
+ }
771
+ uninstalled.push(product.productCode);
772
+ }
773
+ }
774
+ const removeResult = plan.legacyExecutables.length
775
+ ? removeLegacyNewmarkExecutables(plan.legacyExecutables)
776
+ : { removed: [], errors: [] };
777
+ const installResult = installMsiPackage(plan.msiPath, { logDir: options.logDir, allowElevate: options.allowElevate });
778
+ return {
779
+ ok: installResult.ok,
780
+ plan,
781
+ stopped: stopResult.stopped,
782
+ uninstalled,
783
+ removedLegacy: removeResult.removed,
784
+ exitCode: installResult.exitCode,
785
+ logPath: installResult.logPath,
786
+ error: installResult.error,
787
+ };
788
+ }
521
789
  //# sourceMappingURL=installUpdate.js.map
@@ -0,0 +1,47 @@
1
+ export declare const MOBILE_TOKEN_FILENAME = ".newmark-mobile-token";
2
+ export declare const MOBILE_PAIRING_FILENAME = ".newmark-mobile-pairing.json";
3
+ export declare const MOBILE_PORT = 47890;
4
+ export declare const MOBILE_PAIRING_TTL_MS = 120000;
5
+ export interface PairingSession {
6
+ pairingId: string;
7
+ token: string;
8
+ host: string;
9
+ hostname: string;
10
+ port: number;
11
+ issuedAt: number;
12
+ expiresAt: number;
13
+ url: string;
14
+ confirmed?: boolean;
15
+ confirmedAt?: number;
16
+ }
17
+ export interface PairingStatus {
18
+ pairingId: string;
19
+ issuedAt: number;
20
+ expiresAt: number;
21
+ confirmed: boolean;
22
+ confirmedAt: number;
23
+ active: boolean;
24
+ expired: boolean;
25
+ }
26
+ export declare function ensureMobileToken(root: string): string;
27
+ export declare function tailscaleIpv4(): string | null;
28
+ export declare function lanIpv4(): string | null;
29
+ export declare function pairingHost(): string;
30
+ export declare function createPairingSession(root: string, ttlMs?: number): PairingSession;
31
+ export declare function pairingUrl(root: string): string;
32
+ export declare function pairingTokenPath(root: string): string;
33
+ export declare function pairingStatus(root: string): PairingStatus;
34
+ export declare function confirmPairing(root: string, pairingId: string, token: string): {
35
+ ok: boolean;
36
+ error?: string;
37
+ status: PairingStatus;
38
+ };
39
+ export declare function pairingQrDataUrl(root: string, ttlMs?: number): Promise<{
40
+ dataUrl: string;
41
+ session: PairingSession;
42
+ }>;
43
+ export declare function pairingQrAscii(root: string, ttlMs?: number): Promise<{
44
+ ascii: string;
45
+ session: PairingSession;
46
+ }>;
47
+ //# sourceMappingURL=mobilePairing.d.ts.map
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.MOBILE_PAIRING_TTL_MS = exports.MOBILE_PORT = exports.MOBILE_PAIRING_FILENAME = exports.MOBILE_TOKEN_FILENAME = void 0;
37
+ exports.ensureMobileToken = ensureMobileToken;
38
+ exports.tailscaleIpv4 = tailscaleIpv4;
39
+ exports.lanIpv4 = lanIpv4;
40
+ exports.pairingHost = pairingHost;
41
+ exports.createPairingSession = createPairingSession;
42
+ exports.pairingUrl = pairingUrl;
43
+ exports.pairingTokenPath = pairingTokenPath;
44
+ exports.pairingStatus = pairingStatus;
45
+ exports.confirmPairing = confirmPairing;
46
+ exports.pairingQrDataUrl = pairingQrDataUrl;
47
+ exports.pairingQrAscii = pairingQrAscii;
48
+ const fs = __importStar(require("fs"));
49
+ const os = __importStar(require("os"));
50
+ const path = __importStar(require("path"));
51
+ const crypto_1 = require("crypto");
52
+ const child_process_1 = require("child_process");
53
+ const QRCode = __importStar(require("qrcode"));
54
+ exports.MOBILE_TOKEN_FILENAME = '.newmark-mobile-token';
55
+ exports.MOBILE_PAIRING_FILENAME = '.newmark-mobile-pairing.json';
56
+ exports.MOBILE_PORT = 47890;
57
+ exports.MOBILE_PAIRING_TTL_MS = 120_000;
58
+ function pairingStatePath(root) {
59
+ return path.join(root, exports.MOBILE_PAIRING_FILENAME);
60
+ }
61
+ function readPairingState(root) {
62
+ try {
63
+ const parsed = JSON.parse(fs.readFileSync(pairingStatePath(root), 'utf-8'));
64
+ if (!parsed || !parsed.pairingId || !parsed.token)
65
+ return null;
66
+ return parsed;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ function writePairingState(root, state) {
73
+ fs.mkdirSync(root, { recursive: true });
74
+ fs.writeFileSync(pairingStatePath(root), JSON.stringify(state, null, 2), 'utf-8');
75
+ }
76
+ function ensureMobileToken(root) {
77
+ const tokenPath = path.join(root, exports.MOBILE_TOKEN_FILENAME);
78
+ try {
79
+ const existing = fs.readFileSync(tokenPath, 'utf-8').replace(/\s+/g, '').trim();
80
+ if (existing.length >= 32)
81
+ return existing;
82
+ }
83
+ catch {
84
+ // first start: no token file yet
85
+ }
86
+ const generated = (0, crypto_1.randomBytes)(24).toString('hex');
87
+ try {
88
+ fs.mkdirSync(root, { recursive: true });
89
+ fs.writeFileSync(tokenPath, generated, { encoding: 'utf-8', mode: 0o600 });
90
+ }
91
+ catch {
92
+ // keep the generated token for this process even if persistence fails
93
+ }
94
+ return generated;
95
+ }
96
+ function tailscaleIpv4() {
97
+ const exe = process.platform === 'win32' ? 'tailscale.exe' : 'tailscale';
98
+ try {
99
+ const result = (0, child_process_1.spawnSync)(exe, ['ip', '-4'], { encoding: 'utf-8', windowsHide: true, timeout: 3000 });
100
+ if (result.error || result.status !== 0)
101
+ return null;
102
+ const lines = String(result.stdout || '').split(/\r?\n/).map(line => line.trim()).filter(Boolean);
103
+ return lines[0] || null;
104
+ }
105
+ catch {
106
+ return null;
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
+ }
122
+ function pairingHost() {
123
+ return tailscaleIpv4() || lanIpv4() || '127.0.0.1';
124
+ }
125
+ function buildPairingUrl(session) {
126
+ const query = new URLSearchParams({
127
+ token: session.token,
128
+ host: session.hostname,
129
+ port: String(session.port),
130
+ pairingId: session.pairingId,
131
+ issuedAt: String(session.issuedAt),
132
+ expiresAt: String(session.expiresAt),
133
+ });
134
+ return `newmark-pair://${session.host}:${session.port}?${query.toString()}`;
135
+ }
136
+ function createPairingSession(root, ttlMs = exports.MOBILE_PAIRING_TTL_MS) {
137
+ const token = ensureMobileToken(root);
138
+ const host = pairingHost();
139
+ const now = Date.now();
140
+ const session = {
141
+ pairingId: (0, crypto_1.randomBytes)(12).toString('hex'),
142
+ token,
143
+ host,
144
+ hostname: os.hostname(),
145
+ port: exports.MOBILE_PORT,
146
+ issuedAt: now,
147
+ expiresAt: now + Math.max(1000, Number(ttlMs) || exports.MOBILE_PAIRING_TTL_MS),
148
+ url: '',
149
+ confirmed: false,
150
+ confirmedAt: 0,
151
+ };
152
+ session.url = buildPairingUrl(session);
153
+ writePairingState(root, session);
154
+ return session;
155
+ }
156
+ function pairingUrl(root) {
157
+ return createPairingSession(root).url;
158
+ }
159
+ function pairingTokenPath(root) {
160
+ return path.join(root, exports.MOBILE_TOKEN_FILENAME);
161
+ }
162
+ function pairingStatus(root) {
163
+ const state = readPairingState(root);
164
+ if (!state) {
165
+ return { pairingId: '', issuedAt: 0, expiresAt: 0, confirmed: false, confirmedAt: 0, active: false, expired: true };
166
+ }
167
+ const now = Date.now();
168
+ const confirmed = state.confirmed === true;
169
+ const expired = now >= Number(state.expiresAt || 0);
170
+ return {
171
+ pairingId: state.pairingId,
172
+ issuedAt: Number(state.issuedAt || 0),
173
+ expiresAt: Number(state.expiresAt || 0),
174
+ confirmed,
175
+ confirmedAt: Number(state.confirmedAt || 0),
176
+ active: !confirmed && !expired,
177
+ expired: !confirmed && expired,
178
+ };
179
+ }
180
+ function confirmPairing(root, pairingId, token) {
181
+ const state = readPairingState(root);
182
+ const status = pairingStatus(root);
183
+ if (!state || !state.pairingId) {
184
+ return { ok: false, error: 'No pairing window is active.', status };
185
+ }
186
+ if (state.token !== token) {
187
+ return { ok: false, error: 'Pairing token does not match.', status };
188
+ }
189
+ if (state.pairingId !== pairingId) {
190
+ return { ok: false, error: 'Pairing window is stale.', status };
191
+ }
192
+ if (status.expired) {
193
+ return { ok: false, error: 'Pairing window expired.', status };
194
+ }
195
+ if (status.confirmed) {
196
+ return { ok: true, status };
197
+ }
198
+ const updated = { ...state, confirmed: true, confirmedAt: Date.now() };
199
+ writePairingState(root, updated);
200
+ return { ok: true, status: pairingStatus(root) };
201
+ }
202
+ async function pairingQrDataUrl(root, ttlMs) {
203
+ const session = createPairingSession(root, ttlMs);
204
+ const dataUrl = await QRCode.toDataURL(session.url, {
205
+ width: 420,
206
+ margin: 1,
207
+ errorCorrectionLevel: 'M',
208
+ color: { dark: '#101828', light: '#FFFFFF' },
209
+ });
210
+ return { dataUrl, session };
211
+ }
212
+ async function pairingQrAscii(root, ttlMs) {
213
+ const session = createPairingSession(root, ttlMs);
214
+ const ascii = await QRCode.toString(session.url, {
215
+ type: 'terminal',
216
+ small: true,
217
+ errorCorrectionLevel: 'M',
218
+ });
219
+ return { ascii, session };
220
+ }
221
+ //# sourceMappingURL=mobilePairing.js.map
@@ -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;