botmux 2.94.0 → 2.96.0

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 (231) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +3 -3
  3. package/dist/adapters/backend/tmux-backend.d.ts +18 -2
  4. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  5. package/dist/adapters/backend/tmux-backend.js +44 -11
  6. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  7. package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
  8. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  9. package/dist/adapters/backend/types.d.ts +8 -0
  10. package/dist/adapters/backend/types.d.ts.map +1 -1
  11. package/dist/adapters/backend/types.js.map +1 -1
  12. package/dist/adapters/backend/zellij-backend.js +1 -1
  13. package/dist/adapters/backend/zellij-backend.js.map +1 -1
  14. package/dist/adapters/cli/kimi.d.ts +4 -0
  15. package/dist/adapters/cli/kimi.d.ts.map +1 -0
  16. package/dist/adapters/cli/kimi.js +54 -0
  17. package/dist/adapters/cli/kimi.js.map +1 -0
  18. package/dist/adapters/cli/registry.d.ts +2 -1
  19. package/dist/adapters/cli/registry.d.ts.map +1 -1
  20. package/dist/adapters/cli/registry.js +6 -20
  21. package/dist/adapters/cli/registry.js.map +1 -1
  22. package/dist/adapters/cli/types.d.ts +1 -1
  23. package/dist/adapters/cli/types.d.ts.map +1 -1
  24. package/dist/adapters/hook-installer.d.ts.map +1 -1
  25. package/dist/adapters/hook-installer.js +138 -28
  26. package/dist/adapters/hook-installer.js.map +1 -1
  27. package/dist/bot-registry.d.ts +101 -4
  28. package/dist/bot-registry.d.ts.map +1 -1
  29. package/dist/bot-registry.js +192 -3
  30. package/dist/bot-registry.js.map +1 -1
  31. package/dist/cli/stdin-encoding.d.ts +21 -0
  32. package/dist/cli/stdin-encoding.d.ts.map +1 -1
  33. package/dist/cli/stdin-encoding.js +37 -0
  34. package/dist/cli/stdin-encoding.js.map +1 -1
  35. package/dist/cli.d.ts.map +1 -1
  36. package/dist/cli.js +27 -4
  37. package/dist/cli.js.map +1 -1
  38. package/dist/core/ask-hook/opencode.d.ts +15 -17
  39. package/dist/core/ask-hook/opencode.d.ts.map +1 -1
  40. package/dist/core/ask-hook/opencode.js +17 -19
  41. package/dist/core/ask-hook/opencode.js.map +1 -1
  42. package/dist/core/command-handler.d.ts.map +1 -1
  43. package/dist/core/command-handler.js +29 -3
  44. package/dist/core/command-handler.js.map +1 -1
  45. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  46. package/dist/core/dashboard-ipc-server.js +282 -5
  47. package/dist/core/dashboard-ipc-server.js.map +1 -1
  48. package/dist/core/dashboard-rows.d.ts +2 -0
  49. package/dist/core/dashboard-rows.d.ts.map +1 -1
  50. package/dist/core/dashboard-rows.js +2 -0
  51. package/dist/core/dashboard-rows.js.map +1 -1
  52. package/dist/core/session-discovery.d.ts +9 -0
  53. package/dist/core/session-discovery.d.ts.map +1 -1
  54. package/dist/core/session-discovery.js +24 -0
  55. package/dist/core/session-discovery.js.map +1 -1
  56. package/dist/core/session-manager.d.ts.map +1 -1
  57. package/dist/core/session-manager.js +7 -6
  58. package/dist/core/session-manager.js.map +1 -1
  59. package/dist/core/terminal-url.d.ts.map +1 -1
  60. package/dist/core/terminal-url.js +15 -0
  61. package/dist/core/terminal-url.js.map +1 -1
  62. package/dist/core/trigger-session.d.ts.map +1 -1
  63. package/dist/core/trigger-session.js +101 -9
  64. package/dist/core/trigger-session.js.map +1 -1
  65. package/dist/core/types.d.ts +16 -0
  66. package/dist/core/types.d.ts.map +1 -1
  67. package/dist/core/types.js.map +1 -1
  68. package/dist/core/worker-pool.d.ts +1 -1
  69. package/dist/core/worker-pool.d.ts.map +1 -1
  70. package/dist/core/worker-pool.js +69 -11
  71. package/dist/core/worker-pool.js.map +1 -1
  72. package/dist/daemon.d.ts.map +1 -1
  73. package/dist/daemon.js +14 -11
  74. package/dist/daemon.js.map +1 -1
  75. package/dist/dashboard/aggregator.d.ts +5 -0
  76. package/dist/dashboard/aggregator.d.ts.map +1 -1
  77. package/dist/dashboard/aggregator.js +7 -0
  78. package/dist/dashboard/aggregator.js.map +1 -1
  79. package/dist/dashboard/bot-payload.d.ts +8 -0
  80. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  81. package/dist/dashboard/bot-payload.js +8 -1
  82. package/dist/dashboard/bot-payload.js.map +1 -1
  83. package/dist/dashboard/connector-api.d.ts.map +1 -1
  84. package/dist/dashboard/connector-api.js +14 -3
  85. package/dist/dashboard/connector-api.js.map +1 -1
  86. package/dist/dashboard/session-cleanup.d.ts +1 -0
  87. package/dist/dashboard/session-cleanup.d.ts.map +1 -1
  88. package/dist/dashboard/session-cleanup.js +2 -0
  89. package/dist/dashboard/session-cleanup.js.map +1 -1
  90. package/dist/dashboard/settings-write-applier.d.ts +2 -1
  91. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  92. package/dist/dashboard/settings-write-applier.js +7 -0
  93. package/dist/dashboard/settings-write-applier.js.map +1 -1
  94. package/dist/dashboard/trigger-api.d.ts +4 -0
  95. package/dist/dashboard/trigger-api.d.ts.map +1 -1
  96. package/dist/dashboard/trigger-api.js +20 -0
  97. package/dist/dashboard/trigger-api.js.map +1 -1
  98. package/dist/dashboard/web/bot-defaults.d.ts +1 -0
  99. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  100. package/dist/dashboard/web/bot-defaults.js +352 -37
  101. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  102. package/dist/dashboard/web/connectors.d.ts +11 -0
  103. package/dist/dashboard/web/connectors.d.ts.map +1 -1
  104. package/dist/dashboard/web/connectors.js +45 -1
  105. package/dist/dashboard/web/connectors.js.map +1 -1
  106. package/dist/dashboard/web/groups.d.ts +7 -0
  107. package/dist/dashboard/web/groups.d.ts.map +1 -1
  108. package/dist/dashboard/web/groups.js +62 -15
  109. package/dist/dashboard/web/groups.js.map +1 -1
  110. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  111. package/dist/dashboard/web/i18n.js +82 -4
  112. package/dist/dashboard/web/i18n.js.map +1 -1
  113. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  114. package/dist/dashboard/web/sessions.js +149 -5
  115. package/dist/dashboard/web/sessions.js.map +1 -1
  116. package/dist/dashboard/web/settings.d.ts.map +1 -1
  117. package/dist/dashboard/web/settings.js +11 -0
  118. package/dist/dashboard/web/settings.js.map +1 -1
  119. package/dist/dashboard/web/whiteboards.d.ts.map +1 -1
  120. package/dist/dashboard/web/whiteboards.js +14 -5
  121. package/dist/dashboard/web/whiteboards.js.map +1 -1
  122. package/dist/dashboard/webhook-routes.d.ts.map +1 -1
  123. package/dist/dashboard/webhook-routes.js +126 -6
  124. package/dist/dashboard/webhook-routes.js.map +1 -1
  125. package/dist/dashboard-web/app.js +814 -725
  126. package/dist/dashboard-web/apple-touch-icon.png +0 -0
  127. package/dist/dashboard-web/favicon.png +0 -0
  128. package/dist/dashboard-web/index.html +2 -0
  129. package/dist/dashboard-web/style.css +47 -0
  130. package/dist/dashboard.js +247 -16
  131. package/dist/dashboard.js.map +1 -1
  132. package/dist/global-config.d.ts +10 -0
  133. package/dist/global-config.d.ts.map +1 -1
  134. package/dist/global-config.js +8 -0
  135. package/dist/global-config.js.map +1 -1
  136. package/dist/i18n/en.d.ts.map +1 -1
  137. package/dist/i18n/en.js +21 -0
  138. package/dist/i18n/en.js.map +1 -1
  139. package/dist/i18n/zh.d.ts.map +1 -1
  140. package/dist/i18n/zh.js +21 -0
  141. package/dist/i18n/zh.js.map +1 -1
  142. package/dist/im/lark/card-builder.d.ts.map +1 -1
  143. package/dist/im/lark/card-builder.js +1 -0
  144. package/dist/im/lark/card-builder.js.map +1 -1
  145. package/dist/im/lark/client.d.ts +12 -0
  146. package/dist/im/lark/client.d.ts.map +1 -1
  147. package/dist/im/lark/client.js +48 -21
  148. package/dist/im/lark/client.js.map +1 -1
  149. package/dist/im/lark/event-dispatcher.d.ts +15 -1
  150. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  151. package/dist/im/lark/event-dispatcher.js +199 -16
  152. package/dist/im/lark/event-dispatcher.js.map +1 -1
  153. package/dist/im/lark/grant-command.js +4 -4
  154. package/dist/im/lark/grant-command.js.map +1 -1
  155. package/dist/im/lark/message-parser.d.ts +40 -1
  156. package/dist/im/lark/message-parser.d.ts.map +1 -1
  157. package/dist/im/lark/message-parser.js +37 -2
  158. package/dist/im/lark/message-parser.js.map +1 -1
  159. package/dist/im/lark/summary-command.d.ts +19 -0
  160. package/dist/im/lark/summary-command.d.ts.map +1 -0
  161. package/dist/im/lark/summary-command.js +276 -0
  162. package/dist/im/lark/summary-command.js.map +1 -0
  163. package/dist/mir-local-runtime.d.ts +40 -0
  164. package/dist/mir-local-runtime.d.ts.map +1 -1
  165. package/dist/mir-local-runtime.js +246 -52
  166. package/dist/mir-local-runtime.js.map +1 -1
  167. package/dist/mir-runner.js +26 -1
  168. package/dist/mir-runner.js.map +1 -1
  169. package/dist/platform/binding.d.ts +6 -0
  170. package/dist/platform/binding.d.ts.map +1 -1
  171. package/dist/platform/binding.js +17 -0
  172. package/dist/platform/binding.js.map +1 -1
  173. package/dist/platform/tunnel-client.d.ts +2 -0
  174. package/dist/platform/tunnel-client.d.ts.map +1 -1
  175. package/dist/platform/tunnel-client.js.map +1 -1
  176. package/dist/services/bot-config-store.d.ts.map +1 -1
  177. package/dist/services/bot-config-store.js +1 -0
  178. package/dist/services/bot-config-store.js.map +1 -1
  179. package/dist/services/card-prefs-store.d.ts +2 -2
  180. package/dist/services/card-prefs-store.d.ts.map +1 -1
  181. package/dist/services/card-prefs-store.js +5 -5
  182. package/dist/services/card-prefs-store.js.map +1 -1
  183. package/dist/services/chat-reply-mode-store.d.ts +6 -4
  184. package/dist/services/chat-reply-mode-store.d.ts.map +1 -1
  185. package/dist/services/chat-reply-mode-store.js +6 -4
  186. package/dist/services/chat-reply-mode-store.js.map +1 -1
  187. package/dist/services/oncall-store.d.ts +25 -0
  188. package/dist/services/oncall-store.d.ts.map +1 -1
  189. package/dist/services/oncall-store.js +56 -0
  190. package/dist/services/oncall-store.js.map +1 -1
  191. package/dist/services/pi-transcript.d.ts +19 -0
  192. package/dist/services/pi-transcript.d.ts.map +1 -0
  193. package/dist/services/pi-transcript.js +222 -0
  194. package/dist/services/pi-transcript.js.map +1 -0
  195. package/dist/services/summary-range-store.d.ts +21 -0
  196. package/dist/services/summary-range-store.d.ts.map +1 -0
  197. package/dist/services/summary-range-store.js +89 -0
  198. package/dist/services/summary-range-store.js.map +1 -0
  199. package/dist/services/transcript-resolver.d.ts.map +1 -1
  200. package/dist/services/transcript-resolver.js +21 -4
  201. package/dist/services/transcript-resolver.js.map +1 -1
  202. package/dist/services/trigger-types.d.ts +14 -2
  203. package/dist/services/trigger-types.d.ts.map +1 -1
  204. package/dist/services/trigger-types.js +15 -1
  205. package/dist/services/trigger-types.js.map +1 -1
  206. package/dist/setup/bot-config-editor.d.ts +7 -0
  207. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  208. package/dist/setup/bot-config-editor.js +35 -0
  209. package/dist/setup/bot-config-editor.js.map +1 -1
  210. package/dist/types.d.ts +21 -1
  211. package/dist/types.d.ts.map +1 -1
  212. package/dist/utils/executable.d.ts +3 -0
  213. package/dist/utils/executable.d.ts.map +1 -0
  214. package/dist/utils/executable.js +24 -0
  215. package/dist/utils/executable.js.map +1 -0
  216. package/dist/utils/terminal-favicon.d.ts +2 -0
  217. package/dist/utils/terminal-favicon.d.ts.map +1 -0
  218. package/dist/utils/terminal-favicon.js +5 -0
  219. package/dist/utils/terminal-favicon.js.map +1 -0
  220. package/dist/worker.js +211 -12
  221. package/dist/worker.js.map +1 -1
  222. package/dist/workflows/definition.d.ts +8 -8
  223. package/dist/workflows/events/payloads.d.ts +10 -10
  224. package/dist/workflows/events/schema.d.ts +28 -28
  225. package/dist/workflows/v3/bot-resolve.d.ts +1 -1
  226. package/dist/workflows/v3/bot-resolve.d.ts.map +1 -1
  227. package/dist/workflows/v3/bot-resolve.js +2 -1
  228. package/dist/workflows/v3/bot-resolve.js.map +1 -1
  229. package/dist/workflows/v3/cli-run.js +2 -2
  230. package/dist/workflows/v3/cli-run.js.map +1 -1
  231. package/package.json +2 -2
