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
@@ -35,8 +35,10 @@ function inferDomain(name) {
35
35
  return 'interaction';
36
36
  if (name === 'skill')
37
37
  return 'skills';
38
- if (name === 'task')
38
+ if (name === 'task' || name === 'subagent_create' || name === 'SubAgent')
39
39
  return 'subagent';
40
+ if (/^task_(read|create)$/.test(name))
41
+ return 'plan';
40
42
  if (/^(linked_plan|build_history_query)$/.test(name))
41
43
  return 'plan';
42
44
  return 'general';
@@ -379,7 +379,7 @@ class ToolExecutor {
379
379
  remote_root: { type: 'string' },
380
380
  remote_path: { type: 'string' },
381
381
  }, ['action']),
382
- t('task', 'Create a same-conversation peer agent and return immediately. The peer has a stable human-readable name, a short id, and a canonical UUID-qualified identity. The peer name is decoupled from its id: pass name for readable references and id for exact targeting. Pass model to select an exact configured model deployment (deployment:providerId:modelId or an unambiguous provider/model name). When model is omitted, the peer inherits the parent Agent\'s currently resolved model deployment. Plan mode peers are forced to Plan.', { nature: { type: 'string' }, name: { type: 'string', description: 'Legacy alias for nature.' }, prompt: { type: 'string' }, preset: { type: 'string' }, agent: { type: 'string' }, model: { type: 'string', description: 'Optional exact model deployment. Omit to inherit the parent Agent resolved model.' }, mode: { type: 'string' }, input_mode: { type: 'string' }, flow: { type: 'string' } }, ['prompt']),
382
+ t('SubAgent', 'Create one real same-conversation peer SubAgent and return immediately. This tool is only for delegation; never use it to create or update the conversation task checklist (use task_create for that). Creation is bound to the currently running Build Block: non-Ultra intelligence has a hard ceiling of 4 active peers for that Build, Ultra has 16, and excess calls terminate without creating or queueing a record. The peer has a stable human-readable name plus a separate exact id. Pass model to select an exact configured model deployment (deployment:providerId:modelId or an unambiguous provider/model name). When model is omitted, the peer inherits the parent Agent\'s currently resolved model deployment. Plan mode peers are forced to Plan.', { name: { type: 'string', description: 'Human-readable SubAgent name shown in the monitoring sidebar. Omit only when a named preset supplies it.' }, nature: { type: 'string', description: 'Legacy alias for name.' }, prompt: { type: 'string', description: 'Work delegated to this SubAgent; this is not a checklist item.' }, preset: { type: 'string' }, agent: { type: 'string' }, model: { type: 'string', description: 'Optional exact model deployment. Omit to inherit the parent Agent resolved model.' }, mode: { type: 'string' }, input_mode: { type: 'string' }, flow: { type: 'string' } }, ['prompt']),
383
383
  t('subagent_list', 'List flat same-conversation peer agents, optionally filtered by status. Each entry exposes both the stable name and the exact id; use the id for any subsequent targeting.', { status: { type: 'string', enum: ['idle', 'queued', 'working', 'completed', 'error', 'closed'] } }, []),
384
384
  t('subagent_read', 'Read one same-conversation peer status, queue/mailbox summary, latest bounded feedback, and result. Available for running, queued, completed, error, and closed peers. Pass the exact id returned by subagent_list, or a name for convenience lookup.', { id: { type: 'string', description: 'Exact peer id from subagent_list.' }, name: { type: 'string', description: 'Convenience peer name; ambiguous names resolve to the first match.' }, max_chars: { type: 'number', description: 'Bounded result size from 2000 to 32000 characters.' } }, []),
385
385
  t('subagent_send', 'Persist a mailbox message to a same-conversation peer agent. Target by exact id (preferred) or name.', { id: { type: 'string', description: 'Exact peer id from subagent_list.' }, name: { type: 'string', description: 'Convenience peer name.' }, message: { type: 'string' }, prompt: { type: 'string', description: 'Legacy alias for message.' }, kind: { type: 'string', enum: ['directive', 'question', 'result', 'handoff'] }, reply_to: { type: 'string' }, correlation_id: { type: 'string' } }, []),
@@ -559,7 +559,11 @@ class ToolExecutor {
559
559
  return (0, compat_1.normalizeToolResult)(output, { tool, workspacePath: wsPath, mode: context.mode || '' });
560
560
  }
