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
@@ -18,10 +18,11 @@ import * as observedBotsStore from '../services/observed-bots-store.js';
18
18
  import { getDeploymentIdentity } from '../services/deployment-identity.js';
19
19
  import * as grantPrefsStore from '../services/grant-prefs-store.js';
20
20
  import { findConfigField, applyConfigField, coerceConfigValue } from '../services/bot-config-store.js';
21
+ import { summaryRangeFromBotConfig, updateDashboardSummaryRange } from '../services/summary-range-store.js';
21
22
  import { config } from '../config.js';
22
23
  import { computeSandboxDiff, applySandboxDiff } from '../services/sandbox-land.js';
23
24
  import { buildSafeInsightConversation, buildSafeInsightOverview, buildSafeInsightReport, buildSafeInsightTurnDetail } from '../services/insight/report.js';
24
- import { readRawConfig, findEntryIndex, requireConfigPath } from '../services/config-store.js';
25
+ import { readRawConfig, findEntryIndex, requireConfigPath, rmwBotEntry } from '../services/config-store.js';
25
26
  import { setDefaultLocale, localeForBot, t } from '../i18n/index.js';
26
27
  import { isLocale } from '../i18n/types.js';
27
28
  import { readGlobalConfig } from '../global-config.js';
@@ -44,6 +45,7 @@ import { deleteRoleProfileEntry, deleteRoleProfileIfEmpty, isValidRoleProfileId,
44
45
  import { triggerSessionTurn } from './trigger-session.js';
45
46
  import { triggerWorkflowFromEnvelope } from '../workflows/trigger-from-envelope.js';
46
47
  import { validateTriggerRequest } from '../services/trigger-types.js';
48
+ import { resolveCliSelection, selectionKeyForBot } from '../setup/cli-selection.js';
47
49
  // Workflow runner is wired by the daemon (it owns the heavy triggerWorkflowRun
48
50
  // deps). Until set, workflow-targeted triggers report not-implemented.
49
51
  let workflowRunner = null;
@@ -209,6 +211,46 @@ ipcRoute('POST', '/api/sessions/:sessionId/restart', (_req, res, params) => {
209
211
  function findSessionRecord(sessionId) {
210
212
  return findActiveBySessionId(sessionId)?.session ?? sessionStore.getSession(sessionId);
211
213
  }
214
+ function buildAsyncTriggerLookupResponse(sessionId, triggerId) {
215
+ const ds = findActiveBySessionId(sessionId);
216
+ if (!ds) {
217
+ return { ok: false, errorCode: 'session_not_found', error: `active session not found: ${sessionId}` };
218
+ }
219
+ const resolvedTriggerId = triggerId || ds.latestAsyncTriggerId;
220
+ if (!resolvedTriggerId) {
221
+ return { ok: false, errorCode: 'bad_request', error: 'no async trigger recorded for this session' };
222
+ }
223
+ const state = ds.asyncTriggerResults?.get(resolvedTriggerId);
224
+ if (!state) {
225
+ return { ok: false, errorCode: 'bad_request', error: `async trigger not found for session: ${resolvedTriggerId}` };
226
+ }
227
+ if (state.status === 'completed') {
228
+ return {
229
+ ok: true,
230
+ triggerId: resolvedTriggerId,
231
+ action: 'completed',
232
+ target: { kind: 'turn', sessionId, chatId: ds.chatId },
233
+ output: state.content ? { content: state.content } : undefined,
234
+ async: {
235
+ status: 'completed',
236
+ sessionId,
237
+ completedAt: state.completedAt ? new Date(state.completedAt).toISOString() : undefined,
238
+ },
239
+ message: 'async trigger completed',
240
+ };
241
+ }
242
+ return {
243
+ ok: true,
244
+ triggerId: resolvedTriggerId,
245
+ action: 'queued',
246
+ target: { kind: 'turn', sessionId, chatId: ds.chatId },
247
+ async: {
248
+ status: 'pending',
249
+ sessionId,
250
+ },
251
+ message: 'async trigger pending',
252
+ };
253
+ }
212
254
  // 看板放置:dashboard 看板视图拖拽卡片后持久化列 + 列内排序位置。
213
255
  // 改完广播 session.update,所有打开的 dashboard 实时同步。
214
256
  ipcRoute('POST', '/api/sessions/:sessionId/board', async (req, res, params) => {
@@ -352,6 +394,17 @@ ipcRoute('GET', '/api/sessions/:sessionId/history', async (req, res, params) =>
352
394
  jsonRes(res, 502, { ok: false, error: String(err?.message ?? err) });
353
395
  }
354
396
  });
397
+ ipcRoute('GET', '/api/sessions/:sessionId/trigger-result', (req, res, params) => {
398
+ const url = new URL(req.url ?? '/', 'http://localhost');
399
+ const triggerId = url.searchParams.get('triggerId') ?? undefined;
400
+ const result = buildAsyncTriggerLookupResponse(params.sessionId, triggerId);
401
+ const status = result.ok
402
+ ? 200
403
+ : result.errorCode === 'session_not_found'
404
+ ? 404
405
+ : 400;
406
+ jsonRes(res, status, result);
407
+ });
355
408
  // 会话 insight:只读解析本会话的 transcript,产出动作 span / 失败聚合 / 规则建议
356
409
  // (SafeInsightReport)。底层 services/insight 已做 fail-closed 脱敏投影——raw 命令
357
410
  // 与输出永不进结构。detail=summary 只返聚合+建议(/insight 卡片、抽屉概览用);
@@ -487,6 +540,33 @@ ipcRoute('POST', '/api/sessions/:sessionId/rename', async (req, res, params) =>
487
540
  });
488
541
  jsonRes(res, 200, { ok: true, title });
489
542
  });
