newmark-agent 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/assets/app-icon-dark.svg +6 -0
  2. package/dist/assets/app-icon-dark.svg +6 -0
  3. package/dist/cli-commands.js +2 -1
  4. package/dist/cli-discovery.js +2 -0
  5. package/dist/conversation-utility-host.bundle.cjs +577 -166
  6. package/dist/conversation-utility-host.js +3 -0
  7. package/dist/core/agent.d.ts +40 -4
  8. package/dist/core/agent.js +251 -49
  9. package/dist/core/agentKernelRunner.d.ts +3 -0
  10. package/dist/core/agentKernelRunner.js +74 -91
  11. package/dist/core/config.js +1 -1
  12. package/dist/core/conversationKernel.d.ts +40 -0
  13. package/dist/core/conversationKernel.js +219 -8
  14. package/dist/core/electronUtilityAgentClient.d.ts +8 -0
  15. package/dist/core/electronUtilityAgentClient.js +5 -1
  16. package/dist/core/electronUtilityRuntimePool.d.ts +15 -0
  17. package/dist/core/electronUtilityRuntimePool.js +11 -0
  18. package/dist/core/installUpdate.js +11 -8
  19. package/dist/core/mobilePairing.d.ts +1 -0
  20. package/dist/core/mobilePairing.js +15 -1
  21. package/dist/core/subagent.d.ts +10 -3
  22. package/dist/core/subagent.js +23 -8
  23. package/dist/core/toolPolicy.js +14 -3
  24. package/dist/core/utilityAgentProtocol.d.ts +29 -1
  25. package/dist/core/utilityHostToolRouter.js +18 -0
  26. package/dist/core/wslAgentClient.d.ts +8 -0
  27. package/dist/core/wslAgentClient.js +5 -1
  28. package/dist/core/wslAgentProtocol.d.ts +18 -1
  29. package/dist/core/wslAgentRuntimePool.d.ts +15 -0
  30. package/dist/core/wslAgentRuntimePool.js +11 -0
  31. package/dist/launcher.js +14 -11
  32. package/dist/main.js +207 -9
  33. package/dist/providers/chat-completions.adapter.js +6 -2
  34. package/dist/providers/responses.adapter.js +1 -0
  35. package/dist/server.d.ts +33 -1
  36. package/dist/server.js +696 -48
  37. package/dist/toolchain/registry-seeder.js +3 -1
  38. package/dist/tools/index.js +57 -6
  39. package/dist/tools/nativeTools.js +2 -1
  40. package/dist/ui/index.html +88 -101
  41. package/dist/wsl-agent-host.bundle.cjs +577 -166
  42. package/dist/wsl-agent-host.js +3 -0
  43. package/package.json +4 -2
@@ -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';
@@ -294,6 +294,13 @@ class ToolExecutor {
294
294
  max_refs: { type: 'number' },
295
295
  attribute: { type: 'string' },
296
296
  }, ['action']),