561
561
  validateInvocation(tool, argsStr, _mode = '', inputSchema) {
562
- if (inputSchema === undefined && !(0, nativeTools_1.isNativeToolEnabled)(tool, this.config.nativeToolEnabled())) {
562
+ // Persisted work history may replay the old create aliases. They share the
563
+ // public SubAgent schema and enablement, but are never advertised back to
564
+ // the model as independent tools.
565
+ const schemaTool = tool === 'task' || tool === 'subagent_create' ? 'SubAgent' : tool;
566
+ if (inputSchema === undefined && !(0, nativeTools_1.isNativeToolEnabled)(schemaTool, this.config.nativeToolEnabled())) {
563
567
  return { ok: false, error: `[tool disabled] ${tool} is disabled in Settings > Tools.` };
564
568
  }
565
569
  let parsed;
@@ -591,12 +595,12 @@ class ToolExecutor {
591
595
  // catalogs may advertise a narrower enum, but a structurally valid
592
596
  // hidden call must reach evaluateToolPolicy so it is classified as a
593
597
  // policy denial rather than a misleading schema failure.
594
- ? this.definitions().find(candidate => candidate.function?.name === tool)
598
+ ? this.definitions().find(candidate => candidate.function?.name === schemaTool)
595
599
  : { function: { name: tool, parameters: inputSchema } };
596
600
  if (!definition) {
597
601
  return { ok: false, error: `[tool unsupported] ${tool || '(missing tool)'} is not available for the ${this.hostProfile.kind} host on ${this.hostProfile.platform}.` };
598
602
  }
599
- const validation = this.argumentValidators.validate(tool, definition.function.parameters, args);
603
+ const validation = this.argumentValidators.validate(schemaTool, definition.function.parameters, args);
600
604
  return validation.ok ? { ok: true, args } : { ok: false, error: `[tool schema error] ${validation.error}` };
601
605
  }
602
606
  async execute(tool, argsStr, wsPath, context = {}) {
@@ -918,7 +922,9 @@ class ToolExecutor {
918
922
  });
919
923
  }
920
924
  case 'ssh_workspace': return await this.sshWorkspace(args, wsPath, context.signal);
921
- case 'task': return `[task] Subagent request accepted: ${g('name')}`;
925
+ case 'task':
926
+ case 'subagent_create':
927
+ case 'SubAgent': return `[SubAgent] SubAgent request accepted: ${g('name')}`;
922
928
  case 'subagent_send': return `[subagent_send] Routed to Agent runtime: ${g('name')}`;
923
929
  case 'subagent_read': return `[subagent_read] Routed to Agent runtime: ${g('name') || g('id')}`;
924
930
  case 'subagent_result': return `[subagent_result] Routed to Agent runtime: ${g('name')}`;
@@ -33,7 +33,7 @@ exports.NATIVE_TOOL_CATALOG = [
33
33
  { name: 'pdf_read', label: 'PDF read', description: 'Read PDF text and render scanned pages through vision before local OCR fallback.', category: 'core', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
34
34
  { name: 'terminal_takeover', label: 'Terminal takeover', description: 'Maintain a persistent Agent-controlled shell session.', category: 'desktop', defaultEnabled: true },
35
35
  { name: 'ssh_workspace', label: 'OpenSSH workspace', description: 'Manage native OpenSSH connections and link remote workspaces by PC_Hash.', category: 'ssh', defaultEnabled: true },
36
- { name: 'task', label: 'Subagent task', description: 'Create a same-conversation peer agent.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
36
+ { name: 'SubAgent', label: 'SubAgent', description: 'Create one real same-conversation peer SubAgent.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
37
37
  { name: 'subagent_list', label: 'Subagent list', description: 'List same-conversation peer agents.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
38
38
  { name: 'subagent_read', label: 'Subagent read', description: 'Read bounded status, feedback, result, queue, and mailbox summaries for a same-conversation peer.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
39
39
  { name: 'subagent_send', label: 'Subagent send', description: 'Persist a message to a peer mailbox.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
@@ -154,6 +154,7 @@ function createCoreRuntimeAdapter(options = {}) {
154
154
  const { AutomationManager } = require(path.join(desktopDist, "core", "automation.js"));
155
155
  const { FlowEngine } = require(path.join(desktopDist, "core", "flow.js"));
156
156
  const installUpdate = require(path.join(desktopDist, "core", "installUpdate.js"));
157
+ const mobilePairing = require(path.join(desktopDist, "core", "mobilePairing.js"));
157
158
  const root = path.resolve(options.root || path.join(os.homedir(), ".Newmark"));
158
159
  const workspacePath = safeWorkspacePath(root, options.workspacePath || process.cwd());
159
160
  ensureRuntimeRoot(root, configModule);
@@ -211,7 +212,8 @@ function createCoreRuntimeAdapter(options = {}) {
211
212
  defaultTerminalShell: agent.config.getStr("terminal", "default_shell") || (process.platform === "win32" ? "powershell" : "bash"),
212
213
  status: agent.status,
213
214
  connected: true,
214
- runtimeRoot: root
215
+ runtimeRoot: root,
216
+ remoteTouchEnabled: agent.config.getBool('remote', 'touch_enabled')
215
217
  });
216
218
 
217
219
  const snapshotFor = (requested = currentTarget()) => {
@@ -473,6 +475,20 @@ function createCoreRuntimeAdapter(options = {}) {
473
475
  updateVersion() {
474
476
  return { version: installUpdate.currentAppVersion(), root };
475
477
  },
478
+ async pairingQr() {
479
+ const qr = await mobilePairing.pairingQrAscii(root);
480
+ return {
481
+ ascii: qr.ascii,
482
+ url: qr.session.url,
483
+ pairingId: qr.session.pairingId,
484
+ expiresAt: qr.session.expiresAt,
485
+ tokenFile: mobilePairing.pairingTokenPath(root),
486
+ tailscaleIpv4: mobilePairing.tailscaleIpv4(),
487
+ };
488
+ },
489
+ pairingStatus() {
490
+ return mobilePairing.pairingStatus(root);
491
+ },
476
492
  updateCheckGithub(input = {}) {
477
493
  return installUpdate.checkGitHubUpdate(input.repo, input.tag, input.asset, input.token);
478
494
  },
@@ -93,6 +93,46 @@ function executeAction(state, action) {
93
93
  const appearance = applyThemeAppearance(state, state.theme === "dark" ? "Light" : "Dark");
94
94
  state.adapter.saveConfig(appearance);
95
95
  state.notice = `${state.theme === "dark" ? "Dark" : "Light"} terminal theme`;
96
+ } else if (action === "pair-mobile") {
97
+ state.overlay = "pair";
98
+ state.pairingQrLines = ["Loading pairing QR…"];
99
+ state.pairingUrl = "";
100
+ state.pairingTokenFile = "";
101
+ if (typeof state.requestPaint === "function") state.requestPaint();
102
+ if (typeof state.adapter.pairingQr !== "function") {
103
+ state.pairingQrLines = ["Pairing QR is unavailable in this adapter."];
104
+ if (typeof state.requestPaint === "function") state.requestPaint();
105
+ return;
106
+ }
107
+ Promise.resolve(state.adapter.pairingQr())
108
+ .then((pairing) => {
109
+ state.pairingQrLines = String(pairing?.ascii || "").split(/\r?\n/);
110
+ state.pairingUrl = String(pairing?.url || "");
111
+ state.pairingTokenFile = String(pairing?.tokenFile || "");
112
+ if (typeof state.requestPaint === "function") state.requestPaint();
113
+ if (typeof state.adapter.pairingStatus === "function") {
114
+ if (state._pairingPoll) clearInterval(state._pairingPoll);
115
+ state._pairingPoll = setInterval(() => {
116
+ const status = state.adapter.pairingStatus();
117
+ if (!status) return;
118
+ if (status.confirmed) {
119
+ clearInterval(state._pairingPoll);
120
+ state.overlay = null;
121
+ state.notice = "Mobile device connected";
122
+ if (typeof state.requestPaint === "function") state.requestPaint();
123
+ } else if (status.expired || !status.active) {
124
+ clearInterval(state._pairingPoll);
125
+ state.overlay = null;
126
+ state.notice = "Pairing window expired";
127
+ if (typeof state.requestPaint === "function") state.requestPaint();
128
+ }
129
+ }, 1000);
130
+ }
131
+ })
132
+ .catch((error) => {
133
+ state.pairingQrLines = [`Pairing failed: ${error?.message || error}`];
134
+ if (typeof state.requestPaint === "function") state.requestPaint();
135
+ });
96
136
  } else if (action === "help") {
97
137
  state.overlay = "help";
98
138
  }
@@ -166,6 +206,7 @@ function start(options = {}) {
166
206
  return;
167
207
  }
168
208
  const state = createState({ adapter });
209
+ state.requestPaint = () => paint();
169
210
  let timer = null;
170
211
  let animationTimer = null;
171
212
  let closing = false;
@@ -315,6 +315,7 @@ const commands = [
315
315
  { label: "Open Automations", hint: "", action: "view:automation" },
316
316
  { label: "Open WorkFlow", hint: "", action: "view:workflow" },
317
317
  { label: "Open Settings", hint: "", action: "view:settings" },
318
+ { label: "Show mobile pairing QR", hint: "", action: "pair-mobile" },
318
319
  { label: "New conversation", hint: "N", action: "new-chat" },
319
320
  { label: "Toggle theme", hint: "T", action: "theme" },
320
321
  { label: "Keyboard shortcuts", hint: "?", action: "help" }
@@ -1154,6 +1154,17 @@ function overlayLines(state, width, p) {
1154
1154
  `${p.cyan}Press Esc or ? to close${p.reset}`
1155
1155
  ], Math.min(78, width - 4), p, "Keyboard shortcuts");
1156
1156
  }
1157
+ if (state.overlay === "pair") {
1158
+ const qrLines = Array.isArray(state.pairingQrLines) ? state.pairingQrLines : [];
1159
+ const rows = [
1160
+ ...qrLines,
1161
+ "",
1162
+ ...(state.pairingUrl ? [`${p.muted}${state.pairingUrl}${p.reset}`] : []),
1163
+ ...(state.pairingTokenFile ? [`${p.muted}Token: ${state.pairingTokenFile}${p.reset}`] : []),
1164
+ `${p.cyan}Scan with the Newmark mobile app · Esc close${p.reset}`
1165
+ ];
1166
+ return card(rows, Math.min(88, width - 4), p, "Pair mobile device");
1167
+ }
1157
1168
  if (state.overlay === "palette") {
1158
1169
  const commands = filteredCommands(state);
1159
1170
  const paletteViewport = Math.min(7, Math.max(1, commands.length));
@@ -23,7 +23,9 @@ function settingsRows(state, tab = state.settingsTab) {
23
23
  { key: "dialogStyle", label: "Conversation style", value: s.general.dialogStyle, choices: ["Formal", "Friendly"], save: ["config", "dialogStyle"] },
24
24
  { key: "feedbackLevel", label: "Option feedback", value: s.general.feedbackLevel, choices: ["Default", "Ask more", "Ask less", "Autonomous"], save: ["config", "feedbackLevel"] },
25
25
  { key: "closeBehavior", label: "Close behavior", value: s.general.closeBehavior, choices: ["Close app", "Minimize to tray"], save: ["setting", "general", "close_behavior"] },
26
- { key: "expandTools", label: "Expand tool usage", value: s.general.expandTools, choices: [true, false], save: ["setting", "general", "expand_tools"] }
26
+ { key: "expandTools", label: "Expand tool usage", value: s.general.expandTools, choices: [true, false], save: ["setting", "general", "expand_tools"] },
27
+ { key: "remoteTouch", label: "Remote behavior · Mobile remote-touch", value: s.general.remoteTouch, choices: [true, false], save: ["setting", "remote", "touch_enabled"] },
28
+ { key: "remotePair", label: "Remote behavior · Start connection", value: "Open QR", choices: [], action: "pair-mobile" }
27
29
  ];
28
30
  if (tab === "personalization") return [
29
31
  { key: "theme", label: "Theme", value: s.personalization.theme, choices: ["Dark", "Light", "System"], save: ["config", "theme"] },
@@ -253,7 +253,8 @@ function createState(options = {}) {
253
253
  dialogStyle: snapshot.dialogStyle === "friendly" ? "Friendly" : "Formal",
254
254
  feedbackLevel: { ask_more: "Ask more", ask_less: "Ask less", fully_autonomous: "Autonomous" }[snapshot.feedback] || "Default",
255
255
  closeBehavior: snapshot.closeBehavior === "minimize" ? "Minimize to tray" : "Close app",
256
- expandTools: snapshot.expandToolsDefault !== false
256
+ expandTools: snapshot.expandToolsDefault !== false,
257
+ remoteTouch: snapshot.remoteTouchEnabled !== false
257
258
  },
258
259
  personalization: {
259
260
  theme: { light: "Light", system: "System", dark: "Dark" }[String(snapshot.darkMode || "dark").toLowerCase()] || "Dark",
@@ -1071,6 +1072,25 @@ function toggleSelected(state) {
1071
1072
  }
1072
1073
  const row = settingsRows(state)[state.selected];
1073
1074
  if (!row) return;
1075
+ if (row.action === "pair-mobile") {
1076
+ state.overlay = "pair";
1077
+ state.pairingQrLines = ["Loading pairing QR…"];
1078
+ state.pairingUrl = "";
1079
+ state.pairingTokenFile = "";
1080
+ if (typeof state.adapter.pairingQr !== "function") {
1081
+ state.pairingQrLines = ["Pairing QR is unavailable in this adapter."];
1082
+ return false;
1083
+ }
1084
+ return Promise.resolve(state.adapter.pairingQr()).then((pairing) => {
1085
+ state.pairingQrLines = String(pairing?.ascii || "").split(/\r?\n/);
1086
+ state.pairingUrl = String(pairing?.url || "");
1087
+ state.pairingTokenFile = String(pairing?.tokenFile || "");
1088
+ return true;
1089
+ }).catch((error) => {
1090
+ state.pairingQrLines = [`Pairing failed: ${error?.message || error}`];
1091
+ return false;
1092
+ });
1093
+ }
1074
1094
  if (state.settingsTab === "general" && row.key === "inputBehavior") {
1075
1095
  const current = row.choices.findIndex((value) => value === row.value);
1076
1096
  state.settingChoiceTab = state.settingsTab;