543
+ // 会话锁定:保护被锁定会话不被 dashboard「清理空闲」批量关闭。锁定是会话元数据,
544
+ // 不影响用户显式点击关闭/批量关闭,避免把会话变成不可管理状态。
545
+ ipcRoute('POST', '/api/sessions/:sessionId/lock', async (req, res, params) => {
546
+ let body;
547
+ try {
548
+ body = await readJsonBody(req);
549
+ }
550
+ catch {
551
+ return jsonRes(res, 400, { ok: false, error: 'bad_json' });
552
+ }
553
+ if (typeof body.locked !== 'boolean')
554
+ return jsonRes(res, 400, { ok: false, error: 'bad_locked' });
555
+ const session = findSessionRecord(params.sessionId);
556
+ if (!session)
557
+ return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
558
+ if (body.locked)
559
+ session.locked = true;
560
+ else
561
+ delete session.locked;
562
+ sessionStore.updateSession(session);
563
+ const locked = !!session.locked;
564
+ dashboardEventBus.publish({
565
+ type: 'session.update',
566
+ body: { sessionId: params.sessionId, patch: { locked } },
567
+ });
568
+ jsonRes(res, 200, { ok: true, locked });
569
+ });
490
570
  /**
491
571
  * Mint the WRITABLE web-terminal link for a live session — the dashboard
492
572
  * counterpart to the Lark card's "🔑 获取操作链接" button. Returns the URL with
@@ -863,9 +943,11 @@ ipcRoute('POST', '/api/trigger', async (req, res) => {
863
943
  ? 403
864
944
  : result.errorCode === 'session_not_found'
865
945
  ? 404
866
- : result.errorCode === 'target_required' || result.errorCode === 'bad_request'
867
- ? 400
868
- : 500;
946
+ : result.errorCode === 'wait_timeout'
947
+ ? 504
948
+ : result.errorCode === 'target_required' || result.errorCode === 'bad_request'
949
+ ? 400
950
+ : 500;
869
951
  return jsonRes(res, status, result);
870
952
  }
871
953
  catch (e) {
@@ -1180,6 +1262,18 @@ ipcRoute('GET', '/api/bot-default-oncall', async (_req, res) => {
1180
1262
  p2pMode = 'chat';
1181
1263
  }
1182
1264
  catch { /* default thread */ }