@@ -7,6 +7,14 @@ import { store } from './store.js';
7
7
  import { botAvatarHtml, escapeHtml, loadNameMaps, loadingHtml, t } from './ui.js';
8
8
  let cache = { bots: [] };
9
9
  let loadError = null;
10
+ let cliOptions = [
11
+ { id: 'claude-code', label: 'Claude' },
12
+ { id: 'codex', label: 'Codex' },
13
+ { id: 'traex', label: 'traex' },
14
+ ];
15
+ let cliOptionsLoaded = false;
16
+ let ttadkModelDefault = 'glm-5.1';
17
+ let ttadkModelSuggestions = [];
10
18
  // master-detail:左侧员工名册选中谁,右侧就渲染谁的档案
11
19
  let selectedAppId = null;
12
20
  export function displayCliId(bot, sessionFallback) {
@@ -24,6 +32,76 @@ function cliIdOf(appId) {
24
32
  }
25
33
  return best?.cliId ?? '';
26
34
  }
35
+ async function loadCliOptions() {
36
+ if (cliOptionsLoaded)
37
+ return;
38
+ cliOptionsLoaded = true;
39
+ try {
40
+ const r = await fetch('/api/cli-options');
41
+ const body = await r.json().catch(() => ({}));
42
+ if (r.ok && Array.isArray(body?.options)) {
43
+ cliOptions = body.options.filter((o) => o && typeof o.id === 'string' && typeof o.label === 'string');
44
+ if (typeof body.ttadkModelDefault === 'string' && body.ttadkModelDefault.trim()) {
45
+ ttadkModelDefault = body.ttadkModelDefault.trim();
46
+ }
47
+ if (Array.isArray(body.ttadkModelSuggestions)) {
48
+ ttadkModelSuggestions = body.ttadkModelSuggestions.filter((s) => typeof s === 'string');
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ // Keep the static fallback; saving still works for plain claude-code.
54
+ }
55
+ }
56
+ function agentSelectionKey(bot, sessionFallback) {
57
+ const explicit = typeof bot?.agentSelectionKey === 'string' && bot.agentSelectionKey ? bot.agentSelectionKey : '';
58
+ if (explicit)
59
+ return explicit;
60
+ const cli = displayCliId(bot, sessionFallback);
61
+ return cli || 'claude-code';
62
+ }
63
+ function selectedCliOption(key) {
64
+ return cliOptions.find(o => o.id === key);
65
+ }
66
+ function modelSuggestionsForOption(opt) {
67
+ if (opt?.gateway === 'ttadk' && opt.acceptsModel !== false)
68
+ return ttadkModelSuggestions;
69
+ return [];
70
+ }
71
+ export function renderBotAgentSection(b, sessionFallback) {
72
+ const key = agentSelectionKey(b, sessionFallback);
73
+ const optHtml = cliOptions
74
+ .map(o => `<option value="${escapeHtml(o.id)}" ${o.id === key ? 'selected' : ''}>${escapeHtml(o.label)}(${escapeHtml(o.id)})</option>`)
75
+ .join('');
76
+ const model = typeof b?.model === 'string' ? b.model : '';
77
+ const suggestions = modelSuggestionsForOption(selectedCliOption(key));
78
+ const disabled = selectedCliOption(key)?.gateway === 'ttadk' && selectedCliOption(key)?.acceptsModel === false;
79
+ return `<section class="bd-section">
80
+ <h3 class="bd-section-title">${t('botDefaults.sectionAgent')}</h3>
81
+ <div class="bd-row">
82
+ <label>
83
+ <span>${t('botDefaults.agentCli')}</span>
84
+ <select data-input="agentCliId">${optHtml}</select>
85
+ </label>
86
+ </div>
87
+ <div class="bd-row">
88
+ <label>
89
+ <span>${t('botDefaults.agentModel')}</span>
90
+ <input type="text" data-input="agentModel" list="agent-model-suggestions-${escapeHtml(b.larkAppId)}"
91
+ placeholder="${escapeHtml(t('botDefaults.agentModelPlaceholder'))}"
92
+ value="${escapeHtml(model)}" ${disabled ? 'disabled' : ''}>
93
+ <datalist id="agent-model-suggestions-${escapeHtml(b.larkAppId)}">
94
+ ${suggestions.map(m => `<option value="${escapeHtml(m)}"></option>`).join('')}
95
+ </datalist>
96
+ </label>
97
+ <small class="bd-help">${t('botDefaults.agentHelp')}</small>
98
+ <div class="actions">
99
+ <button type="button" class="primary" data-action="save-agent">${t('botDefaults.agentSave')}</button>
100
+ <span class="oncall-status" data-agent-status></span>
101
+ </div>
102
+ </div>
103
+ </section>`;
104
+ }
27
105
  function pageHtml() {
28
106
  return `<section class="page">
29
107
  <div class="page-heading">
@@ -89,7 +167,7 @@ export async function renderBotDefaultsPage(root) {
89
167
  refreshBtn.disabled = true;
90
168
  try {
91
169
  botProfileRoleCache.clear();
92
- await loadBots();
170
+ await Promise.all([loadBots(), loadCliOptions()]);
93
171
  rerender();
94
172
  }
95
173
  finally {
@@ -107,7 +185,7 @@ export async function renderBotDefaultsPage(root) {
107
185
  });
108
186
  // /api/bots 要逐 daemon 探活,慢——先亮 loading 占住右侧详情区。
109
187
  listEl.innerHTML = loadingHtml();
110
- await loadBots();
188
+ await Promise.all([loadBots(), loadCliOptions()]);
111
189
  function rerender() {
112
190
  const f = new FormData(form);
113
191
  const q = (f.get('q') ?? '').toLowerCase();
@@ -166,7 +244,11 @@ export async function renderBotDefaultsPage(root) {
166
244
  </article>`;
167
245
  }
168
246
  const def = b.defaultOncall ?? { enabled: false, workingDir: '', since: 0 };
169
- const enabled = !!def.enabled;
247
+ // 默认工作目录模式(三选一,dashboard 互斥):oncall > default > off。
248
+ const wdMode = def.enabled ? 'oncall' : (b.defaultWorkingDir ? 'default' : 'off');
249
+ // 输入框预填「最可能的目录」:defaultWorkingDir 优先,否则 oncall 目录(disable 后仍保留),
250
+ // 便于来回切换模式不用重输。
251
+ const wdDirValue = b.defaultWorkingDir || def.workingDir || '';
170
252
  const name = b.botName ?? b.larkAppId;
171
253
  const cli = displayCliId(b, cliIdOf(b.larkAppId));
172
254
  return `<article class="bd-card bd-profile" data-appid="${escapeHtml(b.larkAppId)}">
@@ -185,23 +267,29 @@ export async function renderBotDefaultsPage(root) {
185
267
  </header>
186
268
  <div class="bd-body bd-grid">
187
269
  <section class="bd-tile">
270
+ ${renderBotAgentSection(b, cli)}
188
271
  <section class="bd-section">
189
- <h3 class="bd-section-title">${t('botDefaults.sectionOncall')}</h3>
190
- <label class="toggle-row">
191
- <input type="checkbox" data-action="toggle" ${enabled ? 'checked' : ''}>
192
- <span class="switch" aria-hidden="true"></span>
193
- <span class="toggle-tx"><strong>${t('botDefaults.defaultOncall')}</strong>
194
- <small>${t('botDefaults.defaultOncallHelp')}。${t('botDefaults.warning')}</small></span>
195
- </label>
272
+ <h3 class="bd-section-title">${t('botDefaults.sectionWorkingDir')}</h3>
196
273
  <div class="bd-row">
197
274
  <label>
198
- <span>${t('botDefaults.workingDir')}</span>
275
+ <span>${t('botDefaults.workingDirMode')}</span>
276
+ <select data-input="workingDirMode">
277
+ <option value="off" ${wdMode === 'off' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeOff'))}</option>
278
+ <option value="default" ${wdMode === 'default' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeDefault'))}</option>
279
+ <option value="oncall" ${wdMode === 'oncall' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeOncall'))}</option>
280
+ </select>
281
+ </label>
282
+ <small class="bd-help">${t('botDefaults.workingDirModeHelp')}</small>
283
+ </div>
284
+ <div class="bd-row" data-wd-dir-row ${wdMode === 'off' ? 'hidden' : ''}>
285
+ <label>
286
+ <span>${t('botDefaults.workingDirField')}</span>
199
287
  <input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
200
- value="${escapeHtml(def.workingDir ?? '')}" ${enabled ? '' : 'disabled'}>
288
+ value="${escapeHtml(wdDirValue)}">
201
289
  </label>
202
290
  </div>
203
291
  <div class="actions">
204
- <button type="button" class="primary" data-action="save">${t('botDefaults.save')}</button>
292
+ <button type="button" class="primary" data-action="save-working-dir">${t('botDefaults.save')}</button>
205
293
  <span class="oncall-status" data-status></span>
206
294
  </div>
207
295
  ${renderAutoStartControls(b)}
@@ -209,8 +297,8 @@ export async function renderBotDefaultsPage(root) {
209
297
  ${renderSandboxSection(b)}
210
298
  </section>
211
299
  <section class="bd-tile">${renderRoleSection(b)}</section>
212
- <section class="bd-tile">${renderSessionModeSection(b)}${renderCrossBotSection(b)}${renderSessionCapSection(b)}${renderStartupCommandsSection(b)}${renderEnvSection(b)}</section>
213
- <section class="bd-tile">${renderCardBehaviorSection(b)}${renderBrandSection(b)}</section>
300
+ <section class="bd-tile">${renderSessionModeSection(b)}${renderCrossBotSection(b)}${renderSessionCapSection(b)}${renderStartupCommandsSection(b)}${renderLaunchShellSection(b)}${renderEnvSection(b)}</section>
301
+ <section class="bd-tile">${renderCardBehaviorSection(b)}${renderSummaryTriggerSection(b)}${renderBrandSection(b)}</section>
214
302
  <section class="bd-tile">${renderGrantSection(b)}</section>
215
303
  </div>
216
304
  </article>`;
@@ -354,6 +442,29 @@ export async function renderBotDefaultsPage(root) {
354
442
  <div class="actions"><span class="oncall-status" data-crossbot-status></span></div>
355
443
  </section>`;
356
444
  }
445
+ function renderSummaryTriggerSection(b) {
446
+ const range = b.summaryRange ?? { limit: 50, sinceHours: 24 };
447
+ const limit = Number.isInteger(range.limit) && range.limit >= 0 ? range.limit : 50;
448
+ const sinceHours = Number.isInteger(range.sinceHours) && range.sinceHours >= 0 ? range.sinceHours : 24;
449
+ return `<section class="bd-section">
450
+ <h3 class="bd-section-title">${t('botDefaults.sectionSummaryTrigger')}</h3>
451
+ <div class="bd-row bd-summary-limits">
452
+ <label>
453
+ <span>${t('botDefaults.summaryLimit')}</span>
454
+ <input type="number" min="0" step="1" data-input="summaryLimit" value="${limit}">
455
+ </label>
456
+ <label>
457
+ <span>${t('botDefaults.summarySinceHours')}</span>
458
+ <input type="number" min="0" step="1" data-input="summarySinceHours" value="${sinceHours}">
459
+ </label>
460
+ </div>
461
+ <small class="bd-help">${t('botDefaults.summaryLimitHelp')}</small>
462
+ <div class="actions">
463
+ <button type="button" class="primary" data-action="save-summary-trigger">${t('botDefaults.summarySave')}</button>
464
+ <span class="oncall-status" data-summary-trigger-status></span>
465
+ </div>
466
+ </section>`;
467
+ }
357
468
  // 会话模式:私聊(p2pMode)+ 普通群(regularGroupReplyMode)两个默认会话方式
358
469
  // 放在同一板块,各自一个下拉、一改即保存。
359
470
  // • p2pMode → PUT /api/bots/:appId/p2p-mode(走 applyConfigField,与 /botconfig 同路径)
@@ -364,7 +475,7 @@ export async function renderBotDefaultsPage(root) {
364
475
  const p2p = b.p2pMode === 'chat' ? 'chat' : 'thread';
365
476
  const regular = (b.regularGroupReplyMode === 'new-topic' || b.regularGroupReplyMode === 'shared' || b.regularGroupReplyMode === 'chat-topic')
366
477
  ? b.regularGroupReplyMode : 'chat';
367
- const mention = (b.regularGroupMentionMode === 'topic' || b.regularGroupMentionMode === 'never')
478
+ const mention = (b.regularGroupMentionMode === 'topic' || b.regularGroupMentionMode === 'never' || b.regularGroupMentionMode === 'ambient')
368
479
  ? b.regularGroupMentionMode : 'always';
369
480
  const docMode = b.docSubscribeDefaultMode === 'all' ? 'all' : 'mention-only';
370
481
  const opt = (v, label) => `<option value="${v}" ${regular === v ? 'selected' : ''}>${escapeHtml(label)}</option>`;
@@ -407,6 +518,7 @@ export async function renderBotDefaultsPage(root) {
407
518
  ${mopt('always', t('botDefaults.mentionModeAlways'))}
408
519
  ${mopt('topic', t('botDefaults.mentionModeTopic'))}
409
520
  ${mopt('never', t('botDefaults.mentionModeNever'))}
521
+ ${mopt('ambient', t('botDefaults.mentionModeAmbient'))}
410
522
  </select>
411
523
  </label>
412
524
  <small class="bd-help">${t('botDefaults.mentionModeHelp')}</small>
@@ -475,6 +587,24 @@ export async function renderBotDefaultsPage(root) {
475
587
  </div>
476
588
  </div>`;
477
589
  }
590
+ // 启动 shell launchShell:启动 CLI 用的 shell(zsh|bash|sh 或绝对路径),覆盖 $SHELL。
591
+ // 用于登录 $SHELL(如 bash)的 rc 文件里 `exec zsh` 跳转、导致 CLI 起不来的场景。
592
+ // next-session 生效。PUT /api/bot-launch-shell 落 bots.json。
593
+ function renderLaunchShellSection(b) {
594
+ const val = typeof b.launchShell === 'string' ? b.launchShell : '';
595
+ return `<div class="bd-subsection">
596
+ <h4 class="bd-subsection-title">${t('botDefaults.sectionLaunchShell')}</h4>
597
+ <p class="bd-section-note">${t('botDefaults.launchShellHelp')}</p>
598
+ <input type="text" data-input="launchShell"
599
+ placeholder="${escapeHtml(t('botDefaults.launchShellPlaceholder'))}"
600
+ value="${escapeHtml(val)}"
601
+ style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">
602
+ <div class="actions">
603
+ <button type="button" class="primary" data-action="save-launch-shell">${t('botDefaults.launchShellSave')}</button>
604
+ <span class="oncall-status" data-launch-shell-status></span>
605
+ </div>
606
+ </div>`;
607
+ }
478
608
  // 环境变量 env:注入到本 bot CLI 进程的环境变量(JSON 对象),如让某个 bot 走 GLM/
479
609
  // 第三方服务商(ANTHROPIC_BASE_URL+ANTHROPIC_AUTH_TOKEN)或设 HTTPS_PROXY。next-session
480
610
  // 生效(下个新会话起注入)。PUT /api/bots/:appId/env 落 bots.json,跨后端按会话注入
@@ -675,53 +805,136 @@ export async function renderBotDefaultsPage(root) {
675
805
  listEl.querySelectorAll('.bd-card').forEach(card => {
676
806
  const appId = card.dataset.appid;
677
807
  void ensureProfileRolesLoaded(appId, card);
678
- const toggle = card.querySelector('input[data-action=toggle]');
808
+ // ── 默认工作目录模式(三选一互斥:off / default / oncall)─────────────────
809
+ const wdModeSel = card.querySelector('select[data-input=workingDirMode]');
679
810
  const input = card.querySelector('input[data-input=workingDir]');
680
- const saveBtn = card.querySelector('button[data-action=save]');
811
+ const wdDirRow = card.querySelector('[data-wd-dir-row]');
812
+ const saveBtn = card.querySelector('button[data-action=save-working-dir]');
681
813
  const statusEl = card.querySelector('[data-status]');
682
- if (!toggle || !input || !saveBtn || !statusEl)
814
+ if (!wdModeSel || !input || !saveBtn || !statusEl)
683
815
  return; // error card
684
- toggle.addEventListener('change', () => {
685
- input.disabled = !toggle.checked;
686
- if (toggle.checked)
816
+ // ── Agent CLI / model (next-session) ─────────────────────────────────
817
+ const agentCliSel = card.querySelector('select[data-input=agentCliId]');
818
+ const agentModelInput = card.querySelector('input[data-input=agentModel]');
819
+ const agentSaveBtn = card.querySelector('button[data-action=save-agent]');
820
+ const agentStatusEl = card.querySelector('[data-agent-status]');
821
+ function syncAgentModelField() {
822
+ if (!agentCliSel || !agentModelInput)
823
+ return;
824
+ const list = card.querySelector(`#agent-model-suggestions-${CSS.escape(appId)}`);
825
+ const opt = selectedCliOption(agentCliSel.value);
826
+ const isTtadk = opt?.gateway === 'ttadk';
827
+ const acceptsModel = isTtadk && opt.acceptsModel !== false;
828
+ if (isTtadk && !acceptsModel) {
829
+ if (list)
830
+ list.innerHTML = '';
831
+ agentModelInput.value = '';
832
+ agentModelInput.disabled = true;
833
+ agentModelInput.placeholder = t('botOnboarding.modelTtadkCocoPlaceholder');
834
+ return;
835
+ }
836
+ agentModelInput.disabled = false;
837
+ if (acceptsModel) {
838
+ if (list)
839
+ list.innerHTML = ttadkModelSuggestions.map(m => `<option value="${escapeHtml(m)}"></option>`).join('');
840
+ agentModelInput.placeholder = t('botOnboarding.modelTtadkPlaceholder').replace('{model}', ttadkModelDefault);
841
+ if (!agentModelInput.value.trim())
842
+ agentModelInput.value = ttadkModelDefault;
843
+ }
844
+ else {
845
+ if (list)
846
+ list.innerHTML = '';
847
+ agentModelInput.placeholder = t('botDefaults.agentModelPlaceholder');
848
+ if (agentModelInput.value.trim() === ttadkModelDefault)
849
+ agentModelInput.value = '';
850
+ }
851
+ }
852
+ if (agentCliSel && agentModelInput && agentSaveBtn && agentStatusEl) {
853
+ agentCliSel.addEventListener('change', syncAgentModelField);
854
+ agentSaveBtn.addEventListener('click', async () => {
855
+ agentStatusEl.textContent = '';
856
+ agentStatusEl.className = 'oncall-status';
857
+ agentSaveBtn.disabled = true;
858
+ agentCliSel.disabled = true;
859
+ agentModelInput.disabled = true;
860
+ try {
861
+ const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/agent`, {
862
+ method: 'PUT',
863
+ headers: { 'content-type': 'application/json' },
864
+ body: JSON.stringify({ cliId: agentCliSel.value, model: agentModelInput.value }),
865
+ });
866
+ const body = await r.json().catch(() => ({}));
867
+ if (r.ok && body.ok) {
868
+ agentStatusEl.textContent = `✓ ${t('botDefaults.agentSaved')}`;
869
+ agentStatusEl.classList.add('hint-ok');
870
+ const cached = cache.bots.find((bb) => bb.larkAppId === appId);
871
+ if (cached) {
872
+ cached.cliId = body.cliId;
873
+ cached.wrapperCli = body.wrapperCli ?? null;
874
+ cached.model = body.model ?? '';
875
+ cached.agentSelectionKey = body.selectionKey ?? agentCliSel.value;
876
+ }
877
+ }
878
+ else {
879
+ agentStatusEl.textContent = `✗ ${body.error ?? r.status}`;
880
+ agentStatusEl.classList.add('hint-warn-inline');
881
+ }
882
+ }
883
+ catch (e) {
884
+ agentStatusEl.textContent = `✗ ${e?.message ?? e}`;
885
+ agentStatusEl.classList.add('hint-warn-inline');
886
+ }
887
+ finally {
888
+ agentSaveBtn.disabled = false;
889
+ agentCliSel.disabled = false;
890
+ agentModelInput.disabled = false;
891
+ syncAgentModelField();
892
+ }
893
+ });
894
+ }
895
+ // 选「关闭」隐藏目录输入框;选其它则显示并聚焦(off 不需要目录)。
896
+ wdModeSel.addEventListener('change', () => {
897
+ const off = wdModeSel.value === 'off';
898
+ if (wdDirRow)
899
+ wdDirRow.hidden = off;
900
+ if (!off)
687
901
  input.focus();
688
902
  });
689
903
  saveBtn.addEventListener('click', async () => {
690
904
  statusEl.textContent = '';
691
905
  statusEl.className = 'oncall-status';
692
- const enabled = toggle.checked;
906
+ const mode = wdModeSel.value;
693
907
  const workingDir = input.value.trim();
694
- if (enabled && !workingDir) {
908
+ if (mode !== 'off' && !workingDir) {
695
909
  statusEl.textContent = t('botDefaults.required');
696
910
  statusEl.classList.add('hint-warn-inline');
697
911
  return;
698
912
  }
699
913
  saveBtn.disabled = true;
700
914
  try {
701
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/default-oncall`, {
915
+ const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/working-dir-mode`, {
702
916
  method: 'PUT',
703
917
  headers: { 'content-type': 'application/json' },
704
- body: JSON.stringify({ enabled, workingDir }),
918
+ body: JSON.stringify({ mode, workingDir }),
705
919
  });
706
920
  const body = await r.json().catch(() => ({}));
707
921
  if (r.ok && body.ok) {
708
922
  const resolvedNote = body.resolvedPath ? ` → ${body.resolvedPath}` : '';
709
- statusEl.textContent = enabled
710
- ? `✓ 已开启${resolvedNote}(未绑定的群下次开话题自动 oncall)`
711
- : '✓ 已关闭(已绑定的群不动)';
923
+ statusEl.textContent = `✓ ${t('botDefaults.workingDirSaved')}${resolvedNote}`;
712
924
  statusEl.classList.add('hint-ok');
713
925
  // Patch in-cache snapshot so the next manual Refresh / filter
714
- // rerender shows the new since/workingDir. We deliberately don't
715
- // call rerender() here — that would rebuild the card and wipe the
716
- // success toast the user just saw.
926
+ // rerender reflects the new mode. We deliberately don't call
927
+ // rerender() here — that would rebuild the card and wipe the toast.
717
928
  const cached = cache.bots.find((b) => b.larkAppId === appId);
718
- if (cached && body.defaultOncall)
719
- cached.defaultOncall = body.defaultOncall;
720
- // Update the visible "上次启用时间" line in-place so the user
721
- // sees the timestamp jump without losing the toast.
929
+ if (cached) {
930
+ if (body.defaultOncall)
931
+ cached.defaultOncall = body.defaultOncall;
932
+ cached.defaultWorkingDir = body.defaultWorkingDir ?? null;
933
+ }
934
+ // Oncall 模式 re-stamps `since` → reflect it in the meta line.
722
935
  const metaEl = card.querySelector('[data-oncall-since]');
723
936
  if (metaEl && body.defaultOncall?.since != null) {
724
- metaEl.textContent = `上次启用时间:${fmtSince(body.defaultOncall.since)}`;
937
+ metaEl.textContent = `${t('botDefaults.lastEnabled')}: ${fmtSince(body.defaultOncall.since)}`;
725
938
  }
726
939
  }
727
940
  else {
@@ -889,6 +1102,67 @@ export async function renderBotDefaultsPage(root) {
889
1102
  putCardPref({ botToBotSameDir: crossBotCb.checked }, crossBotCb, crossBotStatusEl);
890
1103
  });
891
1104
  }
1105
+ // ── /summary 总结范围 ───────────────────────────────────────────────
1106
+ const summaryLimitInput = card.querySelector('input[data-input=summaryLimit]');
1107
+ const summarySinceInput = card.querySelector('input[data-input=summarySinceHours]');
1108
+ const summarySaveBtn = card.querySelector('button[data-action=save-summary-trigger]');
1109
+ const summaryStatusEl = card.querySelector('[data-summary-trigger-status]');
1110
+ function readNonNegativeInt(input, fallback) {
1111
+ const raw = input.value.trim();
1112
+ if (raw === '')
1113
+ return fallback;
1114
+ if (!/^(0|[1-9]\d*)$/.test(raw))
1115
+ return null;
1116
+ return Number(raw);
1117
+ }
1118
+ if (summaryLimitInput && summarySinceInput && summarySaveBtn) {
1119
+ summarySaveBtn.addEventListener('click', async () => {
1120
+ if (!summaryStatusEl)
1121
+ return;
1122
+ summaryStatusEl.textContent = '';
1123
+ summaryStatusEl.className = 'oncall-status';
1124
+ const limit = readNonNegativeInt(summaryLimitInput, 50);
1125
+ const sinceHours = readNonNegativeInt(summarySinceInput, 24);
1126
+ if (limit == null || sinceHours == null) {
1127
+ summaryStatusEl.textContent = `✗ ${t('botDefaults.summaryNumberInvalid')}`;
1128
+ summaryStatusEl.classList.add('hint-warn-inline');
1129
+ return;
1130
+ }
1131
+ summarySaveBtn.disabled = true;
1132
+ try {
1133
+ const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/summary-range`, {
1134
+ method: 'PUT',
1135
+ headers: { 'content-type': 'application/json' },
1136
+ body: JSON.stringify({
1137
+ limit,
1138
+ sinceHours,
1139
+ }),
1140
+ });
1141
+ const body = await r.json().catch(() => ({}));
1142
+ if (r.ok && body.ok) {
1143
+ summaryStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1144
+ summaryStatusEl.classList.add('hint-ok');
1145
+ const next = body.summaryRange ?? { limit, sinceHours };
1146
+ summaryLimitInput.value = String(Number.isInteger(next.limit) && next.limit >= 0 ? next.limit : limit);
1147
+ summarySinceInput.value = String(Number.isInteger(next.sinceHours) && next.sinceHours >= 0 ? next.sinceHours : sinceHours);
1148
+ const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1149
+ if (cached)
1150
+ cached.summaryRange = next;
1151
+ }
1152
+ else {
1153
+ summaryStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1154
+ summaryStatusEl.classList.add('hint-warn-inline');
1155
+ }
1156
+ }
1157
+ catch (e) {
1158
+ summaryStatusEl.textContent = `✗ ${e?.message ?? e}`;
1159
+ summaryStatusEl.classList.add('hint-warn-inline');
1160
+ }
1161
+ finally {
1162
+ summarySaveBtn.disabled = false;
1163
+ }
1164
+ });
1165
+ }
892
1166
  // ── File sandbox toggle (auto-save on change) ─────────────────────────
893
1167
  const sandboxCb = card.querySelector('input[data-action=toggle-sandbox]');
894
1168
  const sandboxStatusEl = card.querySelector('[data-sandbox-status]');
@@ -1330,6 +1604,47 @@ export async function renderBotDefaultsPage(root) {
1330
1604
  }
1331
1605
  });
1332
1606
  }
1607
+ // ── 启动 shell launchShell(shell 名或绝对路径;空=清除→回 $SHELL) ──────
1608
+ const launchShellEl = card.querySelector('input[data-input=launchShell]');
1609
+ const launchShellSaveBtn = card.querySelector('button[data-action=save-launch-shell]');
1610
+ const launchShellStatusEl = card.querySelector('[data-launch-shell-status]');
1611
+ if (launchShellEl && launchShellSaveBtn) {
1612
+ launchShellSaveBtn.addEventListener('click', async () => {
1613
+ if (!launchShellStatusEl)
1614
+ return;
1615
+ launchShellStatusEl.textContent = '';
1616
+ launchShellStatusEl.className = 'oncall-status';
1617
+ launchShellSaveBtn.disabled = true;
1618
+ try {
1619
+ const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/launch-shell`, {
1620
+ method: 'PUT',
1621
+ headers: { 'content-type': 'application/json' },
1622
+ body: JSON.stringify({ launchShell: launchShellEl.value }),
1623
+ });
1624
+ const body = await r.json().catch(() => ({}));
1625
+ if (r.ok && body.ok) {
1626
+ launchShellStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1627
+ launchShellStatusEl.classList.add('hint-ok');
1628
+ const next = typeof body.launchShell === 'string' ? body.launchShell : '';
1629
+ launchShellEl.value = next;
1630
+ const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1631
+ if (cached)
1632
+ cached.launchShell = next;
1633
+ }
1634
+ else {
1635
+ launchShellStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1636
+ launchShellStatusEl.classList.add('hint-warn-inline');
1637
+ }
1638
+ }
1639
+ catch (e) {
1640
+ launchShellStatusEl.textContent = `✗ ${e?.message ?? e}`;
1641
+ launchShellStatusEl.classList.add('hint-warn-inline');
1642
+ }
1643
+ finally {
1644
+ launchShellSaveBtn.disabled = false;
1645
+ }
1646
+ });
1647
+ }
1333
1648
  // ── 环境变量 env(JSON 对象;空=清除) ──────────────────────────────
1334
1649
  const envEl = card.querySelector('textarea[data-input=env]');
1335
1650
  const envSaveBtn = card.querySelector('button[data-action=save-env]');