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
@@ -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;
@@ -1115,6 +1092,74 @@ button.left-ws-item {
1115
1092
  border-color: var(--accent);
1116
1093
  }
1117
1094
 
1095
+ .conv-more-btn {
1096
+ width: 20px;
1097
+ height: 20px;
1098
+ margin-left: auto;
1099
+ border: 0;
1100
+ border-radius: var(--radius-sm);
1101
+ background: transparent;
1102
+ color: var(--text-dim);
1103
+ cursor: pointer;
1104
+ display: flex;
1105
+ align-items: center;
1106
+ justify-content: center;
1107
+ transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
1108
+ flex-shrink: 0;
1109
+ }
1110
+ .conv-more-btn:hover,
1111
+ .conv-more-btn[aria-expanded="true"] {
1112
+ background: var(--control-hover-bg);
1113
+ color: var(--text-bright);
1114
+ }
1115
+
1116
+ .conv-action-menu {
1117
+ position: fixed;
1118
+ inset: auto;
1119
+ margin: 0;
1120
+ z-index: 560;
1121
+ display: none;
1122
+ min-width: 168px;
1123
+ max-height: min(56vh, 320px);
1124
+ overflow: auto;
1125
+ padding: 6px;
1126
+ border: 1px solid var(--glass-border-2);
1127
+ border-radius: var(--radius-lg);
1128
+ background: var(--modal-surface);
1129
+ box-shadow: var(--shadow-lg);
1130
+ backdrop-filter: blur(var(--glass-blur-3)) saturate(1.08);
1131
+ }
1132
+ .conv-action-menu:popover-open { display: block; animation: model-menu-in 150ms var(--ease-out-expo); }
1133
+
1134
+ .conv-action-menu-item {
1135
+ width: 100%;
1136
+ min-height: 32px;
1137
+ display: flex;
1138
+ align-items: center;
1139
+ gap: 8px;
1140
+ padding: 6px 10px;
1141
+ border: 0;
1142
+ border-radius: var(--radius-md);
1143
+ background: transparent;
1144
+ color: var(--text);
1145
+ text-align: left;
1146
+ white-space: nowrap;
1147
+ font: 12px var(--font-ui);
1148
+ cursor: pointer;
1149
+ }
1150
+ .conv-action-menu-item:hover,
1151
+ .conv-action-menu-item:focus-visible {
1152
+ background: var(--control-hover-bg);
1153
+ color: var(--text-bright);
1154
+ outline: none;
1155
+ }
1156
+ .conv-action-menu-item:disabled { opacity: 0.55; cursor: default; }
1157
+ .conv-action-menu-item .nm-icon { color: var(--text-dim); flex-shrink: 0; }
1158
+ .conv-action-menu-item:hover .nm-icon,
1159
+ .conv-action-menu-item:focus-visible .nm-icon { color: var(--text-bright); }
1160
+ .conv-action-menu-item.active { color: var(--text-accent); }
1161
+ .conv-action-menu-item.active .nm-icon { color: var(--text-accent); }
1162
+
1118
1163
  /* ============================================================
1119
1164
  RESIZE HANDLES
1120
1165
  ============================================================ */
@@ -2715,15 +2760,18 @@ button.todo-item { background: transparent; }
2715
2760
  }
2716
2761
 
2717
2762
  .option-btn {
2718
- padding: 5px 14px;
2763
+ display: block;
2764
+ width: 100%;
2765
+ padding: 6px 14px;
2719
2766
  border: 1px solid var(--border);
2720
2767
  border-radius: var(--radius-md);
2721
2768
  background: var(--glass-bg-1);
2722
2769
  color: var(--text);
2723
2770
  font-size: 12px;
2724
2771
  font-family: var(--font-ui);
2772
+ text-align: left;
2725
2773
  cursor: pointer;
2726
- margin: 2px 4px 2px 0;
2774
+ margin: 3px 0;
2727
2775
  transition: background var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo);
2728
2776
  }
2729
2777
 
@@ -2739,6 +2787,45 @@ button.todo-item { background: transparent; }
2739
2787
  font-size: 11px;
2740
2788
  }
2741
2789
 
2790
+ .option-manual {
2791
+ display: flex;
2792
+ align-items: center;
2793
+ gap: 6px;
2794
+ margin-top: 8px;
2795
+ }
2796
+
2797
+ .option-manual-input {
2798
+ flex: 1;
2799
+ min-width: 0;
2800
+ height: 30px;
2801
+ padding: 3px 9px;
2802
+ border: 1px solid var(--border);
2803
+ border-radius: var(--radius-md);
2804
+ background: var(--glass-bg-1);
2805
+ color: var(--text);
2806
+ font: 12px var(--font-ui);
2807
+ outline: none;
2808
+ }
2809
+
2810
+ .option-manual-input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-glow); }
2811
+ .option-manual-input:disabled { opacity: .55; cursor: default; }
2812
+
2813
+ .option-manual-submit {
2814
+ flex-shrink: 0;
2815
+ height: 30px;
2816
+ padding: 0 12px;
2817
+ border: 1px solid var(--accent);
2818
+ border-radius: var(--radius-md);
2819
+ background: rgba(91,120,255,0.14);
2820
+ color: var(--text-bright);
2821
+ font: 12px var(--font-ui);
2822
+ cursor: pointer;
2823
+ transition: background var(--duration-fast) var(--ease-out-expo);
2824
+ }
2825
+
2826
+ .option-manual-submit:hover { background: rgba(91,120,255,0.24); }
2827
+ .option-manual-submit:disabled { opacity: .55; cursor: default; }
2828
+
2742
2829
  /* ============================================================
2743
2830
  INPUT STACK
2744
2831
  ============================================================ */
@@ -3722,11 +3809,15 @@ button.todo-item { background: transparent; }
3722
3809
  linear-gradient(#fff 0 0) content-box,
3723
3810
  linear-gradient(#fff 0 0);
3724
3811
  mask-composite: exclude;
3725
- 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;
3726
3818
  z-index: -1;
3727
3819
  pointer-events: none;
3728
3820
  transform: translateZ(0);
3729
- will-change: transform;
3730
3821
  }
3731
3822
 
3732
3823
  @supports not (background: conic-gradient(from 0deg, red, blue)) {
@@ -3734,11 +3825,11 @@ button.todo-item { background: transparent; }
3734
3825
  background: linear-gradient(var(--marquee-angle, 0deg), var(--g1), var(--g2), var(--g3), var(--g4), var(--g1));
3735
3826
  }
3736
3827
  @keyframes marquee-rotate-fallback { 0% { filter: hue-rotate(0deg); } 100% { filter: hue-rotate(360deg); } }
3737
- .marquee-border::before { animation: marquee-rotate-fallback var(--marquee-speed) linear infinite; }
3828
+ .marquee-border::before { animation: none; }
3738
3829
  }
3739
3830
 
3740
3831
  .working-glow {
3741
- animation: working-border-glow 2s ease-in-out infinite;
3832
+ animation: none;
3742
3833
  box-shadow: 0 0 16px var(--accent-glow), 0 0 40px rgba(91,120,255,0.1);
3743
3834
  border-color: rgba(91,120,255,0.4) !important;
3744
3835
  }