297
+ t('screen_capture', 'Read-only active Windows screenshot available without provisioning or starting full Computer Use. Capture the whole desktop or one visible application. The image is a one-use model input and is deleted immediately; semantic Windows UI objects are returned alongside it.', {
298
+ target: { type: 'string', enum: ['desktop', 'application'], description: 'Capture the whole desktop or one visible application. Defaults to desktop.' },
299
+ app_target: { type: 'string', description: 'For target=application: application title, process name, process id, or window handle.' },
300
+ capture_max_width: { type: 'number', minimum: 320, maximum: 2048, description: 'Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged.' },
301
+ capture_max_height: { type: 'number', minimum: 240, maximum: 2048, description: 'Maximum ephemeral screenshot height; defaults to 960. Aspect ratio is preserved and the source is never enlarged.' },
302
+ max_chars: { type: 'number', minimum: 1000, maximum: 100000, description: 'Maximum returned semantic UI text characters.' },
303
+ }, []),
297
304
  t('computer_use', 'Native Windows desktop control with persistent observation/action helpers. Use observe/app_observe before acting. sequence may perform up to three stable low-risk move/click/scroll/wait/app_activate steps and stops when focus, window, menu, dialog, scene, or risk changes. Vision screenshots remain one-time inputs and are deleted immediately.', {
298
305
  action: { type: 'string', enum: ['observe', 'app_list', 'app_observe', 'sequence', 'takeover_start', 'takeover_stop', 'move', 'click', 'scroll', 'type', 'key', 'wait', 'app_activate', 'app_click', 'app_scroll', 'app_type', 'app_key'] },
299
306
  capture_max_width: { type: 'number', minimum: 320, maximum: 2048, description: 'For observe/app_observe only. Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged.' },
@@ -379,7 +386,7 @@ class ToolExecutor {
379
386
  remote_root: { type: 'string' },
380
387
  remote_path: { type: 'string' },
381
388
  }, ['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']),
389
+ 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
390
  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
391
  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
392
  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 +566,11 @@ class ToolExecutor {
559
566
  return (0, compat_1.normalizeToolResult)(output, { tool, workspacePath: wsPath, mode: context.mode || '' });
560
567
  }
561
568
  validateInvocation(tool, argsStr, _mode = '', inputSchema) {
562
- if (inputSchema === undefined && !(0, nativeTools_1.isNativeToolEnabled)(tool, this.config.nativeToolEnabled())) {
569
+ // Persisted work history may replay the old create aliases. They share the
570
+ // public SubAgent schema and enablement, but are never advertised back to
571
+ // the model as independent tools.
572
+ const schemaTool = tool === 'task' || tool === 'subagent_create' ? 'SubAgent' : tool;
573
+ if (inputSchema === undefined && !(0, nativeTools_1.isNativeToolEnabled)(schemaTool, this.config.nativeToolEnabled())) {
563
574
  return { ok: false, error: `[tool disabled] ${tool} is disabled in Settings > Tools.` };
564
575
  }
565
576
  let parsed;
@@ -591,12 +602,12 @@ class ToolExecutor {
591
602
  // catalogs may advertise a narrower enum, but a structurally valid
592
603
  // hidden call must reach evaluateToolPolicy so it is classified as a
593
604
  // policy denial rather than a misleading schema failure.
594
- ? this.definitions().find(candidate => candidate.function?.name === tool)
605
+ ? this.definitions().find(candidate => candidate.function?.name === schemaTool)
595
606
  : { function: { name: tool, parameters: inputSchema } };
596
607
  if (!definition) {
597
608
  return { ok: false, error: `[tool unsupported] ${tool || '(missing tool)'} is not available for the ${this.hostProfile.kind} host on ${this.hostProfile.platform}.` };
598
609
  }
599
- const validation = this.argumentValidators.validate(tool, definition.function.parameters, args);
610
+ const validation = this.argumentValidators.validate(schemaTool, definition.function.parameters, args);
600
611
  return validation.ok ? { ok: true, args } : { ok: false, error: `[tool schema error] ${validation.error}` };
601
612
  }
602
613
  async execute(tool, argsStr, wsPath, context = {}) {
@@ -786,6 +797,44 @@ class ToolExecutor {
786
797
  result: parsed,
787
798
  }, null, 2);
788
799
  }
800
+ case 'screen_capture': {
801
+ const target = g('target').toLowerCase() === 'application' ? 'application' : 'desktop';
802
+ if (process.env.NEWMARK_WSL_DISTRO) {
803
+ const result = await (0, wslHostToolBridge_1.requestWindowsHostTool)('screen_capture', args, {
804
+ conversationId: context.conversationId || 'default',
805
+ workspaceId: context.workspaceId || (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(context.workspacePath || wsPath),
806
+ actorId: context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID,
807
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
808
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
809
+ mode: context.mode || 'build',
810
+ }, 120_000, context.signal);
811
+ return typeof result === 'string' ? result : JSON.stringify(result);
812
+ }
813
+ if (process.env.NEWMARK_ISOLATED_RUNTIME === '1') {
814
+ const result = await (0, utilityHostToolBridge_1.requestUtilityHostTool)('screen_capture', args, {
815
+ conversationId: context.conversationId || 'default',
816
+ workspaceId: context.workspaceId || (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(context.workspacePath || wsPath),
817
+ actorId: context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID,
818
+ workspacePath: context.workspacePath || wsPath,
819
+ backend: 'utility',
820
+ mode: context.mode || 'build',
821
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
822
+ }, 120_000, context.signal);
823
+ return typeof result === 'string' ? result : JSON.stringify(result);
824
+ }
825
+ return await (0, computerUse_1.runComputerUse)({
826
+ action: target === 'application' ? 'app_observe' : 'observe',
827
+ appTarget: g('app_target'),
828
+ windowHandle: target === 'application' ? g('app_target') : '',
829
+ maxChars: Number(args.max_chars || 30000),
830
+ workspacePath: wsPath,
831
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
832
+ captureMaxWidth: Number(args.capture_max_width),
833
+ captureMaxHeight: Number(args.capture_max_height),
834
+ invocation: context.invocation,
835
+ ownerId: `screen-capture:${computerUseOwner(context, wsPath)}:${String(context.actorId || 'root')}`,
836
+ });
837
+ }
789
838
  case 'computer_use': {
790
839
  const action = normalizeComputerUseAction(g('action'));
791
840
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || 'root')}`;
@@ -918,7 +967,9 @@ class ToolExecutor {
918
967
  });
919
968
  }
920
969
  case 'ssh_workspace': return await this.sshWorkspace(args, wsPath, context.signal);
921
- case 'task': return `[task] Subagent request accepted: ${g('name')}`;
970
+ case 'task':
971
+ case 'subagent_create':
972
+ case 'SubAgent': return `[SubAgent] SubAgent request accepted: ${g('name')}`;
922
973
  case 'subagent_send': return `[subagent_send] Routed to Agent runtime: ${g('name')}`;
923
974
  case 'subagent_read': return `[subagent_read] Routed to Agent runtime: ${g('name') || g('id')}`;
924
975
  case 'subagent_result': return `[subagent_result] Routed to Agent runtime: ${g('name')}`;
@@ -970,7 +1021,7 @@ class ToolExecutor {
970
1021
  hostSupportsTool(name) {
971
1022
  if (name.startsWith('browser_') && !this.hostProfile.electronBrowser)
972
1023
  return false;
973
- if (name === 'computer_use' && !this.hostProfile.windowsComputerUse)
1024
+ if ((name === 'computer_use' || name === 'screen_capture') && !this.hostProfile.windowsComputerUse)
974
1025
  return false;
975
1026
  return true;
976
1027
  }
@@ -26,6 +26,7 @@ exports.NATIVE_TOOL_CATALOG = [
26
26
  { name: 'browser_reload', label: 'Browser reload', description: 'Reload the built-in browser.', category: 'browser', defaultEnabled: true },
27
27
  { name: 'browser_cdp', label: 'Browser CDP', description: 'Run an advanced Chrome DevTools Protocol command.', category: 'browser', defaultEnabled: true },
28
28
  { name: 'browser_use', label: 'Browser Use', description: 'Observe and control the built-in browser through scoped opaque element refs and structured receipts.', category: 'browser', defaultEnabled: true },
29
+ { name: 'screen_capture', label: 'Active screenshot', description: 'Capture the Windows desktop or one visible application without starting the full Computer Use control surface.', category: 'desktop', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
29
30
  { name: 'computer_use', label: 'Computer Use', description: 'Observe and control Windows desktop UI with screenshots and semantic objects.', category: 'desktop', defaultEnabled: true },
30
31
  { name: 'image_inspect', label: 'Image inspect', description: 'Crop and magnify images submitted in the current conversation for closer visual inspection.', category: 'core', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
31
32
  { name: 'image_display', label: 'Image display', description: 'Present a workspace PNG/JPEG inside the current Build Block.', category: 'core', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
@@ -33,7 +34,7 @@ exports.NATIVE_TOOL_CATALOG = [
33
34
  { 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
35
  { name: 'terminal_takeover', label: 'Terminal takeover', description: 'Maintain a persistent Agent-controlled shell session.', category: 'desktop', defaultEnabled: true },
35
36
  { 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' },
37
+ { name: 'SubAgent', label: 'SubAgent', description: 'Create one real same-conversation peer SubAgent.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
37
38
  { name: 'subagent_list', label: 'Subagent list', description: 'List same-conversation peer agents.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
38
39
  { 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
40
  { name: 'subagent_send', label: 'Subagent send', description: 'Persist a message to a peer mailbox.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
@@ -428,38 +428,15 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
428
428
  }
429
429
 
430
430
  #topbar .logo {
431
- position: relative;
432
431
  display: inline-flex;
433
432
  width: 26px;
434
433
  height: 26px;
435
- padding: 1px;
436
- border-radius: 8px;
437
434
  align-items: center;
438
435
  justify-content: center;
439
- overflow: hidden;
440
- box-shadow: 0 0 12px var(--accent-glow);
441
436
  flex-shrink: 0;
442
437
  }
443
438
 
444
- #topbar .logo::before {
445
- content: "";
446
- position: absolute;
447
- inset: calc(-2 * var(--marquee-width));
448
- background: conic-gradient(from var(--marquee-angle, 0deg), var(--g1), var(--g2), var(--g3), var(--g4), var(--g1));
449
- animation: marquee-rotate var(--marquee-speed) linear infinite;
450
- }
451
-
452
- #topbar .logo::after {
453
- content: "";
454
- position: absolute;
455
- inset: 2px;
456
- border-radius: 7px;
457
- background: var(--bg2);
458
- }
459
-
460
439
  #topbar .logo img {
461
- position: relative;
462
- z-index: 1;
463
440
  display: block;
464
441
  width: 22px;
465
442
  height: 22px;
@@ -3832,11 +3809,15 @@ button.todo-item { background: transparent; }
3832
3809
  linear-gradient(#fff 0 0) content-box,
3833
3810
  linear-gradient(#fff 0 0);
3834
3811
  mask-composite: exclude;
3835
- animation: marquee-rotate var(--marquee-speed) linear infinite;
3812
+ /* A masked conic-gradient whose custom angle changes every frame forces a
3813
+ full GPU repaint. Several simultaneous Agent/SubAgent surfaces made that
3814
+ repaint saturate the renderer and GPU, starving ordinary text input. Keep
3815
+ the shared status border visually present, but static; live progress is
3816
+ already communicated by status text, duration, and event updates. */
3817
+ animation: none;
3836
3818
  z-index: -1;
3837
3819
  pointer-events: none;
3838
3820
  transform: translateZ(0);
3839
- will-change: transform;
3840
3821
  }
3841
3822
 
3842
3823
  @supports not (background: conic-gradient(from 0deg, red, blue)) {
@@ -3844,11 +3825,11 @@ button.todo-item { background: transparent; }
3844
3825
  background: linear-gradient(var(--marquee-angle, 0deg), var(--g1), var(--g2), var(--g3), var(--g4), var(--g1));
3845
3826
  }
3846
3827
  @keyframes marquee-rotate-fallback { 0% { filter: hue-rotate(0deg); } 100% { filter: hue-rotate(360deg); } }
3847
- .marquee-border::before { animation: marquee-rotate-fallback var(--marquee-speed) linear infinite; }
3828
+ .marquee-border::before { animation: none; }
3848
3829
  }
3849
3830
 
3850
3831
  .working-glow {
3851
- animation: working-border-glow 2s ease-in-out infinite;
3832
+ animation: none;
3852
3833
  box-shadow: 0 0 16px var(--accent-glow), 0 0 40px rgba(91,120,255,0.1);
3853
3834
  border-color: rgba(91,120,255,0.4) !important;
3854
3835
  }
@@ -5833,7 +5814,7 @@ html.newmark-memory-overview-viewer body {
5833
5814
  <div id="topbar">
5834
5815
  <span class="title" id="title-text">
5835
5816
  <span class="logo" id="title-app-logo">
5836
- <img id="title-app-icon" src="../assets/app-icon-dark-64.png" alt="" aria-hidden="true">
5817
+ <img id="title-app-icon" src="../assets/app-icon-dark.svg" alt="" aria-hidden="true">
5837
5818
  </span>
5838
5819
  Newmark Agent
5839
5820
  </span>
@@ -6502,10 +6483,10 @@ var NEWMARK_I18N = {
6502
6483
  'settings.runInWsl': 'Agent runtime environment',
6503
6484
  'settings.runInWslDesc': 'Keeps the Windows UI and native desktop tools local while running the persistent Agent kernel, model calls, shell, and file tools inside WSL.',
6504
6485
  'settings.remoteTouch': 'Mobile remote-touch',
6505
- 'settings.remoteTouchDesc': 'Allow the mobile app to reach this Newmark instance over Tailscale. Turn off to disable remote pairing and sync APIs immediately.',
6486
+ 'settings.remoteTouchDesc': 'Allow mobile access on the same LAN / Tailscale network',
6506
6487
  'settings.remoteBehavior': 'Remote behavior',
6507
6488
  'settings.remoteConnect': 'Start connection',
6508
- 'settings.remoteConnectDesc': 'Show the pairing QR code for the Newmark mobile app.',
6489
+ 'settings.remoteConnectDesc': 'Show pairing QR',
6509
6490
  'settings.wslDistro': 'WSL distribution',
6510
6491
  'settings.wslConnected': 'WSL backend connected',
6511
6492
  'settings.wslDisconnected': 'WSL backend unavailable',
@@ -7030,7 +7011,7 @@ var NEWMARK_I18N = {
7030
7011
  'option.manualPlaceholder': 'Or type your own answer...',
7031
7012
  'option.manualSubmit': 'Submit',
7032
7013
  'mobile.pair': 'Pair mobile device',
7033
- 'mobile.pairDesc': 'Scan this QR code with the Newmark mobile app over Tailscale. The pairing token is saved after the first bind.',
7014
+ 'mobile.pairDesc': 'Scan this QR code with the Newmark mobile app on the same LAN / Tailscale network. The pairing token is saved after the first bind.',
7034
7015
  'mobile.pairTokenFile': 'Token file',
7035
7016
  'subagent.empty': 'No retained subagent records',
7036
7017
  'subagent.openHistory': 'Open subagent history',
@@ -7229,10 +7210,10 @@ var NEWMARK_I18N = {
7229
7210
  'settings.runInWsl': 'Agent 运行环境',
7230
7211
  'settings.runInWslDesc': 'Windows UI 与桌面原生工具保留在本机,持续 Agent 内核、模型请求、Shell 和文件工具运行在 WSL。',
7231
7212
  'settings.remoteTouch': '移动端远程触及',
7232
- 'settings.remoteTouchDesc': '允许移动端通过 Tailscale 连接本机 Newmark。关闭后立即禁用远程配对与同步 API。',
7213
+ 'settings.remoteTouchDesc': '允许移动端通过同一内网 / Tailscale 连接',
7233
7214
  'settings.remoteBehavior': '远程行为',
7234
7215
  'settings.remoteConnect': '发起连接',
7235
- 'settings.remoteConnectDesc': '显示 Newmark 移动端配对二维码。',
7216
+ 'settings.remoteConnectDesc': '显示配对二维码',
7236
7217
  'settings.wslDistro': 'WSL 发行版',
7237
7218
  'settings.wslConnected': 'WSL 后端已连接',
7238
7219
  'settings.wslDisconnected': 'WSL 后端不可用',
@@ -7757,7 +7738,7 @@ var NEWMARK_I18N = {
7757
7738
  'option.manualPlaceholder': '或输入自己的答案...',
7758
7739
  'option.manualSubmit': '提交',
7759
7740
  'mobile.pair': '配对移动设备',
7760
- 'mobile.pairDesc': '使用 Newmark 移动端通过 Tailscale 扫描二维码。首次绑定后保存配对令牌。',
7741
+ 'mobile.pairDesc': '使用 Newmark 移动端在同一内网 / Tailscale 网络下扫描二维码。首次绑定后保存配对令牌。',
7761
7742
  'mobile.pairTokenFile': '令牌文件',
7762
7743
  'subagent.empty': '暂无保留的 subagent 记录',
7763
7744
  'subagent.openHistory': '打开 subagent 历史',
@@ -10172,7 +10153,7 @@ function workEventIcon(type) {
10172
10153
 
10173
10154
  function workToolActivity(event) {
10174
10155
  var name = publicToolNameForUi(event && event.toolName).toLowerCase();
10175
- if (name === 'task' || name.indexOf('subagent_') === 0) return { key: 'subagents', type: 'tool_subagent' };
10156
+ if (isSubagentCreateToolName(name) || name.indexOf('subagent_') === 0) return { key: 'subagents', type: 'tool_subagent' };
10176
10157
  if (name === 'skill' || name === 'skill_load' || name === 'skill_read' || name === 'skill_download') return { key: 'skills', type: 'tool_skill' };
10177
10158
  if (name === 'mcp' || name.indexOf('mcp__') === 0 || /^mcp[:._-]/.test(name) || /[:._-]mcp[:._-]/.test(name)) return { key: 'mcp', type: 'tool_mcp' };
10178
10159
  if (name === 'context_compression' || name === 'context_compress' || name === 'context_history_manage') return { key: 'context_compression', type: 'tool_context_compression' };
@@ -10213,8 +10194,8 @@ function workToolCommandLabel(item) {
10213
10194
  var args = workToolArgsObject(item);
10214
10195
  var toolName = publicToolNameForUi(item && item.toolName);
10215
10196
  var normalizedName = toolName.toLowerCase();
10216
- if (normalizedName === 'task') {
10217
- return (currentLang() === 'zh' ? '调用 Subagent · ' : 'Called Subagent · ') + String(args.nature || args.name || args.agent || 'Subagent').trim();
10197
+ if (isSubagentCreateToolName(normalizedName)) {
10198
+ return (currentLang() === 'zh' ? '调用 Subagent · ' : 'Called Subagent · ') + String(args.name || args.nature || args.agent || 'Subagent').trim();
10218
10199
  }
10219
10200
  if (normalizedName.indexOf('subagent_') === 0) {
10220
10201
  return (currentLang() === 'zh' ? 'Subagent 操作 · ' : 'Subagent action · ') + String(args.name || args.id || normalizedName.replace(/^subagent_/, '')).trim();
@@ -10253,14 +10234,19 @@ function workToolCommandLabel(item) {
10253
10234
 
10254
10235
  function workToolRowIcon(item) {
10255
10236
  var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10256
- if (name === 'task' || name.indexOf('subagent_') === 0) return 'bot';
10237
+ if (isSubagentCreateToolName(name) || name.indexOf('subagent_') === 0) return 'bot';
10257
10238
  if (name === 'skill' || name === 'skill_download') return 'sparkles';
10258
10239
  if (name === 'mcp' || /^mcp[:._-]/.test(name) || /[:._-]mcp[:._-]/.test(name)) return 'plug';
10259
10240
  return 'square-terminal';
10260
10241
  }
10261
10242
 
10262
- function findSubagentUiRecord(name) {
10243
+ function isSubagentCreateToolName(name) {
10263
10244
  var normalized = String(name || '').trim().toLowerCase();
10245
+ return normalized === 'subagent' || normalized === 'subagent_create' || normalized === 'task';
10246
+ }
10247
+
10248
+ function findSubagentUiRecord(identity) {
10249
+ var normalized = String(identity || '').trim().toLowerCase();
10264
10250
  return (state.subagents || []).find(function(item) {
10265
10251
  return [item.id, item.name, item.displayName, item.qualifiedName].some(function(value) {
10266
10252
  var candidate = String(value || '').trim().toLowerCase();
@@ -10304,7 +10290,7 @@ function workToolGroupLabel(items, completed) {
10304
10290
  var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10305
10291
  if (name === 'skill' || name === 'skill_load' || name === 'skill_read' || name === 'skill_download') skills.push(workToolCommandLabel(item));
10306
10292
  else if (name === 'mcp' || name.indexOf('mcp__') === 0 || /^mcp[:._-]/.test(name) || /[:._-]mcp[:._-]/.test(name)) mcps.push(workToolCommandLabel(item));
10307
- else if (name === 'task' || name.indexOf('subagent_') === 0) subagents.push(workToolCommandLabel(item));
10293
+ else if (isSubagentCreateToolName(name) || name.indexOf('subagent_') === 0) subagents.push(workToolCommandLabel(item));
10308
10294
  });
10309
10295
  var commands = Math.max(0, (items || []).length - files - skills.length - mcps.length - subagents.length);
10310
10296
  var parts = [];
@@ -10337,24 +10323,8 @@ function renderWorkToolGroup(event, eventIndex) {
10337
10323
  var hasFiles = items.some(function(item) { return !!workToolEditedFile(item); });
10338
10324
  var onlySubagents = items.length > 0 && items.every(function(item) {
10339
10325
  var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10340
- return name === 'task';
10326
+ return isSubagentCreateToolName(name);
10341
10327
  });
10342
- // A Build Block that spawns several SubAgents is summarized as one chip
10343
- // ("operated N SubAgents") instead of flooding the block with one chip per
10344
- // SubAgent. Each name stays available in the tooltip.
10345
- var subagentTasks = items.map(function(item, itemIndex) {
10346
- var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10347
- if (name !== 'task') return null;
10348
- var subagentArgs = workToolArgsObject(item);
10349
- var subagentName = String(subagentArgs.nature || subagentArgs.name || subagentArgs.agent || 'Subagent').trim();
10350
- var subagentRecord = findSubagentUiRecord(subagentName);
10351
- return {
10352
- key: String(item.id || item.toolCallId || ('task-' + itemIndex)),
10353
- subagentName: subagentName || 'Subagent',
10354
- status: String(subagentRecord && subagentRecord.status || 'accepted'),
10355
- };
10356
- }).filter(Boolean);
10357
- var collapseSubagents = subagentTasks.length > 1;
10358
10328
  var activityRows = items.map(function(item, itemIndex) {
10359
10329
  var edited = workToolEditedFile(item);
10360
10330
  if (edited) {
@@ -10367,26 +10337,22 @@ function renderWorkToolGroup(event, eventIndex) {
10367
10337
  '<div class="conversation-work-file-diff">' + renderWorkFileDiff(edited) + '</div></details>';
10368
10338
  }
10369
10339
  var toolName = publicToolNameForUi(item && item.toolName).toLowerCase();
10370
- if (toolName === 'task') {
10371
- if (collapseSubagents) return '';
10340
+ if (isSubagentCreateToolName(toolName)) {
10372
10341
  var subagentArgs = workToolArgsObject(item);
10373
- var subagentName = String(subagentArgs.nature || subagentArgs.name || subagentArgs.agent || 'Subagent').trim();
10374
- var subagentRecord = findSubagentUiRecord(subagentName);
10375
- var subagentStatus = String(subagentRecord && subagentRecord.status || 'accepted');
10376
- return '<button type="button" class="conversation-work-subagent-chip' + (subagentStatus === 'completed' ? ' completed' : '') + '" data-subagent-name="' + escAttr(subagentName) + '" onclick="window.openSubagentFromBuild(this.getAttribute(\'data-subagent-name\'))" title="' + escAttr(currentLang() === 'zh' ? '查看 Subagent 工作情况' : 'View Subagent work') + '">' +
10377
- iconSvg('bot', 'Subagent', 'tiny') + '<span class="conversation-work-subagent-name">' + esc(subagentName) + '</span><span class="conversation-work-subagent-status">' + esc(subagentUiStatusLabel(subagentStatus)) + '</span></button>';
10342
+ var requestedName = String(subagentArgs.name || subagentArgs.nature || subagentArgs.agent || 'Subagent').trim();
10343
+ var subagentRecord = findSubagentUiRecord(requestedName);
10344
+ if (!subagentRecord || !subagentRecord.id) {
10345
+ return '<div class="conversation-work-activity-item">' + iconSvg('square-terminal', 'tool', 'tiny') +
10346
+ '<div><div class="conversation-work-command-label" title="' + escAttr(workToolCommandLabel(item)) + '">' + esc(workToolCommandLabel(item)) + '</div></div></div>';
10347
+ }
10348
+ var subagentStatus = String(subagentRecord.status || 'accepted');
10349
+ var createdName = String(subagentRecord.name || subagentRecord.displayName || 'Subagent');
10350
+ return '<button type="button" class="conversation-work-subagent-chip' + (subagentStatus === 'completed' ? ' completed' : '') + '" data-subagent-id="' + escAttr(subagentRecord.id) + '" onclick="window.openSubagentFromBuild(this.getAttribute(\'data-subagent-id\'))" title="' + escAttr(currentLang() === 'zh' ? '查看 Subagent 工作情况' : 'View Subagent work') + '">' +
10351
+ iconSvg('bot', 'Subagent', 'tiny') + '<span class="conversation-work-subagent-name">' + esc(createdName) + '</span><span class="conversation-work-subagent-status">' + esc(subagentUiStatusLabel(subagentStatus)) + '</span></button>';
10378
10352
  }
10379
10353
  return '<div class="conversation-work-activity-item">' + iconSvg(workToolRowIcon(item), 'tool', 'tiny') +
10380
10354
  '<div><div class="conversation-work-command-label" title="' + escAttr(workToolCommandLabel(item)) + '">' + esc(workToolCommandLabel(item)) + '</div>' + renderWorkDisplayImage(item.displayImage) + '</div></div>';
10381
10355
  }).join('');
10382
- if (collapseSubagents) {
10383
- var allCompleted = subagentTasks.every(function(task) { return task.status === 'completed'; });
10384
- var names = subagentTasks.map(function(task) { return task.subagentName; });
10385
- activityRows += '<button type="button" class="conversation-work-subagent-chip conversation-work-subagent-chip-multi' + (allCompleted ? ' completed' : '') + '" title="' + escAttr(names.join(',')) + '">' +
10386
- iconSvg('bot', 'Subagent', 'tiny') + '<span class="conversation-work-subagent-name">' +
10387
- esc(currentLang() === 'zh' ? '操作了 ' + subagentTasks.length + ' 个 SubAgent' : 'Operated ' + subagentTasks.length + ' SubAgents') +
10388
- '</span><span class="conversation-work-subagent-status">' + esc(subagentUiStatusLabel(allCompleted ? 'completed' : subagentTasks[0].status)) + '</span></button>';
10389
- }
10390
10356
  return '<details class="conversation-work-activity" data-activity-key="' + escAttr(activityDomKey) + '"><summary>' +
10391
10357
  iconSvg(hasFiles ? 'pencil' : 'square-terminal', 'tools', 'tiny') + '<span>' + esc(workToolGroupLabel(items, !!event.completed)) + '</span>' +
10392
10358
  '<span class="conversation-work-activity-chevron" aria-hidden="true"></span></summary><div class="conversation-work-activity-list' + (onlySubagents ? ' conversation-work-subagent-list' : '') + '">' +
@@ -10688,17 +10654,32 @@ function renderConversationWorkRuns(runs, target) {
10688
10654
  // every delta can starve Chromium's renderer/CDP event loop.
10689
10655
  var pendingConversationWorkRunRenders = {};
10690
10656
  var scheduledConversationWorkRunRenders = {};
10657
+ var lastConversationWorkRunRenderAt = {};
10658
+ var WORK_RUN_RENDER_MIN_INTERVAL_MS = 100;
10659
+ function flushConversationWorkRunRender(key) {
10660
+ var latest = pendingConversationWorkRunRenders[key];
10661
+ delete pendingConversationWorkRunRenders[key];
10662
+ delete scheduledConversationWorkRunRenders[key];
10663
+ if (!latest || !isActiveConversationTarget(latest.target)) return;
10664
+ lastConversationWorkRunRenderAt[key] = performance.now();
10665
+ renderConversationWorkRun(latest);
10666
+ }
10691
10667
  function scheduleConversationWorkRunRender(run) {
10692
10668
  var key = runtimeKeyFor(run.target && run.target.workspaceId, run.target && run.target.conversationId) + '::' + String(run.runId || '');
10693
10669
  pendingConversationWorkRunRenders[key] = run;
10694
10670
  if (scheduledConversationWorkRunRenders[key]) return;
10695
- scheduledConversationWorkRunRenders[key] = true;
10696
- requestAnimationFrame(function() {
10697
- var latest = pendingConversationWorkRunRenders[key];
10698
- delete pendingConversationWorkRunRenders[key];
10699
- delete scheduledConversationWorkRunRenders[key];
10700
- if (latest && isActiveConversationTarget(latest.target)) renderConversationWorkRun(latest);
10701
- });
10671
+ var elapsed = performance.now() - Number(lastConversationWorkRunRenderAt[key] || 0);
10672
+ var delay = Math.max(0, WORK_RUN_RENDER_MIN_INTERVAL_MS - elapsed);
10673
+ scheduledConversationWorkRunRenders[key] = setTimeout(function() {
10674
+ requestAnimationFrame(function() { flushConversationWorkRunRender(key); });
10675
+ }, delay);
10676
+ }
10677
+
10678
+ function renderConversationWorkRunImmediately(run) {
10679
+ var key = runtimeKeyFor(run.target && run.target.workspaceId, run.target && run.target.conversationId) + '::' + String(run.runId || '');
10680
+ pendingConversationWorkRunRenders[key] = run;
10681
+ if (scheduledConversationWorkRunRenders[key]) clearTimeout(scheduledConversationWorkRunRenders[key]);
10682
+ flushConversationWorkRunRender(key);
10702
10683
  }
10703
10684
 
10704
10685
  function applyAgentWorkEventToRun(event) {
@@ -10761,7 +10742,9 @@ function applyAgentWorkEventToRun(event) {
10761
10742
  if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
10762
10743
  state.workRunsByTarget[runtimeKeyFor(target.workspaceId, target.conversationId)] = runs;
10763
10744
  var existingElement = findWorkRunElement(run);
10745
+ var terminalRender = ['done', 'error', 'interrupted', 'force_interrupted', 'final_response'].indexOf(type) >= 0;
10764
10746
  if (!existingElement || type === 'start') renderConversationWorkRun(run);
10747
+ else if (terminalRender) renderConversationWorkRunImmediately(run);
10765
10748
  else scheduleConversationWorkRunRender(run);
10766
10749
  }
10767
10750
  return run;
@@ -11499,7 +11482,7 @@ function appendAgentWorkEvent(event) {
11499
11482
  setWorking(!!runningConversationRecord(activeConversationId()) || currentFlowRunning());
11500
11483
  updateSubmitButtonState();
11501
11484
  }
11502
- if (active && event.type === 'tool_result' && ['linked_plan', 'task', 'subagent_send', 'subagent_read', 'subagent_close'].indexOf(String(event.toolName || '')) >= 0 && api.getState) {
11485
+ if (active && event.type === 'tool_result' && ['linked_plan', 'task_read', 'task_create', 'task', 'subagent_create', 'SubAgent', 'subagent_send', 'subagent_read', 'subagent_close'].indexOf(String(event.toolName || '')) >= 0 && api.getState) {
11503
11486
  var eventWorkspaceKey = currentWorkspaceKey();
11504
11487
  api.getState(target).then(function(snapshot) {
11505
11488
  if (!isActiveConversationTarget(target) || eventWorkspaceKey !== currentWorkspaceKey()) return;
@@ -15557,14 +15540,14 @@ function renderGeneralSettings() {
15557
15540
  return wslHtml + '<div class="setting-subsection">' +
15558
15541
  '<div class="setting-subsection-title">' + esc(t('settings.remoteBehavior')) + '</div>' +
15559
15542
  '<div class="setting-row">' +
15560
- '<label class="setting-label" for="remote-touch-enabled">' + esc(t('settings.remoteTouch')) + '</label>' +
15561
- '<div class="setting-control"><label class="toggle"><input type="checkbox" id="remote-touch-enabled"' + (state.remoteTouchEnabled ? ' checked' : '') + ' onchange="window.setRemoteTouchEnabled(this.checked)"><span class="toggle-track"></span></label>' +
15562
- '<div class="setting-desc">' + esc(t('settings.remoteTouchDesc')) + '</div></div>' +
15543
+ '<label class="setting-label" for="remote-touch-enabled" style="display:inline-flex;align-items:center;gap:6px;min-width:120px;">' +
15544
+ '<input type="checkbox" id="remote-touch-enabled"' + (state.remoteTouchEnabled ? ' checked' : '') + ' onchange="window.setRemoteTouchEnabled(this.checked)">' +
15545
+ '<span>' + esc(t('settings.remoteTouch')) + '</span></label>' +
15546
+ '<span class="setting-desc" style="flex:1;min-width:140px;">' + esc(t('settings.remoteTouchDesc')) + '</span>' +
15563
15547
  '</div>' +
15564
- '<div class="setting-row">' +
15565
- '<span class="setting-label">' + esc(t('settings.remoteConnect')) + '</span>' +
15566
- '<div class="setting-control"><button type="button" class="sec-btn primary" onclick="window.showMobilePairing()">' + esc(t('settings.remoteConnect')) + '</button>' +
15567
- '<div class="setting-desc">' + esc(t('settings.remoteConnectDesc')) + '</div></div>' +
15548
+ '<div class="setting-row" style="border-bottom:none;">' +
15549
+ '<button type="button" class="sec-btn primary" onclick="window.showMobilePairing()">' + esc(t('settings.remoteConnect')) + '</button>' +
15550
+ '<span class="setting-desc" style="flex:1;">' + esc(t('settings.remoteConnectDesc')) + '</span>' +
15568
15551
  '</div>' +
15569
15552
  '</div>' +
15570
15553
  '<div class="setting-row">' +
@@ -16221,8 +16204,7 @@ var systemColorScheme = window.matchMedia('(prefers-color-scheme: dark)');
16221
16204
  window.refreshTitlebarThemeIcon = function() {
16222
16205
  var icon = document.getElementById('title-app-icon');
16223
16206
  if (!icon) return;
16224
- var useDarkTheme = state.theme === 'system' ? systemColorScheme.matches : state.theme !== 'light';
16225
- icon.src = useDarkTheme ? '../assets/app-icon-dark-64.png' : '../assets/app-icon-light-64.png';
16207
+ icon.src = '../assets/app-icon-dark.svg';
16226
16208
  };
16227
16209
  window.refreshTitlebarSystemIcon = window.refreshTitlebarThemeIcon;
16228
16210
  if (systemColorScheme.addEventListener) systemColorScheme.addEventListener('change', function() {
@@ -18503,9 +18485,9 @@ window.renderSubagentList = function() {
18503
18485
  var unread = Number(a.mailbox && a.mailbox.unread || 0);
18504
18486
  var queue = a.status === 'queued' && a.queueSequence ? (' · #' + a.queueSequence) : '';
18505
18487
  var meta = esc((a.agentMode || a.mode || 'build') + ' / ' + (a.model || t('common.default')) + ' / ' + ((a.messages || []).length || a.messageCount || 0) + ' ' + t('common.messageShort') + queue + (unread ? (' · ' + t('subagent.unread').replace('{count}', String(unread))) : ''));
18506
- var display = a.displayName || a.name || a.id || 'Agent';
18507
- var qualified = a.qualifiedName || a.name || a.id || display;
18508
- html += '<button type="button" onclick="window.openSubagentHistory(' + JSON.stringify(a.id || a.name).replace(/"/g, '&quot;') + ')" title="' + escAttr(t('subagent.openHistory')) + '" style="width:100%;display:flex;align-items:center;gap:6px;padding:7px 8px;font-size:12px;border:0;border-bottom:1px solid var(--glass-border-1);background:transparent;text-align:left;cursor:pointer;">' +
18488
+ var display = a.name || a.displayName || a.id || 'Agent';
18489
+ var qualified = a.qualifiedName || a.id || display;
18490
+ html += '<button type="button" data-subagent-id="' + escAttr(a.id || '') + '" onclick="window.openSubagentHistory(this.getAttribute(\'data-subagent-id\'))" title="' + escAttr(t('subagent.openHistory')) + '" style="width:100%;display:flex;align-items:center;gap:6px;padding:7px 8px;font-size:12px;border:0;border-bottom:1px solid var(--glass-border-1);background:transparent;text-align:left;cursor:pointer;">' +
18509
18491
  iconSvg('bot', 'Subagent', 'small') +
18510
18492
  '<span style="flex:1;min-width:0;color:var(--text);" title="' + escAttr(qualified) + '"><span style="display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + esc(display) + '</span><span style="display:block;color:var(--text-dim);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + meta + '</span></span>' +
18511
18493
  '<span style="color:var(--accent2);font-size:10px;">' + esc(a.status || t('common.idle')) + '</span>' +
@@ -18517,10 +18499,10 @@ window.renderSubagentList = function() {
18517
18499
  };
18518
18500
 
18519
18501
  window.refreshSubagentBuildChips = function() {
18520
- var chips = document.querySelectorAll('.conversation-work-subagent-chip[data-subagent-name]');
18502
+ var chips = document.querySelectorAll('.conversation-work-subagent-chip[data-subagent-id]');
18521
18503
  for (var i = 0; i < chips.length; i++) {
18522
18504
  var chip = chips[i];
18523
- var record = findSubagentUiRecord(chip.getAttribute('data-subagent-name'));
18505
+ var record = findSubagentUiRecord(chip.getAttribute('data-subagent-id'));
18524
18506
  var status = String(record && record.status || 'accepted');
18525
18507
  chip.classList.toggle('completed', status === 'completed');
18526
18508
  var statusEl = chip.querySelector('.conversation-work-subagent-status');
@@ -18546,15 +18528,15 @@ window.scheduleSubagentStateRefresh = function() {
18546
18528
  }, 1200);
18547
18529
  };
18548
18530
 
18549
- window.openSubagentFromBuild = function(name) {
18550
- var requested = String(name || '').trim();
18531
+ window.openSubagentFromBuild = function(id) {
18532
+ var requested = String(id || '').trim();
18551
18533
  var openLatest = function() {
18552
18534
  var agent = findSubagentUiRecord(requested);
18553
18535
  if (!agent) {
18554
18536
  showUiNotice((currentLang() === 'zh' ? '尚未找到 Subagent:' : 'Subagent is not available yet: ') + requested, 'error', 'subagent-build-' + requested);
18555
18537
  return false;
18556
18538
  }
18557
- window.openSubagentHistory(agent.id || agent.name);
18539
+ window.openSubagentHistory(agent.id);
18558
18540
  return true;
18559
18541
  };
18560
18542
  if (!api.getState) return Promise.resolve(openLatest());
@@ -18712,13 +18694,17 @@ window.closeSubagentHistory = function() {
18712
18694
 
18713
18695
  window.openSubagentHistory = function(id) {
18714
18696
  var agents = state.subagents || [];
18715
- var agent = agents.find(function(a) { return a.id === id || a.name === id; }) || {};
18716
- state.subagentHistoryActorId = agent.id || id || '';
18697
+ var agent = agents.find(function(a) { return a.id === id; });
18698
+ if (!agent) {
18699
+ showUiNotice((currentLang() === 'zh' ? '尚未找到真实 Subagent:' : 'Real Subagent is not available: ') + String(id || ''), 'error', 'subagent-history-' + String(id || 'missing'));
18700
+ return false;
18701
+ }
18702
+ state.subagentHistoryActorId = agent.id;
18717
18703
  var existing = document.getElementById('subagent-history-overlay');
18718
18704
  if (existing) existing.remove();
18719
18705
  var lines = [];
18720
- lines.push('<div style="font-size:13px;color:var(--text);font-weight:600;margin-bottom:4px;">' + esc(agent.displayName || agent.name || t('subagent.historyTitle')) + '</div>');
18721
- lines.push('<div style="font-size:10px;color:var(--text-dim);word-break:break-all;margin-bottom:8px;">' + esc(agent.qualifiedName || agent.name || agent.id || '') + '</div>');
18706
+ lines.push('<div style="font-size:13px;color:var(--text);font-weight:600;margin-bottom:4px;">' + esc(agent.name || agent.displayName || t('subagent.historyTitle')) + '</div>');
18707
+ lines.push('<div style="font-size:10px;color:var(--text-dim);word-break:break-all;margin-bottom:8px;">' + esc(agent.qualifiedName || agent.id || '') + '</div>');
18722
18708
  lines.push('<div id="subagent-history-meta" style="font-size:11px;color:var(--text-dim);margin-bottom:10px;">' + esc((agent.status || t('common.idle')) + ' / ' + (agent.agentMode || agent.mode || 'build') + ' / ' + (agent.model || t('common.default')) + (agent.mailbox && agent.mailbox.unread ? (' / ' + agent.mailbox.unread + ' unread') : '')) + '</div>');
18723
18709
  var compression = agent.metadata && agent.metadata.contextCompression;
18724
18710
  if (compression) {
@@ -18748,6 +18734,7 @@ window.openSubagentHistory = function(id) {
18748
18734
  for (var i = 0; i < cached.length; i++) renderSubagentPeerEvent(cached[i]);
18749
18735
  autoScrollSubagentHistory(body);
18750
18736
  window.scheduleSubagentStateRefresh();
18737
+ return true;
18751
18738
  };
18752
18739
 
18753
18740
  // === Browser ===