1265
+ let cliId = '';
1266
+ let wrapperCli = null;
1267
+ let model = null;
1268
+ let agentSelectionKey = '';
1269
+ try {
1270
+ const cfg = getBot(cachedLarkAppId).config;
1271
+ cliId = cfg.cliId;
1272
+ wrapperCli = typeof cfg.wrapperCli === 'string' && cfg.wrapperCli.trim() ? cfg.wrapperCli : null;
1273
+ model = typeof cfg.model === 'string' && cfg.model.trim() ? cfg.model : null;
1274
+ agentSelectionKey = selectionKeyForBot(cliId, wrapperCli ?? undefined);
1275
+ }
1276
+ catch { /* no registered bot */ }
1183
1277
  let maxLiveWorkers = null;
1184
1278
  try {
1185
1279
  const m = getBot(cachedLarkAppId).config.maxLiveWorkers;
@@ -1205,10 +1299,25 @@ ipcRoute('GET', '/api/bot-default-oncall', async (_req, res) => {
1205
1299
  env = JSON.stringify(e, null, 2);
1206
1300
  }
1207
1301
  catch { /* none */ }
1302
+ // defaultWorkingDir — the "仅默认目录" mode source. Mutually exclusive with
1303
+ // defaultOncall in the dashboard 3-way selector; the frontend derives the
1304
+ // current mode from (defaultOncall.enabled ? oncall : defaultWorkingDir ? default : off).
1305
+ let defaultWorkingDir = null;
1306
+ try {
1307
+ const d = getBot(cachedLarkAppId).config.defaultWorkingDir;
1308
+ if (typeof d === 'string' && d.trim())
1309
+ defaultWorkingDir = d;
1310
+ }
1311
+ catch { /* none */ }
1208
1312
  jsonRes(res, 200, {
1209
1313
  larkAppId: cachedLarkAppId,
1210
1314
  botName: getBotName(),
1315
+ cliId,
1316
+ wrapperCli,
1317
+ model,
1318
+ agentSelectionKey,
1211
1319
  defaultOncall: defaultOncall ?? { enabled: false, workingDir: '', since: 0 },
1320
+ defaultWorkingDir,
1212
1321
  autoboundChatCount: autoboundChats.length,
1213
1322
  brandLabel: brandStore.getBotBrandLabel(cachedLarkAppId) ?? null,
1214
1323
  sandbox: sandboxStore.getBotSandbox(cachedLarkAppId),
@@ -1229,7 +1338,9 @@ ipcRoute('GET', '/api/bot-default-oncall', async (_req, res) => {
1229
1338
  p2pMode,
1230
1339
  maxLiveWorkers,
1231
1340
  startupCommands,
1341
+ launchShell: getBot(cachedLarkAppId).config.launchShell ?? '',
1232
1342
  env,
1343
+ summaryRange: summaryRangeFromBotConfig(getBot(cachedLarkAppId).config),
1233
1344
  skills: getBot(cachedLarkAppId).config.skills ?? null,
1234
1345
  });
1235
1346
  });
@@ -1267,7 +1378,7 @@ ipcRoute('PUT', '/api/bot-card-prefs', async (req, res) => {
1267
1378
  if (m)
1268
1379
  patch.regularGroupReplyMode = m;
1269
1380
  }
1270
- if (body.regularGroupMentionMode === 'always' || body.regularGroupMentionMode === 'topic' || body.regularGroupMentionMode === 'never') {
1381
+ if (body.regularGroupMentionMode === 'always' || body.regularGroupMentionMode === 'topic' || body.regularGroupMentionMode === 'never' || body.regularGroupMentionMode === 'ambient') {
1271
1382
  patch.regularGroupMentionMode = body.regularGroupMentionMode;
1272
1383
  }
1273
1384
  if (body.docSubscribeDefaultMode === 'mention-only' || body.docSubscribeDefaultMode === 'all') {
@@ -1280,6 +1391,41 @@ ipcRoute('PUT', '/api/bot-card-prefs', async (req, res) => {
1280
1391
  return jsonRes(res, 400, { ok: false, error: r.reason });
1281
1392
  jsonRes(res, 200, { ok: true, ...r.prefs });
1282
1393
  });
1394
+ // Per-bot explicit `/summary` history range. Body `{ limit, sinceHours }`.
1395
+ ipcRoute('PUT', '/api/bot-summary-range', async (req, res) => {
1396
+ if (!cachedLarkAppId)
1397
+ return jsonRes(res, 503, { error: 'larkAppId_not_set' });
1398
+ let raw;
1399
+ try {
1400
+ raw = await readJsonBody(req);
1401
+ }
1402
+ catch {
1403
+ return jsonRes(res, 400, { ok: false, error: 'bad_json' });
1404
+ }
1405
+ const r = await updateDashboardSummaryRange(cachedLarkAppId, raw);
1406
+ if (!r.ok)
1407
+ return jsonRes(res, 400, { ok: false, error: r.reason });
1408
+ jsonRes(res, 200, { ok: true, summaryRange: r.summaryRange });
1409
+ });
1410
+ // Backward-compatible dashboard endpoint from the short-lived keyword-trigger UI.
1411
+ ipcRoute('PUT', '/api/bot-summary-trigger', async (req, res) => {
1412
+ if (!cachedLarkAppId)
1413
+ return jsonRes(res, 503, { error: 'larkAppId_not_set' });
1414
+ let raw;
1415
+ try {
1416
+ raw = await readJsonBody(req);
1417
+ }
1418
+ catch {
1419
+ return jsonRes(res, 400, { ok: false, error: 'bad_json' });
1420
+ }
1421
+ const body = raw && typeof raw === 'object' && !Array.isArray(raw)
1422
+ ? { limit: raw.limit, sinceHours: raw.sinceHours }
1423
+ : raw;
1424
+ const r = await updateDashboardSummaryRange(cachedLarkAppId, body);
1425
+ if (!r.ok)
1426
+ return jsonRes(res, 400, { ok: false, error: r.reason });
1427
+ jsonRes(res, 200, { ok: true, summaryRange: r.summaryRange });
1428
+ });
1283
1429
  // Per-bot 授权偏好。Body 任意子集:
1284
1430
  // • restrictGrantCommands: boolean — 限制被授权人只能纯对话
1285
1431
  // • autoGrantRequestCards: boolean — 未授权 @ 被挡住时是否发 grant 申请卡
@@ -1335,6 +1481,60 @@ ipcRoute('PUT', '/api/bot-brand-label', async (req, res) => {
1335
1481
  return jsonRes(res, 400, { ok: false, error: r.reason });
1336
1482
  jsonRes(res, 200, { ok: true, brandLabel: r.brandLabel });
1337
1483
  });
1484
+ // Per-bot agent launch settings. Body `{ cliId, model }` where `cliId` is the
1485
+ // dashboard selection key (plain adapter id or a wrapper option such as
1486
+ // `ttadk-x-codex`). Changes affect the next spawned CLI session.
1487
+ ipcRoute('PUT', '/api/bot-agent', async (req, res) => {
1488
+ if (!cachedLarkAppId)
1489
+ return jsonRes(res, 503, { error: 'larkAppId_not_set' });
1490
+ let body;
1491
+ try {
1492
+ body = await readJsonBody(req);
1493
+ }
1494
+ catch {
1495
+ return jsonRes(res, 400, { ok: false, error: 'bad_json' });
1496
+ }
1497
+ const key = typeof body.cliId === 'string' && body.cliId.trim() ? body.cliId.trim() : '';
1498
+ if (!key)
1499
+ return jsonRes(res, 400, { ok: false, error: 'cli_required' });
1500
+ let selected;
1501
+ try {
1502
+ selected = resolveCliSelection(key);
1503
+ }
1504
+ catch (err) {
1505
+ return jsonRes(res, 400, { ok: false, error: 'invalid_cli', message: err?.message ?? String(err) });
1506
+ }
1507
+ const model = typeof body.model === 'string' ? body.model.trim() : '';
1508
+ const r = await rmwBotEntry(cachedLarkAppId, (entry) => {
1509
+ entry.cliId = selected.cliId;
1510
+ if (selected.wrapperCli)
1511
+ entry.wrapperCli = selected.wrapperCli;
1512
+ else
1513
+ delete entry.wrapperCli;
1514
+ if (model)
1515
+ entry.model = model;
1516
+ else
1517
+ delete entry.model;
1518
+ return { write: true, result: null };
1519
+ });
1520
+ if (!r.ok)
1521
+ return jsonRes(res, 400, { ok: false, error: r.reason });
1522
+ const bot = getBot(cachedLarkAppId);
1523
+ bot.config.cliId = selected.cliId;
1524
+ if (selected.wrapperCli)
1525
+ bot.config.wrapperCli = selected.wrapperCli;
1526
+ else
1527
+ bot.config.wrapperCli = undefined;
1528
+ bot.config.model = model || undefined;
1529
+ const selectionKey = selectionKeyForBot(selected.cliId, selected.wrapperCli);
1530
+ jsonRes(res, 200, {
1531
+ ok: true,
1532
+ cliId: selected.cliId,
1533
+ wrapperCli: selected.wrapperCli ?? null,
1534
+ model: model || null,
1535
+ selectionKey,
1536
+ });
1537
+ });
1338
1538
  // Per-bot 私聊单聊模式 p2pMode。Body `{ p2pMode: 'chat' | 'thread' }`:
1339
1539
  // • 'chat' → 私聊走扁平连续 chat-scope 会话
1340
1540
  // • 'thread'(默认) → 清回每条 DM 独立 thread-scope 会话
@@ -1392,6 +1592,38 @@ ipcRoute('PUT', '/api/bot-startup-commands', async (req, res) => {
1392
1592
  return jsonRes(res, 400, { ok: false, error: r.reason });
1393
1593
  jsonRes(res, 200, { ok: true, startupCommands: (value ?? []).join('\n') });
1394
1594
  });
1595
+ // Per-bot launch-shell override launchShell。Body `{ launchShell: string }`:
1596
+ // 空字符串=清除(回 $SHELL)。走 applyConfigField(与 /config launchShell 同一写盘
1597
+ // + 内存热更新路径),next-session 生效(下个会话起用新 shell 启动 CLI)。
1598
+ ipcRoute('PUT', '/api/bot-launch-shell', async (req, res) => {
1599
+ if (!cachedLarkAppId)
1600
+ return jsonRes(res, 503, { error: 'larkAppId_not_set' });
1601
+ let body;
1602
+ try {
1603
+ body = await readJsonBody(req);
1604
+ }
1605
+ catch {
1606
+ return jsonRes(res, 400, { ok: false, error: 'bad_json' });
1607
+ }
1608
+ const spec = findConfigField('launchShell');
1609
+ if (!spec)
1610
+ return jsonRes(res, 500, { ok: false, error: 'spec_missing' });
1611
+ const raw = typeof body.launchShell === 'string' ? body.launchShell : '';
1612
+ let value;
1613
+ if (!raw.trim()) {
1614
+ value = null; // 清除 → 回 $SHELL
1615
+ }
1616
+ else {
1617
+ const coerced = coerceConfigValue(spec, raw);
1618
+ if (!coerced.ok)
1619
+ return jsonRes(res, 400, { ok: false, error: coerced.reason });
1620
+ value = coerced.value;
1621
+ }
1622
+ const r = await applyConfigField(cachedLarkAppId, spec, value);
1623
+ if (!r.ok)
1624
+ return jsonRes(res, 400, { ok: false, error: r.reason });
1625
+ jsonRes(res, 200, { ok: true, launchShell: value ?? '' });
1626
+ });
1395
1627
  // Per-bot 环境变量 env。Body `{ env: string }`(原始 JSON 文本,如
1396
1628
  // `{"ANTHROPIC_BASE_URL":"…","ANTHROPIC_AUTH_TOKEN":"…"}` 让本 bot 走 GLM/第三方
1397
1629
  // 服务商):空白 → 清除;否则按 json kind 解析 + sanitizePerBotEnv 过滤后落盘。
@@ -1605,6 +1837,51 @@ ipcRoute('PUT', '/api/bot-default-oncall', async (req, res) => {
1605
1837
  return jsonRes(res, 400, r);
1606
1838
  jsonRes(res, 200, { ok: true, defaultOncall: r.defaultOncall, resolvedPath: resolvedPath || undefined });
1607
1839
  });
1840
+ // Per-bot「默认工作目录模式」三选一(dashboard 单选;两个底层字段互斥):
1841
+ // • off → 清 defaultWorkingDir + 关 defaultOncall(新会话弹「选仓库」卡)
1842
+ // • default → 写 defaultWorkingDir + 关 defaultOncall(钉目录、跳过选仓库、不改权限)
1843
+ // • oncall → 开 defaultOncall(+dir) + 清 defaultWorkingDir(新群自动绑+开放对话;
1844
+ // 该目录经 resolveBotDefaultWorkingDir 的 layer-4 兜底覆盖该 bot 所有会话)
1845
+ // 两字段在 oncallStore.setWorkingDirMode 的**同一个 rmwBotEntry 锁内**一次性原子写盘 +
1846
+ // 同步内存:否则两个并发请求分别加锁写各自字段会交错,最终留下 defaultOncall.enabled 与
1847
+ // defaultWorkingDir 同时存在的不一致态(GET/前端按 enabled 显示 oncall,但 runtime 的
1848
+ // effectiveDefaultWorkingDir 优先用 defaultWorkingDir → UI 与实际目录背离;PR #311 Codex 评审)。
1849
+ // next-session 生效(运行中会话需 /restart)。
1850
+ ipcRoute('PUT', '/api/bot-working-dir-mode', async (req, res) => {
1851
+ if (!cachedLarkAppId)
1852
+ return jsonRes(res, 503, { error: 'larkAppId_not_set' });
1853
+ let body;
1854
+ try {
1855
+ body = await readJsonBody(req);
1856
+ }
1857
+ catch {
1858
+ return jsonRes(res, 400, { ok: false, error: 'bad_json' });
1859
+ }
1860
+ const mode = body.mode;
1861
+ if (mode !== 'off' && mode !== 'default' && mode !== 'oncall') {
1862
+ return jsonRes(res, 400, { ok: false, error: 'invalid_mode' });
1863
+ }
1864
+ const workingDir = typeof body.workingDir === 'string' ? body.workingDir.trim() : '';
1865
+ // 非「关闭」模式必须给一个真实存在的目录。
1866
+ let resolvedPath = '';
1867
+ if (mode !== 'off') {
1868
+ if (!workingDir)
1869
+ return jsonRes(res, 400, { ok: false, error: 'workingDir_required' });
1870
+ const v = validateWorkingDir(workingDir);
1871
+ if (!v.ok)
1872
+ return jsonRes(res, 400, { ok: false, error: v.error });
1873
+ resolvedPath = v.resolvedPath;
1874
+ }
1875
+ const r = await oncallStore.setWorkingDirMode(cachedLarkAppId, mode, workingDir);
1876
+ if (!r.ok)
1877
+ return jsonRes(res, 400, r);
1878
+ return jsonRes(res, 200, {
1879
+ ok: true, mode,
1880
+ defaultWorkingDir: r.defaultWorkingDir,
1881
+ defaultOncall: r.defaultOncall,
1882
+ resolvedPath: resolvedPath || undefined,
1883
+ });
1884
+ });
1608
1885
  // Create a brand-new chat with this bot as creator/owner and `larkAppIds` as
1609
1886
  // initial bot members. The dashboard's public route picks any online daemon
1610
1887
  // to act as creator, then forwards here.