@@ -4098,6 +4189,21 @@ textarea.mcp-input { min-height:76px; resize:vertical; font-family:var(--font-mo
4098
4189
  transition: none !important;
4099
4190
  }
4100
4191
 
4192
+ .setting-subsection {
4193
+ margin-top: 14px;
4194
+ padding: 12px 14px;
4195
+ border: 1px solid var(--glass-border-1);
4196
+ border-radius: var(--radius-md);
4197
+ background: var(--glass-bg-1);
4198
+ }
4199
+
4200
+ .setting-subsection-title {
4201
+ margin-bottom: 6px;
4202
+ color: var(--text-bright);
4203
+ font-size: 12px;
4204
+ font-weight: 650;
4205
+ }
4206
+
4101
4207
  .setting-row {
4102
4208
  display: flex;
4103
4209
  align-items: center;
@@ -5340,6 +5446,11 @@ html.newmark-memory-overview-viewer body {
5340
5446
  <path d="m15 10 5 5-5 5" />
5341
5447
  <path d="M4 4v7a4 4 0 0 0 4 4h12" />
5342
5448
  </symbol>
5449
+ <symbol id="ellipsis" viewBox="0 0 24 24">
5450
+ <circle cx="12" cy="12" r="1" />
5451
+ <circle cx="19" cy="12" r="1" />
5452
+ <circle cx="5" cy="12" r="1" />
5453
+ </symbol>
5343
5454
  <symbol id="file-diff" viewBox="0 0 24 24">
5344
5455
  <path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
5345
5456
  <path d="M9 10h6" />
@@ -5448,6 +5559,11 @@ html.newmark-memory-overview-viewer body {
5448
5559
  <path d="M3 16v3a2 2 0 0 0 2 2h3" />
5449
5560
  <path d="M16 21h3a2 2 0 0 0 2-2v-3" />
5450
5561
  </symbol>
5562
+ <symbol id="menu" viewBox="0 0 24 24">
5563
+ <path d="M4 5h16" />
5564
+ <path d="M4 12h16" />
5565
+ <path d="M4 19h16" />
5566
+ </symbol>
5451
5567
  <symbol id="merge" viewBox="0 0 24 24">
5452
5568
  <path d="m8 6 4-4 4 4" />
5453
5569
  <path d="M12 2v10.3a4 4 0 0 1-1.172 2.872L4 22" />
@@ -5698,7 +5814,7 @@ html.newmark-memory-overview-viewer body {
5698
5814
  <div id="topbar">
5699
5815
  <span class="title" id="title-text">
5700
5816
  <span class="logo" id="title-app-logo">
5701
- <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">
5702
5818
  </span>
5703
5819
  Newmark Agent
5704
5820
  </span>
@@ -5841,6 +5957,7 @@ html.newmark-memory-overview-viewer body {
5841
5957
  <button type="button" class="tool-select model-select-button" id="model-select-button" aria-haspopup="listbox" aria-expanded="false" onclick="window.toggleModelSelectMenu()" onkeydown="window.handleModelSelectButtonKey(event)"><span class="model-select-button-label" id="model-select-label"></span><svg class="nm-icon tiny" aria-hidden="true" focusable="false"><use href="#chevron-down"></use></svg></button>
5842
5958
  <div class="model-select-menu" id="model-select-menu" role="listbox" aria-label="Model" popover="manual"></div>
5843
5959
  </div>
5960
+ <div class="conv-action-menu" id="conversation-action-menu" role="menu" aria-label="Conversation actions" popover="manual"></div>
5844
5961
  <div class="context-token-wrap">
5845
5962
  <button type="button" id="context-token-ring" title="Context tokens" aria-haspopup="dialog" aria-expanded="false" onclick="window.toggleContextInspector()" onmouseenter="window.showContextWindowTooltip()" onmouseleave="window.hideContextWindowTooltip()" onfocus="window.showContextWindowTooltip()" onblur="window.hideContextWindowTooltip()"><span class="icon-only-label">Context tokens</span></button>
5846
5963
  <div id="context-token-tooltip"></div>
@@ -6184,6 +6301,7 @@ var state = {
6184
6301
  agentBackend: { enabled: false, connected: true, distro: '', pid: 0, error: '' },
6185
6302
  configuredAgentBackend: 'windows',
6186
6303
  agentBackendRestartRequired: false,
6304
+ remoteTouchEnabled: true,
6187
6305
  wslAvailable: false,
6188
6306
  wslDistros: [],
6189
6307
  defaultTerminalShell: 'powershell',
@@ -6364,6 +6482,11 @@ var NEWMARK_I18N = {
6364
6482
  'settings.configReloaded': 'config.json refreshed',
6365
6483
  'settings.runInWsl': 'Agent runtime environment',
6366
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.',
6485
+ 'settings.remoteTouch': 'Mobile remote-touch',
6486
+ 'settings.remoteTouchDesc': 'Allow mobile access on the same LAN / Tailscale network',
6487
+ 'settings.remoteBehavior': 'Remote behavior',
6488
+ 'settings.remoteConnect': 'Start connection',
6489
+ 'settings.remoteConnectDesc': 'Show pairing QR',
6367
6490
  'settings.wslDistro': 'WSL distribution',
6368
6491
  'settings.wslConnected': 'WSL backend connected',
6369
6492
  'settings.wslDisconnected': 'WSL backend unavailable',
@@ -6632,6 +6755,8 @@ var NEWMARK_I18N = {
6632
6755
  'plugins.management': 'Skills Management',
6633
6756
  'plugins.market': 'Skills Market',
6634
6757
  'plugins.github': 'GitHub CLI',
6758
+ 'plugins.refreshGh': 'Refresh',
6759
+ 'plugins.connectGh': 'Connect GitHub',
6635
6760
  'plugins.noInstalled': 'No installed skills.',
6636
6761
  'plugins.projectSkill': 'project skill',
6637
6762
  'plugins.discovering': 'Discovering local and portable skills...',
@@ -6724,6 +6849,14 @@ var NEWMARK_I18N = {
6724
6849
  'plugins.retry': 'Retry',
6725
6850
  'plugins.dshTitle': 'DSH Plugin compatibility',
6726
6851
  'plugins.dshHelp': 'Developer preview: inspect the locally available official DSH CLI and package configuration. Discovery is read-only and never installs, executes, updates, or rewrites DSH.',
6852
+ 'plugins.dshInstall': 'Install into Newmark',
6853
+ 'plugins.dshInstalled': 'Installed',
6854
+ 'plugins.dshUninstall': 'Uninstall',
6855
+ 'plugins.dshEnable': 'Enable',
6856
+ 'plugins.dshDisable': 'Disable',
6857
+ 'plugins.dshInstallOk': 'DSH plugin installed into local .Newmark.',
6858
+ 'plugins.dshUninstallOk': 'DSH plugin removed.',
6859
+ 'plugins.dshSetOk': 'DSH plugin setting saved.',
6727
6860
  'plugins.dshPreview': 'Developer preview',
6728
6861
  'plugins.dshRescan': 'Rescan',
6729
6862
  'plugins.dshScanning': 'Scanning DSH compatibility...',
@@ -6875,6 +7008,11 @@ var NEWMARK_I18N = {
6875
7008
  'status.pendingOptions': 'Pending options',
6876
7009
  'status.noPendingOptions': 'No option question is waiting for user input.',
6877
7010
  'status.question': 'Question',
7011
+ 'option.manualPlaceholder': 'Or type your own answer...',
7012
+ 'option.manualSubmit': 'Submit',
7013
+ 'mobile.pair': 'Pair mobile device',
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.',
7015
+ 'mobile.pairTokenFile': 'Token file',
6878
7016
  'subagent.empty': 'No retained subagent records',
6879
7017
  'subagent.openHistory': 'Open subagent history',
6880
7018
  'subagent.historyTitle': 'Subagent history',
@@ -6885,6 +7023,7 @@ var NEWMARK_I18N = {
6885
7023
  'subagent.noMessages': 'No messages recorded.',
6886
7024
  'subagent.unread': '{count} unread',
6887
7025
  'conversation.archive': 'Archive',
7026
+ 'conversation.more': 'More conversation actions',
6888
7027
  'conversation.rename': 'Rename conversation',
6889
7028
  'conversation.renameFailed': 'Could not rename the conversation.',
6890
7029
  'conversation.reorderFailed': 'Could not save the conversation order.',
@@ -7070,6 +7209,11 @@ var NEWMARK_I18N = {
7070
7209
  'settings.configReloaded': '已刷新 config.json',
7071
7210
  'settings.runInWsl': 'Agent 运行环境',
7072
7211
  'settings.runInWslDesc': 'Windows UI 与桌面原生工具保留在本机,持续 Agent 内核、模型请求、Shell 和文件工具运行在 WSL。',
7212
+ 'settings.remoteTouch': '移动端远程触及',
7213
+ 'settings.remoteTouchDesc': '允许移动端通过同一内网 / Tailscale 连接',
7214
+ 'settings.remoteBehavior': '远程行为',
7215
+ 'settings.remoteConnect': '发起连接',
7216
+ 'settings.remoteConnectDesc': '显示配对二维码',
7073
7217
  'settings.wslDistro': 'WSL 发行版',
7074
7218
  'settings.wslConnected': 'WSL 后端已连接',
7075
7219
  'settings.wslDisconnected': 'WSL 后端不可用',
@@ -7338,6 +7482,8 @@ var NEWMARK_I18N = {
7338
7482
  'plugins.management': 'Skills 管理',
7339
7483
  'plugins.market': 'Skills Market',
7340
7484
  'plugins.github': 'GitHub CLI',
7485
+ 'plugins.refreshGh': '刷新',
7486
+ 'plugins.connectGh': '连接 GitHub',
7341
7487
  'plugins.noInstalled': '暂无已安装 skill。',
7342
7488
  'plugins.projectSkill': '项目 skill',
7343
7489
  'plugins.discovering': '正在发现本地和便携 skill...',
@@ -7430,6 +7576,14 @@ var NEWMARK_I18N = {
7430
7576
  'plugins.retry': '重试',
7431
7577
  'plugins.dshTitle': 'DSH Plugin 兼容',
7432
7578
  'plugins.dshHelp': '开发者预览:检查本地官方 DSH CLI 与包配置。发现过程严格只读,绝不会安装、执行、更新或改写 DSH。',
7579
+ 'plugins.dshInstall': '安装到 Newmark',
7580
+ 'plugins.dshInstalled': '已安装',
7581
+ 'plugins.dshUninstall': '卸载',
7582
+ 'plugins.dshEnable': '启用',
7583
+ 'plugins.dshDisable': '禁用',
7584
+ 'plugins.dshInstallOk': 'DSH 插件已安装到本地 .Newmark。',
7585
+ 'plugins.dshUninstallOk': 'DSH 插件已移除。',
7586
+ 'plugins.dshSetOk': 'DSH 插件设置已保存。',
7433
7587
  'plugins.dshPreview': '开发者预览',
7434
7588
  'plugins.dshRescan': '重新扫描',
7435
7589
  'plugins.dshScanning': '正在扫描 DSH 兼容信息...',
@@ -7581,6 +7735,11 @@ var NEWMARK_I18N = {
7581
7735
  'status.pendingOptions': '待处理选项',
7582
7736
  'status.noPendingOptions': '暂无等待用户输入的选项问题。',
7583
7737
  'status.question': '问题',
7738
+ 'option.manualPlaceholder': '或输入自己的答案...',
7739
+ 'option.manualSubmit': '提交',
7740
+ 'mobile.pair': '配对移动设备',
7741
+ 'mobile.pairDesc': '使用 Newmark 移动端在同一内网 / Tailscale 网络下扫描二维码。首次绑定后保存配对令牌。',
7742
+ 'mobile.pairTokenFile': '令牌文件',
7584
7743
  'subagent.empty': '暂无保留的 subagent 记录',
7585
7744
  'subagent.openHistory': '打开 subagent 历史',
7586
7745
  'subagent.historyTitle': 'Subagent 历史',
@@ -7591,6 +7750,7 @@ var NEWMARK_I18N = {
7591
7750
  'subagent.noMessages': '没有记录消息。',
7592
7751
  'subagent.unread': '{count} 条未读',
7593
7752
  'conversation.archive': '归档',
7753
+ 'conversation.more': '更多对话操作',
7594
7754
  'conversation.rename': '编辑对话名称',
7595
7755
  'conversation.renameFailed': '无法保存对话名称。',
7596
7756
  'conversation.reorderFailed': '无法保存对话顺序。',
@@ -9993,7 +10153,7 @@ function workEventIcon(type) {
9993
10153
 
9994
10154
  function workToolActivity(event) {
9995
10155
  var name = publicToolNameForUi(event && event.toolName).toLowerCase();
9996
- 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' };
9997
10157
  if (name === 'skill' || name === 'skill_load' || name === 'skill_read' || name === 'skill_download') return { key: 'skills', type: 'tool_skill' };
9998
10158
  if (name === 'mcp' || name.indexOf('mcp__') === 0 || /^mcp[:._-]/.test(name) || /[:._-]mcp[:._-]/.test(name)) return { key: 'mcp', type: 'tool_mcp' };
9999
10159
  if (name === 'context_compression' || name === 'context_compress' || name === 'context_history_manage') return { key: 'context_compression', type: 'tool_context_compression' };
@@ -10034,8 +10194,8 @@ function workToolCommandLabel(item) {
10034
10194
  var args = workToolArgsObject(item);
10035
10195
  var toolName = publicToolNameForUi(item && item.toolName);
10036
10196
  var normalizedName = toolName.toLowerCase();
10037
- if (normalizedName === 'task') {
10038
- 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();
10039
10199
  }
10040
10200
  if (normalizedName.indexOf('subagent_') === 0) {
10041
10201
  return (currentLang() === 'zh' ? 'Subagent 操作 · ' : 'Subagent action · ') + String(args.name || args.id || normalizedName.replace(/^subagent_/, '')).trim();
@@ -10074,14 +10234,19 @@ function workToolCommandLabel(item) {
10074
10234
 
10075
10235
  function workToolRowIcon(item) {
10076
10236
  var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10077
- if (name === 'task' || name.indexOf('subagent_') === 0) return 'bot';
10237
+ if (isSubagentCreateToolName(name) || name.indexOf('subagent_') === 0) return 'bot';
10078
10238
  if (name === 'skill' || name === 'skill_download') return 'sparkles';
10079
10239
  if (name === 'mcp' || /^mcp[:._-]/.test(name) || /[:._-]mcp[:._-]/.test(name)) return 'plug';
10080
10240
  return 'square-terminal';
10081
10241
  }
10082
10242
 
10083
- function findSubagentUiRecord(name) {
10243
+ function isSubagentCreateToolName(name) {
10084
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();
10085
10250
  return (state.subagents || []).find(function(item) {
10086
10251
  return [item.id, item.name, item.displayName, item.qualifiedName].some(function(value) {
10087
10252
  var candidate = String(value || '').trim().toLowerCase();
@@ -10125,7 +10290,7 @@ function workToolGroupLabel(items, completed) {
10125
10290
  var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10126
10291
  if (name === 'skill' || name === 'skill_load' || name === 'skill_read' || name === 'skill_download') skills.push(workToolCommandLabel(item));
10127
10292
  else if (name === 'mcp' || name.indexOf('mcp__') === 0 || /^mcp[:._-]/.test(name) || /[:._-]mcp[:._-]/.test(name)) mcps.push(workToolCommandLabel(item));
10128
- 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));
10129
10294
  });
10130
10295
  var commands = Math.max(0, (items || []).length - files - skills.length - mcps.length - subagents.length);
10131
10296
  var parts = [];
@@ -10158,24 +10323,8 @@ function renderWorkToolGroup(event, eventIndex) {
10158
10323
  var hasFiles = items.some(function(item) { return !!workToolEditedFile(item); });
10159
10324
  var onlySubagents = items.length > 0 && items.every(function(item) {
10160
10325
  var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10161
- return name === 'task';
10326
+ return isSubagentCreateToolName(name);
10162
10327
  });
10163
- // A Build Block that spawns several SubAgents is summarized as one chip
10164
- // ("operated N SubAgents") instead of flooding the block with one chip per
10165
- // SubAgent. Each name stays available in the tooltip.
10166
- var subagentTasks = items.map(function(item, itemIndex) {
10167
- var name = publicToolNameForUi(item && item.toolName).toLowerCase();
10168
- if (name !== 'task') return null;
10169
- var subagentArgs = workToolArgsObject(item);
10170
- var subagentName = String(subagentArgs.nature || subagentArgs.name || subagentArgs.agent || 'Subagent').trim();
10171
- var subagentRecord = findSubagentUiRecord(subagentName);
10172
- return {
10173
- key: String(item.id || item.toolCallId || ('task-' + itemIndex)),
10174
- subagentName: subagentName || 'Subagent',
10175
- status: String(subagentRecord && subagentRecord.status || 'accepted'),
10176
- };
10177
- }).filter(Boolean);
10178
- var collapseSubagents = subagentTasks.length > 1;
10179
10328
  var activityRows = items.map(function(item, itemIndex) {
10180
10329
  var edited = workToolEditedFile(item);
10181
10330
  if (edited) {
@@ -10188,26 +10337,22 @@ function renderWorkToolGroup(event, eventIndex) {
10188
10337
  '<div class="conversation-work-file-diff">' + renderWorkFileDiff(edited) + '</div></details>';
10189
10338
  }
10190
10339
  var toolName = publicToolNameForUi(item && item.toolName).toLowerCase();
10191
- if (toolName === 'task') {
10192
- if (collapseSubagents) return '';
10340
+ if (isSubagentCreateToolName(toolName)) {
10193
10341
  var subagentArgs = workToolArgsObject(item);
10194
- var subagentName = String(subagentArgs.nature || subagentArgs.name || subagentArgs.agent || 'Subagent').trim();
10195
- var subagentRecord = findSubagentUiRecord(subagentName);
10196
- var subagentStatus = String(subagentRecord && subagentRecord.status || 'accepted');
10197
- 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') + '">' +
10198
- 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>';
10199
10352
  }
10200
10353
  return '<div class="conversation-work-activity-item">' + iconSvg(workToolRowIcon(item), 'tool', 'tiny') +
10201
10354
  '<div><div class="conversation-work-command-label" title="' + escAttr(workToolCommandLabel(item)) + '">' + esc(workToolCommandLabel(item)) + '</div>' + renderWorkDisplayImage(item.displayImage) + '</div></div>';
10202
10355
  }).join('');
10203
- if (collapseSubagents) {
10204
- var allCompleted = subagentTasks.every(function(task) { return task.status === 'completed'; });
10205
- var names = subagentTasks.map(function(task) { return task.subagentName; });
10206
- activityRows += '<button type="button" class="conversation-work-subagent-chip conversation-work-subagent-chip-multi' + (allCompleted ? ' completed' : '') + '" title="' + escAttr(names.join(',')) + '">' +
10207
- iconSvg('bot', 'Subagent', 'tiny') + '<span class="conversation-work-subagent-name">' +
10208
- esc(currentLang() === 'zh' ? '操作了 ' + subagentTasks.length + ' 个 SubAgent' : 'Operated ' + subagentTasks.length + ' SubAgents') +
10209
- '</span><span class="conversation-work-subagent-status">' + esc(subagentUiStatusLabel(allCompleted ? 'completed' : subagentTasks[0].status)) + '</span></button>';
10210
- }
10211
10356
  return '<details class="conversation-work-activity" data-activity-key="' + escAttr(activityDomKey) + '"><summary>' +
10212
10357
  iconSvg(hasFiles ? 'pencil' : 'square-terminal', 'tools', 'tiny') + '<span>' + esc(workToolGroupLabel(items, !!event.completed)) + '</span>' +
10213
10358
  '<span class="conversation-work-activity-chevron" aria-hidden="true"></span></summary><div class="conversation-work-activity-list' + (onlySubagents ? ' conversation-work-subagent-list' : '') + '">' +
@@ -10509,17 +10654,32 @@ function renderConversationWorkRuns(runs, target) {
10509
10654
  // every delta can starve Chromium's renderer/CDP event loop.
10510
10655
  var pendingConversationWorkRunRenders = {};
10511
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
+ }
10512
10667
  function scheduleConversationWorkRunRender(run) {
10513
10668
  var key = runtimeKeyFor(run.target && run.target.workspaceId, run.target && run.target.conversationId) + '::' + String(run.runId || '');
10514
10669
  pendingConversationWorkRunRenders[key] = run;
10515
10670
  if (scheduledConversationWorkRunRenders[key]) return;
10516
- scheduledConversationWorkRunRenders[key] = true;
10517
- requestAnimationFrame(function() {
10518
- var latest = pendingConversationWorkRunRenders[key];
10519
- delete pendingConversationWorkRunRenders[key];
10520
- delete scheduledConversationWorkRunRenders[key];
10521
- if (latest && isActiveConversationTarget(latest.target)) renderConversationWorkRun(latest);
10522
- });
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);
10523
10683
  }
10524
10684
 
10525
10685
  function applyAgentWorkEventToRun(event) {
@@ -10582,7 +10742,9 @@ function applyAgentWorkEventToRun(event) {
10582
10742
  if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
10583
10743
  state.workRunsByTarget[runtimeKeyFor(target.workspaceId, target.conversationId)] = runs;
10584
10744
  var existingElement = findWorkRunElement(run);
10745
+ var terminalRender = ['done', 'error', 'interrupted', 'force_interrupted', 'final_response'].indexOf(type) >= 0;
10585
10746
  if (!existingElement || type === 'start') renderConversationWorkRun(run);
10747
+ else if (terminalRender) renderConversationWorkRunImmediately(run);
10586
10748
  else scheduleConversationWorkRunRender(run);
10587
10749
  }
10588
10750
  return run;
@@ -11320,7 +11482,7 @@ function appendAgentWorkEvent(event) {
11320
11482
  setWorking(!!runningConversationRecord(activeConversationId()) || currentFlowRunning());
11321
11483
  updateSubmitButtonState();
11322
11484
  }
11323
- 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) {
11324
11486
  var eventWorkspaceKey = currentWorkspaceKey();
11325
11487
  api.getState(target).then(function(snapshot) {
11326
11488
  if (!isActiveConversationTarget(target) || eventWorkspaceKey !== currentWorkspaceKey()) return;
@@ -11521,6 +11683,10 @@ function addOptionBlock(question, options, questionKey) {
11521
11683
  html += '<button class="option-btn" data-option-question-key="' + escAttr(encodeURIComponent(questionKey)) + '" data-option-label="' + escAttr(label) + '" onclick="window.optionSelected(decodeURIComponent(this.dataset.optionQuestionKey),this.dataset.optionLabel,this)">' +
11522
11684
  esc(label) + (desc ? '<span class="option-desc">' + esc(desc) + '</span>' : '') + '</button>';
11523
11685
  }
11686
+ html += '<div class="option-manual">' +
11687
+ '<input class="option-manual-input" data-option-question-key="' + escAttr(encodeURIComponent(questionKey)) + '" placeholder="' + escAttr(t('option.manualPlaceholder')) + '" onkeydown="if(event.key===\'Enter\'){event.preventDefault();window.optionManualSubmitted(this);}" />' +
11688
+ '<button class="option-manual-submit" onclick="window.optionManualSubmitted(this)">' + esc(t('option.manualSubmit')) + '</button>' +
11689
+ '</div>';
11524
11690
  html += '</div>';
11525
11691
  div.innerHTML = html;
11526
11692
  els['chat-area'].appendChild(div);
@@ -11637,6 +11803,20 @@ window.optionSelected = function(questionKey, opt, button) {
11637
11803
  window.sendMessage();
11638
11804
  };
11639
11805
 
11806
+ window.optionManualSubmitted = function(element) {
11807
+ var manual = element && element.closest ? element.closest('.option-manual') : null;
11808
+ if (!manual) return;
11809
+ var input = manual.querySelector('.option-manual-input');
11810
+ if (!input) return;
11811
+ var value = String(input.value || '').trim();
11812
+ if (!value) { input.focus(); return; }
11813
+ var key = input.getAttribute('data-option-question-key') || '';
11814
+ var decoded;
11815
+ try { decoded = decodeURIComponent(key); } catch (e) { decoded = key; }
11816
+ window.optionSelected(decoded, value, input);
11817
+ Array.from(manual.querySelectorAll('input, button')).forEach(function(control) { control.disabled = true; });
11818
+ };
11819
+
11640
11820
  // === Working State ===
11641
11821
  function setWorking(working) {
11642
11822
  state.isWorking = working;
@@ -12230,6 +12410,119 @@ window.selectModelFromMenu = function(value) {
12230
12410
  window.closeModelSelectMenu({ focus: true });
12231
12411
  };
12232
12412
 
12413
+ window.closeConversationActionMenu = function(options) {
12414
+ var menu = document.getElementById('conversation-action-menu');
12415
+ var anchor = state.conversationActionAnchor;
12416
+ if (menu && typeof menu.hidePopover === 'function' && menu.matches(':popover-open')) menu.hidePopover();
12417
+ document.querySelectorAll('.conv-more-btn[aria-expanded="true"]').forEach(function(item) { item.setAttribute('aria-expanded', 'false'); });
12418
+ state.conversationActionAnchor = null;
12419
+ if (options && options.focus && anchor && anchor.focus) anchor.focus();
12420
+ };
12421
+
12422
+ window.positionConversationActionMenu = function(anchor, menu, pos) {
12423
+ if (!menu) return;
12424
+ var width = 168;
12425
+ var margin = 8;
12426
+ menu.style.width = width + 'px';
12427
+ var rect = anchor && anchor.getBoundingClientRect ? anchor.getBoundingClientRect() : null;
12428
+ var desiredHeight = Math.min(320, menu.scrollHeight || menu.offsetHeight || 0);
12429
+ var left, top, below, above, openAbove;
12430
+ if (pos && typeof pos.x === 'number' && typeof pos.y === 'number') {
12431
+ left = Math.max(margin, Math.min(window.innerWidth - width - margin, pos.x));
12432
+ below = Math.max(0, window.innerHeight - pos.y - margin);
12433
+ above = Math.max(0, pos.y - margin);
12434
+ openAbove = below < desiredHeight && above > below;
12435
+ top = openAbove ? Math.max(margin, pos.y - Math.min(desiredHeight, above) - margin) : pos.y + margin;
12436
+ } else if (rect) {
12437
+ left = Math.max(margin, Math.min(window.innerWidth - width - margin, rect.right - width));
12438
+ below = Math.max(0, window.innerHeight - rect.bottom - margin);
12439
+ above = Math.max(0, rect.top - margin);
12440
+ openAbove = below < desiredHeight && above > below;
12441
+ top = openAbove ? Math.max(margin, rect.top - Math.min(desiredHeight, above) - margin) : rect.bottom + margin;
12442
+ } else {
12443
+ left = Math.max(margin, Math.round((window.innerWidth - width) / 2));
12444
+ top = Math.round(window.innerHeight / 3);
12445
+ }
12446
+ menu.style.left = left + 'px';
12447
+ menu.style.top = top + 'px';
12448
+ };
12449
+
12450
+ window.openConversationActionMenu = function(anchor, conversationId, pos) {
12451
+ var menu = document.getElementById('conversation-action-menu');
12452
+ if (!menu) return;
12453
+ var convs = currentWorkspaceConversations();
12454
+ var idx = convs.findIndex(function(item) { return String(item && item.id || 'default') === String(conversationId || ''); });
12455
+ var conv = convs[idx];
12456
+ if (!conv) return;
12457
+ window.closeModelSelectMenu();
12458
+ document.querySelectorAll('.newmark-select-shell.open').forEach(function(item) { window.closeNewmarkSelect(item); });
12459
+ var pendingKey = currentWorkspaceKey() + '::' + String(conv.id);
12460
+ var pending = !!state.conversationArchivePending[pendingKey];
12461
+ var pinLabel = conv.pinned ? t('conversation.unpin') : t('conversation.pin');
12462
+ var archiveHtml = pending
12463
+ ? '<button type="button" class="conv-action-menu-item" disabled><span class="conv-archive-spinner"></span><span>' + esc(t('archive.archiving')) + '</span></button>'
12464
+ : '<button type="button" class="conv-action-menu-item" data-action="archive" onclick="window.runConversationAction(this, \'archive\')">' + iconSvg('archive', '', 'small') + '<span>' + esc(t('conversation.archive')) + '</span></button>';
12465
+ menu.innerHTML =
12466
+ '<button type="button" class="conv-action-menu-item" data-action="edit" onclick="window.runConversationAction(this, \'edit\')">' + iconSvg('pencil', '', 'small') + '<span>' + esc(t('conversation.rename')) + '</span></button>' +
12467
+ archiveHtml +
12468
+ '<button type="button" class="conv-action-menu-item' + (conv.pinned ? ' active' : '') + '" data-action="pin" onclick="window.runConversationAction(this, \'pin\')">' + iconSvg('pin', '', 'small') + '<span>' + esc(pinLabel) + '</span></button>';
12469
+ menu.setAttribute('data-index', String(idx));
12470
+ var moreBtn = anchor && anchor.classList && anchor.classList.contains('conv-more-btn') ? anchor : (anchor && anchor.querySelector ? anchor.querySelector('.conv-more-btn') : null);
12471
+ document.querySelectorAll('.conv-more-btn[aria-expanded="true"]').forEach(function(item) { item.setAttribute('aria-expanded', 'false'); });
12472
+ if (moreBtn) moreBtn.setAttribute('aria-expanded', 'true');
12473
+ state.conversationActionAnchor = anchor || moreBtn || null;
12474
+ if (typeof menu.showPopover === 'function' && !menu.matches(':popover-open')) menu.showPopover();
12475
+ requestAnimationFrame(function() {
12476
+ window.positionConversationActionMenu(state.conversationActionAnchor || moreBtn, menu, pos);
12477
+ var item = menu.querySelector('.conv-action-menu-item:not(:disabled)');
12478
+ if (item) item.focus({ preventScroll: true });
12479
+ });
12480
+ };
12481
+
12482
+ window.runConversationAction = function(button, action) {
12483
+ var menu = document.getElementById('conversation-action-menu');
12484
+ var idx = Number(menu && menu.getAttribute('data-index') || '-1');
12485
+ window.closeConversationActionMenu();
12486
+ var convs = currentWorkspaceConversations();
12487
+ var conv = convs[idx];
12488
+ if (!conv) return;
12489
+ if (action === 'edit') window.editConversationName(idx);
12490
+ else if (action === 'archive') window.archiveConv(String(conv.id || 'default'));
12491
+ else if (action === 'pin') window.toggleConversationPinned(idx);
12492
+ };
12493
+
12494
+ document.addEventListener('pointerdown', function(event) {
12495
+ var menu = document.getElementById('conversation-action-menu');
12496
+ if (!menu || !menu.matches || !menu.matches(':popover-open')) return;
12497
+ var anchor = state.conversationActionAnchor;
12498
+ if (anchor && anchor.contains && anchor.contains(event.target)) return;
12499
+ if (menu.contains(event.target)) return;
12500
+ window.closeConversationActionMenu();
12501
+ });
12502
+
12503
+ document.addEventListener('keydown', function(event) {
12504
+ if (event.key === 'Escape') window.closeConversationActionMenu();
12505
+ });
12506
+
12507
+ (function() {
12508
+ var menu = document.getElementById('conversation-action-menu');
12509
+ if (!menu) return;
12510
+ menu.addEventListener('keydown', function(event) {
12511
+ if (event.key === 'Escape') {
12512
+ event.preventDefault();
12513
+ window.closeConversationActionMenu({ focus: true });
12514
+ return;
12515
+ }
12516
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
12517
+ event.preventDefault();
12518
+ var items = Array.prototype.slice.call(menu.querySelectorAll('.conv-action-menu-item:not(:disabled)'));
12519
+ if (!items.length) return;
12520
+ var current = items.indexOf(document.activeElement);
12521
+ var next = event.key === 'ArrowDown' ? items[(current + 1) % items.length] : items[(current - 1 + items.length) % items.length];
12522
+ if (next) next.focus();
12523
+ });
12524
+ })();
12525
+
12233
12526
  window.handleModelSelectButtonKey = function(event) {
12234
12527
  if (!event) return;
12235
12528
  if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
@@ -15244,7 +15537,20 @@ function renderGeneralSettings() {
15244
15537
  '<div class="setting-row"><span class="setting-label">' + esc(t('settings.wslDistro')) + '</span>' +
15245
15538
  '<div class="setting-control"><select id="agent-wsl-distro" onchange="window.setAgentWslDistro(this.value)"' + (state.wslAvailable ? '' : ' disabled') + '>' + (wslOptions || '<option>Ubuntu-24.04</option>') + '</select>' +
15246
15539
  '<div class="setting-desc" id="agent-wsl-status">' + esc(state.agentBackend && state.agentBackend.connected ? t('settings.wslConnected') : t('settings.wslDisconnected')) + '</div></div></div>' : '';
15247
- return wslHtml + '<div class="setting-row">' +
15540
+ return wslHtml + '<div class="setting-subsection">' +
15541
+ '<div class="setting-subsection-title">' + esc(t('settings.remoteBehavior')) + '</div>' +
15542
+ '<div class="setting-row">' +
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>' +
15547
+ '</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>' +
15551
+ '</div>' +
15552
+ '</div>' +
15553
+ '<div class="setting-row">' +
15248
15554
  '<span class="setting-label">' + esc(t('settings.configFile')) + '</span>' +
15249
15555
  '<div class="setting-control"><div style="display:flex;align-items:center;gap:7px;flex-wrap:wrap;">' +
15250
15556
  '<button type="button" class="sec-btn" onclick="window.openGlobalConfigFile()">' + esc(t('settings.openConfig')) + '</button>' +
@@ -15898,8 +16204,7 @@ var systemColorScheme = window.matchMedia('(prefers-color-scheme: dark)');
15898
16204
  window.refreshTitlebarThemeIcon = function() {
15899
16205
  var icon = document.getElementById('title-app-icon');
15900
16206
  if (!icon) return;
15901
- var useDarkTheme = state.theme === 'system' ? systemColorScheme.matches : state.theme !== 'light';
15902
- icon.src = useDarkTheme ? '../assets/app-icon-dark-64.png' : '../assets/app-icon-light-64.png';
16207
+ icon.src = '../assets/app-icon-dark.svg';
15903
16208
  };
15904
16209
  window.refreshTitlebarSystemIcon = window.refreshTitlebarThemeIcon;
15905
16210
  if (systemColorScheme.addEventListener) systemColorScheme.addEventListener('change', function() {
@@ -15920,6 +16225,15 @@ window.setAgentBackendMode = async function(mode) {
15920
16225
  return true;
15921
16226
  };
15922
16227
 
16228
+ window.setRemoteTouchEnabled = async function(value) {
16229
+ var next = !!value;
16230
+ state.remoteTouchEnabled = next;
16231
+ if (api.saveSetting) {
16232
+ try { await api.saveSetting('remote', 'touch_enabled', next); } catch (e) {}
16233
+ }
16234
+ window.settingsTab('general');
16235
+ };
16236
+
15923
16237
  window.setAgentWslDistro = async function(value) {
15924
16238
  var distro = String(value || '').trim() || 'Ubuntu-24.04';
15925
16239
  await api.saveSetting('agent', 'wsl_distro', distro);
@@ -18171,9 +18485,9 @@ window.renderSubagentList = function() {
18171
18485
  var unread = Number(a.mailbox && a.mailbox.unread || 0);
18172
18486
  var queue = a.status === 'queued' && a.queueSequence ? (' · #' + a.queueSequence) : '';
18173
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))) : ''));
18174
- var display = a.displayName || a.name || a.id || 'Agent';
18175
- var qualified = a.qualifiedName || a.name || a.id || display;
18176
- 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;">' +
18177
18491
  iconSvg('bot', 'Subagent', 'small') +
18178
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>' +
18179
18493
  '<span style="color:var(--accent2);font-size:10px;">' + esc(a.status || t('common.idle')) + '</span>' +
@@ -18185,10 +18499,10 @@ window.renderSubagentList = function() {
18185
18499
  };
18186
18500
 
18187
18501
  window.refreshSubagentBuildChips = function() {
18188
- var chips = document.querySelectorAll('.conversation-work-subagent-chip[data-subagent-name]');
18502
+ var chips = document.querySelectorAll('.conversation-work-subagent-chip[data-subagent-id]');
18189
18503
  for (var i = 0; i < chips.length; i++) {
18190
18504
  var chip = chips[i];
18191
- var record = findSubagentUiRecord(chip.getAttribute('data-subagent-name'));
18505
+ var record = findSubagentUiRecord(chip.getAttribute('data-subagent-id'));
18192
18506
  var status = String(record && record.status || 'accepted');
18193
18507
  chip.classList.toggle('completed', status === 'completed');
18194
18508
  var statusEl = chip.querySelector('.conversation-work-subagent-status');
@@ -18214,15 +18528,15 @@ window.scheduleSubagentStateRefresh = function() {
18214
18528
  }, 1200);
18215
18529
  };
18216
18530
 
18217
- window.openSubagentFromBuild = function(name) {
18218
- var requested = String(name || '').trim();
18531
+ window.openSubagentFromBuild = function(id) {
18532
+ var requested = String(id || '').trim();
18219
18533
  var openLatest = function() {
18220
18534
  var agent = findSubagentUiRecord(requested);
18221
18535
  if (!agent) {
18222
18536
  showUiNotice((currentLang() === 'zh' ? '尚未找到 Subagent:' : 'Subagent is not available yet: ') + requested, 'error', 'subagent-build-' + requested);
18223
18537
  return false;
18224
18538
  }
18225
- window.openSubagentHistory(agent.id || agent.name);
18539
+ window.openSubagentHistory(agent.id);
18226
18540
  return true;
18227
18541
  };
18228
18542
  if (!api.getState) return Promise.resolve(openLatest());
@@ -18380,13 +18694,17 @@ window.closeSubagentHistory = function() {
18380
18694
 
18381
18695
  window.openSubagentHistory = function(id) {
18382
18696
  var agents = state.subagents || [];
18383
- var agent = agents.find(function(a) { return a.id === id || a.name === id; }) || {};
18384
- 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;
18385
18703
  var existing = document.getElementById('subagent-history-overlay');
18386
18704
  if (existing) existing.remove();
18387
18705
  var lines = [];
18388
- 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>');
18389
- 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>');
18390
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>');
18391
18709
  var compression = agent.metadata && agent.metadata.contextCompression;
18392
18710
  if (compression) {
@@ -18416,6 +18734,7 @@ window.openSubagentHistory = function(id) {
18416
18734
  for (var i = 0; i < cached.length; i++) renderSubagentPeerEvent(cached[i]);
18417
18735
  autoScrollSubagentHistory(body);
18418
18736
  window.scheduleSubagentStateRefresh();
18737
+ return true;
18419
18738
  };
18420
18739
 
18421
18740
  // === Browser ===
@@ -19058,16 +19377,15 @@ function renderConversations() {
19058
19377
  ? '<span class="conv-runtime-badge ' + escAttr(String(runtimeState.status)) + '">' + esc(String(runtimeState.status)) + '</span>' : '';
19059
19378
  var branchCommIcon = conv.branchCommunication
19060
19379
  ? '<span class="conv-branch-comm-icon" title="' + escAttr(t('conversation.branchCommunicationBadge')) + '" aria-label="' + escAttr(t('conversation.branchCommunicationBadge')) + '">' + iconSvg('git-branch', t('conversation.branchCommunicationBadge')) + '</span>' : '';
19061
- var archivePendingKey = currentWorkspaceKey() + '::' + String(conv.id);
19062
- var archiveBtn = state.conversationArchivePending[archivePendingKey]
19063
- ? '<button class="conv-archive-btn archiving" title="' + escAttr(t('archive.archiving')) + '" disabled><span class="conv-archive-spinner"></span></button>'
19064
- : '<button class="conv-archive-btn" onclick="event.stopPropagation();window.archiveConv(this.closest(&quot;.conv-item&quot;).getAttribute(&quot;data-conversation-id&quot;))" title="' + escAttr(t('conversation.archive')) + '">' + iconOnly('archive', t('conversation.archive')) + '</button>';
19065
19380
  div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + branchCommIcon + runtimeBadge +
19066
- '<button class="conv-rename-btn" onclick="event.stopPropagation();window.editConversationName(' + i + ')" title="' + escAttr(t('conversation.rename')) + '">' + iconOnly('pencil', t('conversation.rename')) + '</button>' +
19067
- archiveBtn +
19068
- '<button class="conv-pin-btn' + (conv.pinned ? ' active' : '') + '" onclick="event.stopPropagation();window.toggleConversationPinned(' + i + ')" title="' + escAttr(conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '">' + iconOnly('pin', conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '</button>';
19381
+ '<button class="conv-more-btn" aria-haspopup="menu" aria-expanded="false" onclick="event.stopPropagation();window.openConversationActionMenu(this, this.closest(&quot;.conv-item&quot;).getAttribute(&quot;data-conversation-id&quot;))" title="' + escAttr(t('conversation.more')) + '">' + iconOnly('ellipsis', t('conversation.more')) + '</button>';
19069
19382
  div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
19070
19383
  div.addEventListener('keydown', window.handleConversationKey);
19384
+ div.addEventListener('contextmenu', function(event) {
19385
+ event.preventDefault();
19386
+ event.stopPropagation();
19387
+ window.openConversationActionMenu(event.currentTarget, this.getAttribute('data-conversation-id') || '', { x: event.clientX, y: event.clientY });
19388
+ });
19071
19389
  div.addEventListener('dragstart', function(event) {
19072
19390
  this.classList.add('dragging');
19073
19391
  if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', this.getAttribute('data-conversation-id') || ''); }
@@ -20481,6 +20799,59 @@ window.openDshOfficial = function(kind) {
20481
20799
  });
20482
20800
  };
20483
20801
 
20802
+ function renderDshBundlesMarkup(bundles) {
20803
+ var values = dshCollection(bundles);
20804
+ if (!values.length) return '<div class="plugin-empty">' + esc(t('plugins.dshNone')) + '</div>';
20805
+ return '<div class="dsh-list">' + values.map(function(item, index) {
20806
+ var name = String(item.name || item.id || 'dsh-plugin');
20807
+ var manifestPath = String(item.manifestPath || '');
20808
+ var installed = !!item.installed;
20809
+ var enabled = item.enabled !== false;
20810
+ var actionHtml = installed
20811
+ ? '<div class="dsh-candidate-actions">' +
20812
+ '<label class="toggle"><input type="checkbox" id="dsh-bundle-enabled-' + index + '"' + (enabled ? ' checked' : '') + ' onchange="window.setDshBundleEnabled(' + index + ', this.checked)"><span class="toggle-track"></span></label>' +
20813
+ '<span style="font-size:11px;color:var(--text-dim);">' + esc(t(enabled ? 'plugins.dshEnable' : 'plugins.dshDisable')) + '</span>' +
20814
+ '<button type="button" class="sec-btn" onclick="window.uninstallDshBundle(' + index + ')">' + esc(t('plugins.dshUninstall')) + '</button>' +
20815
+ '<span class="plugin-status-badge">' + esc(t('plugins.dshInstalled')) + '</span></div>'
20816
+ : '<div class="dsh-candidate-actions"><button type="button" class="sec-btn primary" onclick="window.installDshBundle(' + index + ')"' + (manifestPath ? '' : ' disabled') + '>' + esc(t('plugins.dshInstall')) + '</button></div>';
20817
+ return '<div class="dsh-list-item"><strong>' + esc(name) + '</strong>' +
20818
+ '<div class="provider-meta">' + esc(String(item.source || '')) + '</div>' + actionHtml + '</div>';
20819
+ }).join('') + '</div>';
20820
+ }
20821
+
20822
+ window.installDshBundle = function(index) {
20823
+ var bundle = dshCollection(state.dshCompatibility && state.dshCompatibility.bundles)[index];
20824
+ if (!bundle || !bundle.manifestPath) return;
20825
+ if (!api.installDshBundle) { showUiNotice(t('plugins.dshUnavailable'), 'error', 'dsh-install'); return; }
20826
+ api.installDshBundle(bundle.manifestPath).then(function(result) {
20827
+ if (!result || !result.ok) throw new Error((result && result.error) || t('plugins.dshLoadError'));
20828
+ showUiNotice(t('plugins.dshInstallOk'), 'success', 'dsh-install-' + bundle.name);
20829
+ window.renderDshPlugin();
20830
+ }).catch(function(error) { showUiNotice(error.message || String(error), 'error', 'dsh-install-' + bundle.name); });
20831
+ };
20832
+
20833
+ window.uninstallDshBundle = function(index) {
20834
+ var bundle = dshCollection(state.dshCompatibility && state.dshCompatibility.bundles)[index];
20835
+ if (!bundle || !bundle.name) return;
20836
+ if (!api.uninstallDshBundle) { showUiNotice(t('plugins.dshUnavailable'), 'error', 'dsh-uninstall'); return; }
20837
+ api.uninstallDshBundle(bundle.name).then(function(result) {
20838
+ if (!result || !result.ok) throw new Error((result && result.error) || t('plugins.dshLoadError'));
20839
+ showUiNotice(t('plugins.dshUninstallOk'), 'success', 'dsh-uninstall-' + bundle.name);
20840
+ window.renderDshPlugin();
20841
+ }).catch(function(error) { showUiNotice(error.message || String(error), 'error', 'dsh-uninstall-' + bundle.name); });
20842
+ };
20843
+
20844
+ window.setDshBundleEnabled = function(index, enabled) {
20845
+ var bundle = dshCollection(state.dshCompatibility && state.dshCompatibility.bundles)[index];
20846
+ if (!bundle || !bundle.name) return;
20847
+ if (!api.setDshBundleEnabled) { showUiNotice(t('plugins.dshUnavailable'), 'error', 'dsh-toggle'); return; }
20848
+ api.setDshBundleEnabled(bundle.name, !!enabled).then(function(result) {
20849
+ if (!result || !result.ok) throw new Error((result && result.error) || t('plugins.dshLoadError'));
20850
+ showUiNotice(t('plugins.dshSetOk'), 'success', 'dsh-toggle-' + bundle.name);
20851
+ window.renderDshPlugin();
20852
+ }).catch(function(error) { showUiNotice(error.message || String(error), 'error', 'dsh-toggle-' + bundle.name); });
20853
+ };
20854
+
20484
20855
  window.renderDshPlugin = function() {
20485
20856
  var panel = document.getElementById('plugin-panel');
20486
20857
  if (!panel || state.pluginActiveTab !== 'dsh') return;
@@ -20538,7 +20909,7 @@ window.renderDshPluginContent = function() {
20538
20909
  '<dt>' + esc(t('plugins.dshUpdateChannel')) + '</dt><dd id="dsh-update-channel">' + esc(dshDisplayValue(updateChannel)) + '</dd></dl>' +
20539
20910
  '<div class="dsh-description">' + esc(t('plugins.dshReadonlyDetail')) + '</div></section>' +
20540
20911
  '<section class="dsh-card" id="dsh-profiles-card"><div class="dsh-card-title">' + esc(t('plugins.dshProfiles')) + '</div>' + dshListMarkup(dsh.profiles) + '</section>' +
20541
- '<section class="dsh-card" id="dsh-bundles-card"><div class="dsh-card-title">' + esc(t('plugins.dshBundles')) + '</div>' + dshListMarkup(dsh.bundles) + '</section>' +
20912
+ '<section class="dsh-card" id="dsh-bundles-card"><div class="dsh-card-title">' + esc(t('plugins.dshBundles')) + '</div>' + renderDshBundlesMarkup(dsh.bundles) + '</section>' +
20542
20913
  '<section class="dsh-card" id="dsh-warnings-card"><div class="dsh-card-title">' + esc(t('plugins.dshWarnings')) + '</div>' + dshListMarkup(dsh.warnings, t('plugins.dshNone'), true) + '</section>' +
20543
20914
  '<section class="dsh-card" id="dsh-unknown-keys-card"><div class="dsh-card-title">' + esc(t('plugins.dshUnknownKeys')) + '</div>' + dshListMarkup(dsh.unknownKeys) + '</section></div>';
20544
20915
  html += '<section class="plugin-section" id="dsh-config-files"><div class="plugin-section-head"><div class="plugin-section-title">' + esc(t('plugins.dshConfigFiles')) + '</div><span class="plugin-count-badge">' + configFiles.length + '</span></div>' + dshListMarkup(configFiles) + '</section>';
@@ -21030,10 +21401,18 @@ window.removeSkillFromUi = function(name) {
21030
21401
  });
21031
21402
  };
21032
21403
 
21404
+ window.refreshGithubOverview = function() {
21405
+ state.githubOverviewHash = '';
21406
+ window.loadGithubOverview('', { force: true });
21407
+ };
21408
+
21033
21409
  window.renderGithubCliPanel = function() {
21034
21410
  var panel = document.getElementById('plugin-panel');
21035
21411
  if (!panel) return;
21036
- panel.innerHTML = '<div id="gh-overview" class="settings-card" style="min-height:220px;padding:14px;"></div>';
21412
+ panel.innerHTML = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;">' +
21413
+ '<div style="flex:1;font-size:13px;font-weight:700;color:var(--text);">' + esc(t('plugins.github')) + '</div>' +
21414
+ '<button type="button" class="sec-btn" onclick="window.refreshGithubOverview()">' + iconSvg('refresh-cw', t('plugins.refreshGh'), 'tiny') + ' ' + esc(t('plugins.refreshGh')) + '</button></div>' +
21415
+ '<div id="gh-overview" class="settings-card" style="min-height:220px;padding:14px;"></div>';
21037
21416
  if (state.githubOverviewData) window.renderGithubOverview(state.githubOverviewData);
21038
21417
  else document.getElementById('gh-overview').innerHTML = '<div class="settings-empty">' + esc(t('plugins.runningGh')) + '...</div>';
21039
21418
  window.loadGithubOverview('', { initial: !state.githubOverviewLoaded });
@@ -21043,7 +21422,10 @@ window.renderGithubOverview = function(data) {
21043
21422
  var out = document.getElementById('gh-overview');
21044
21423
  if (!out) return;
21045
21424
  if (!data || !data.ok) {
21046
- out.innerHTML = '<div class="settings-empty">' + esc(data && data.error ? data.error : t('plugins.ghUnavailable')) + '</div>';
21425
+ out.innerHTML = '<div class="settings-empty">' + esc(data && data.error ? data.error : t('plugins.ghUnavailable')) + '</div>' +
21426
+ '<div style="display:flex;gap:8px;justify-content:center;margin-top:12px;">' +
21427
+ '<button type="button" class="sec-btn primary" onclick="window.githubCopilotLogin()">' + esc(t('plugins.connectGh')) + '</button>' +
21428
+ '<button type="button" class="sec-btn" onclick="window.refreshGithubOverview()">' + esc(t('plugins.refreshGh')) + '</button></div>';
21047
21429
  return;
21048
21430
  }
21049
21431
  var account = data.account || {};
@@ -21089,6 +21471,10 @@ window.githubOverviewSignature = function(data) {
21089
21471
 
21090
21472
  window.loadGithubOverview = function(repo, options) {
21091
21473
  options = options || {};
21474
+ if (options.force) {
21475
+ state.githubOverviewHash = '';
21476
+ state.githubOverviewLoaded = false;
21477
+ }
21092
21478
  var out = document.getElementById('gh-overview');
21093
21479
  repo = String(repo || '').trim();
21094
21480
  if (state.githubOverviewLoading) {
@@ -21871,6 +22257,47 @@ function guiHasSwitchableBranch() {
21871
22257
  return branches.length > 1;
21872
22258
  }
21873
22259
 
22260
+ window.showMobilePairing = function() {
22261
+ if (!api.mobilePairingQr) {
22262
+ showUiNotice('Mobile pairing API unavailable', 'error', 'mobile-pairing');
22263
+ return;
22264
+ }
22265
+ window.openSubWin(t('mobile.pair'), '<div style="text-align:center;color:var(--text-dim);">' + esc(t('common.loading')) + '</div>');
22266
+ api.mobilePairingQr().then(function(p) {
22267
+ if (!p || !p.ok) throw new Error((p && p.error) || 'Pairing failed');
22268
+ var expiresLabel = p.expiresAt ? Math.max(0, Math.round((p.expiresAt - Date.now()) / 1000)) + 's' : '';
22269
+ var html = '<div style="display:flex;flex-direction:column;align-items:center;gap:12px;padding:4px 2px;">' +
22270
+ '<img src="' + escAttr(p.dataUrl) + '" alt="Pairing QR" style="width:min(420px,100%);max-width:100%;border-radius:12px;background:#fff;padding:10px;">' +
22271
+ '<div style="font-size:12px;color:var(--text);word-break:break-all;text-align:center;">' + esc(p.url) + '</div>' +
22272
+ '<div style="font-size:11px;color:var(--text-dim);">' + esc(t('mobile.pairTokenFile')) + ': ' + esc(p.tokenFile) + '</div>' +
22273
+ '<div style="font-size:11px;color:var(--text-dim);">Tailscale: ' + esc(p.tailscaleIpv4 || '-') + '</div>' +
22274
+ (expiresLabel ? '<div style="font-size:11px;color:var(--text-dim);">' + esc('窗口期: ' + expiresLabel) + '</div>' : '') +
22275
+ '<div style="font-size:11px;color:var(--text-dim);text-align:center;max-width:420px;">' + esc(t('mobile.pairDesc')) + '</div>' +
22276
+ '</div>';
22277
+ window.openSubWin(t('mobile.pair'), html);
22278
+ // 连接成功后自动关闭二维码窗口
22279
+ if (api.mobilePairingStatus) {
22280
+ var poll = setInterval(function() {
22281
+ api.mobilePairingStatus().then(function(s) {
22282
+ var st = s && s.status;
22283
+ if (!st) return;
22284
+ if (st.confirmed) {
22285
+ clearInterval(poll);
22286
+ window.closeSubWin();
22287
+ showUiNotice('Mobile device connected', 'success', 'mobile-pairing-done');
22288
+ } else if (st.expired || !st.active) {
22289
+ clearInterval(poll);
22290
+ window.closeSubWin();
22291
+ showUiNotice('Pairing window expired', 'error', 'mobile-pairing-expired');
22292
+ }
22293
+ }).catch(function() {});
22294
+ }, 1000);
22295
+ }
22296
+ }).catch(function(err) {
22297
+ showUiNotice(err.message || String(err), 'error', 'mobile-pairing');
22298
+ });
22299
+ };
22300
+
21874
22301
  function guiToggleTheme() {
21875
22302
  var current = state.theme === 'light' ? 'light' : 'dark';
21876
22303
  window.setTheme(current === 'light' ? 'dark' : 'light');
@@ -21893,6 +22320,7 @@ var NEWMARK_GUI_COMMANDS = [
21893
22320
  guiCommand('workspace.manager', 'workspace.manager', 'workspace', { bindings:[{ keys:'Mod+K W', scope:'whenNotEditing' }], run:function(){ window.openWorkspaceManager(); } }),
21894
22321
  guiCommand('workspace.new', 'workspace.new', 'workspace', { run:function(){ window.showNewWorkspaceDialog(); } }),
21895
22322
  guiCommand('workspace.settings', 'workspace.settingsTitle', 'workspace', { run:function(){ window.openWsSettings(); }, available:function(){ return !!state.currentWorkspaceId; } }),
22323
+ guiCommand('mobile.pair', 'mobile.pair', 'general', { run:function(){ window.showMobilePairing(); } }),
21896
22324
 
21897
22325
  guiCommand('view.plugins', 'plugins.title', 'navigation', { bindings:[{ keys:'Mod+K P', scope:'whenNotEditing' }], run:function(){ window.showPluginList(); } }),
21898
22326
  guiCommand('view.plugins.mcp', 'plugins.mcp', 'navigation', { run:function(){ window.showPluginList('mcp'); } }),
@@ -22601,6 +23029,7 @@ function schedulePostStartupUiRendering() {
22601
23029
  if (s.agentBackend) state.agentBackend = s.agentBackend;
22602
23030
  if (s.configuredAgentBackend) state.configuredAgentBackend = s.configuredAgentBackend;
22603
23031
  if (s.agentBackendRestartRequired !== undefined) state.agentBackendRestartRequired = !!s.agentBackendRestartRequired;
23032
+ if (s.remoteTouchEnabled !== undefined) state.remoteTouchEnabled = !!s.remoteTouchEnabled;
22604
23033
  if (s.wslAvailable !== undefined) state.wslAvailable = !!s.wslAvailable;
22605
23034
  if (Array.isArray(s.wslDistros)) state.wslDistros = s.wslDistros;
22606
23035
  if (s.conversationId) state.activeBackendConversationId = s.conversationId;