botmux 2.85.1 → 2.86.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 (116) hide show
  1. package/dist/core/command-handler.d.ts.map +1 -1
  2. package/dist/core/command-handler.js +209 -1
  3. package/dist/core/command-handler.js.map +1 -1
  4. package/dist/core/cost-calculator.d.ts.map +1 -1
  5. package/dist/core/cost-calculator.js +7 -106
  6. package/dist/core/cost-calculator.js.map +1 -1
  7. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  8. package/dist/core/dashboard-ipc-server.js +240 -2
  9. package/dist/core/dashboard-ipc-server.js.map +1 -1
  10. package/dist/core/passthrough-commands.d.ts.map +1 -1
  11. package/dist/core/passthrough-commands.js +1 -1
  12. package/dist/core/passthrough-commands.js.map +1 -1
  13. package/dist/core/role-resolver.d.ts +1 -0
  14. package/dist/core/role-resolver.d.ts.map +1 -1
  15. package/dist/core/role-resolver.js +14 -0
  16. package/dist/core/role-resolver.js.map +1 -1
  17. package/dist/dashboard/web/app.d.ts.map +1 -1
  18. package/dist/dashboard/web/app.js +15 -4
  19. package/dist/dashboard/web/app.js.map +1 -1
  20. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  21. package/dist/dashboard/web/bot-defaults.js +116 -0
  22. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  23. package/dist/dashboard/web/groups.d.ts +2 -0
  24. package/dist/dashboard/web/groups.d.ts.map +1 -1
  25. package/dist/dashboard/web/groups.js +419 -3
  26. package/dist/dashboard/web/groups.js.map +1 -1
  27. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  28. package/dist/dashboard/web/i18n.js +617 -3
  29. package/dist/dashboard/web/i18n.js.map +1 -1
  30. package/dist/dashboard/web/insights.d.ts +2 -0
  31. package/dist/dashboard/web/insights.d.ts.map +1 -0
  32. package/dist/dashboard/web/insights.js +1523 -0
  33. package/dist/dashboard/web/insights.js.map +1 -0
  34. package/dist/dashboard/web/role-profile-match.d.ts +31 -0
  35. package/dist/dashboard/web/role-profile-match.d.ts.map +1 -0
  36. package/dist/dashboard/web/role-profile-match.js +58 -0
  37. package/dist/dashboard/web/role-profile-match.js.map +1 -0
  38. package/dist/dashboard/web/roles.d.ts +1 -0
  39. package/dist/dashboard/web/roles.d.ts.map +1 -1
  40. package/dist/dashboard/web/roles.js +520 -27
  41. package/dist/dashboard/web/roles.js.map +1 -1
  42. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  43. package/dist/dashboard/web/sessions.js +84 -0
  44. package/dist/dashboard/web/sessions.js.map +1 -1
  45. package/dist/dashboard-web/app.js +1243 -831
  46. package/dist/dashboard-web/index.html +2 -1
  47. package/dist/dashboard-web/style.css +1085 -3
  48. package/dist/dashboard.js +215 -3
  49. package/dist/dashboard.js.map +1 -1
  50. package/dist/i18n/en.d.ts.map +1 -1
  51. package/dist/i18n/en.js +34 -1
  52. package/dist/i18n/en.js.map +1 -1
  53. package/dist/i18n/zh.d.ts.map +1 -1
  54. package/dist/i18n/zh.js +34 -1
  55. package/dist/i18n/zh.js.map +1 -1
  56. package/dist/services/group-creator.d.ts +6 -0
  57. package/dist/services/group-creator.d.ts.map +1 -1
  58. package/dist/services/group-creator.js +54 -5
  59. package/dist/services/group-creator.js.map +1 -1
  60. package/dist/services/insight/antigravity-span-reader.d.ts +3 -0
  61. package/dist/services/insight/antigravity-span-reader.d.ts.map +1 -0
  62. package/dist/services/insight/antigravity-span-reader.js +249 -0
  63. package/dist/services/insight/antigravity-span-reader.js.map +1 -0
  64. package/dist/services/insight/classify.d.ts +7 -0
  65. package/dist/services/insight/classify.d.ts.map +1 -0
  66. package/dist/services/insight/classify.js +46 -0
  67. package/dist/services/insight/classify.js.map +1 -0
  68. package/dist/services/insight/claude-span-reader.d.ts +3 -0
  69. package/dist/services/insight/claude-span-reader.d.ts.map +1 -0
  70. package/dist/services/insight/claude-span-reader.js +257 -0
  71. package/dist/services/insight/claude-span-reader.js.map +1 -0
  72. package/dist/services/insight/codex-span-reader.d.ts +3 -0
  73. package/dist/services/insight/codex-span-reader.d.ts.map +1 -0
  74. package/dist/services/insight/codex-span-reader.js +290 -0
  75. package/dist/services/insight/codex-span-reader.js.map +1 -0
  76. package/dist/services/insight/intent.d.ts +5 -0
  77. package/dist/services/insight/intent.d.ts.map +1 -0
  78. package/dist/services/insight/intent.js +145 -0
  79. package/dist/services/insight/intent.js.map +1 -0
  80. package/dist/services/insight/jsonl.d.ts +10 -0
  81. package/dist/services/insight/jsonl.d.ts.map +1 -0
  82. package/dist/services/insight/jsonl.js +36 -0
  83. package/dist/services/insight/jsonl.js.map +1 -0
  84. package/dist/services/insight/prompt.d.ts +3 -0
  85. package/dist/services/insight/prompt.d.ts.map +1 -0
  86. package/dist/services/insight/prompt.js +99 -0
  87. package/dist/services/insight/prompt.js.map +1 -0
  88. package/dist/services/insight/redact.d.ts +4 -0
  89. package/dist/services/insight/redact.d.ts.map +1 -0
  90. package/dist/services/insight/redact.js +67 -0
  91. package/dist/services/insight/redact.js.map +1 -0
  92. package/dist/services/insight/report.d.ts +29 -0
  93. package/dist/services/insight/report.d.ts.map +1 -0
  94. package/dist/services/insight/report.js +1126 -0
  95. package/dist/services/insight/report.js.map +1 -0
  96. package/dist/services/insight/safe-detail.d.ts +5 -0
  97. package/dist/services/insight/safe-detail.d.ts.map +1 -0
  98. package/dist/services/insight/safe-detail.js +59 -0
  99. package/dist/services/insight/safe-detail.js.map +1 -0
  100. package/dist/services/insight/scrub.d.ts +22 -0
  101. package/dist/services/insight/scrub.d.ts.map +1 -0
  102. package/dist/services/insight/scrub.js +70 -0
  103. package/dist/services/insight/scrub.js.map +1 -0
  104. package/dist/services/insight/types.d.ts +394 -0
  105. package/dist/services/insight/types.d.ts.map +1 -0
  106. package/dist/services/insight/types.js +2 -0
  107. package/dist/services/insight/types.js.map +1 -0
  108. package/dist/services/role-profile-store.d.ts +25 -0
  109. package/dist/services/role-profile-store.d.ts.map +1 -0
  110. package/dist/services/role-profile-store.js +171 -0
  111. package/dist/services/role-profile-store.js.map +1 -0
  112. package/dist/services/transcript-resolver.d.ts +26 -0
  113. package/dist/services/transcript-resolver.d.ts.map +1 -0
  114. package/dist/services/transcript-resolver.js +111 -0
  115. package/dist/services/transcript-resolver.js.map +1 -0
  116. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"command-handler.d.ts","sourceRoot":"","sources":["../../src/core/command-handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"command-handler.d.ts","sourceRoot":"","sources":["../../src/core/command-handler.ts"],"names":[],"mappings":"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"}
@@ -40,9 +40,11 @@ import { ttadkConfigModelChoices } from '../setup/cli-selection.js';
40
40
  import { publishAttentionPatch, announcePendingRepoSession } from './session-activity.js';
41
41
  import { setCardMode } from '../services/card-mode-store.js';
42
42
  import { canOperate } from '../im/lark/event-dispatcher.js';
43
+ import { buildSafeInsightReport } from '../services/insight/report.js';
43
44
  import { invalidWorkingDirs } from '../utils/working-dir.js';
44
- import { writeRoleFile, deleteRoleFile, resolveRole, resolveTeamRoleFile, writeTeamRoleFile, deleteTeamRoleFile } from './role-resolver.js';
45
+ import { writeRoleFile, deleteRoleFile, resolveRole, resolveRoleFile, resolveTeamRoleFile, writeTeamRoleFile, deleteTeamRoleFile } from './role-resolver.js';
45
46
  import { getBotCapability, setBotCapability, clearBotCapability } from '../services/bot-profile-store.js';
47
+ import { deleteRoleProfileEntry, deleteRoleProfileIfEmpty, isValidRoleProfileId, listRoleProfileEntries, listRoleProfiles, MAX_ROLE_PROFILE_ENTRY_BYTES, readRoleProfileEntry, writeRoleProfileEntry, } from '../services/role-profile-store.js';
46
48
  import { sessionKey, sessionAnchorId } from './types.js';
47
49
  import { t, localeForBot } from '../i18n/index.js';
48
50
  import { runSkillsImCommand } from './skills/im-command.js';
@@ -292,6 +294,139 @@ async function handleRoleCommand(args, rootId, chatId, larkAppId, senderId, deps
292
294
  const trimmed = args.trim();
293
295
  const loc = localeForBot(larkAppId);
294
296
  const dataDir = config.session.dataDir;
297
+ // /role profile [...] — reusable suites of per-bot chat roles. Profiles are
298
+ // not a runtime role layer; applying one materializes this bot's entry into
299
+ // the current chat role.
300
+ const profileMatch = trimmed.match(/^profile\b([\s\S]*)$/);
301
+ if (profileMatch) {
302
+ const profileArgs = profileMatch[1].trim();
303
+ const subMatch = profileArgs.match(/^(\S+)(?:\s+([\s\S]*))?$/);
304
+ const sub = (subMatch?.[1] ?? '').toLowerCase();
305
+ const subBody = subMatch?.[2]?.trim() ?? '';
306
+ if (!sub || sub === 'help') {
307
+ await sessionReply(rootId, t('role.profile.help', undefined, loc));
308
+ return;
309
+ }
310
+ if (sub === 'list' || sub === 'ls') {
311
+ const profiles = listRoleProfiles(dataDir);
312
+ if (profiles.length === 0) {
313
+ await sessionReply(rootId, t('role.profile.list_empty', undefined, loc));
314
+ return;
315
+ }
316
+ const lines = profiles.map(p => {
317
+ const hasEntry = readRoleProfileEntry(dataDir, p.profileId, larkAppId) !== null;
318
+ const status = hasEntry
319
+ ? t('role.profile.current_configured', undefined, loc)
320
+ : t('role.profile.current_missing', undefined, loc);
321
+ return `• ${p.profileId} — ${p.entryCount} ${t('role.profile.entries', undefined, loc)}; ${status}`;
322
+ });
323
+ await sessionReply(rootId, `${t('role.profile.list_header', undefined, loc)}\n${lines.join('\n')}`);
324
+ return;
325
+ }
326
+ const [profileId = '', ...afterProfile] = subBody.split(/\s+/);
327
+ if (!profileId || !isValidRoleProfileId(profileId)) {
328
+ await sessionReply(rootId, t('role.profile.invalid', undefined, loc));
329
+ return;
330
+ }
331
+ if (sub === 'show') {
332
+ const showAll = afterProfile.includes('--all');
333
+ if (showAll) {
334
+ const entries = listRoleProfileEntries(dataDir, profileId);
335
+ if (entries.length === 0) {
336
+ await sessionReply(rootId, t('role.profile.no_entries', { profile: profileId }, loc));
337
+ return;
338
+ }
339
+ const body = entries.map(entry => `### ${entry.larkAppId}\n${t('role.byte_count', { bytes: entry.byteLength, max: MAX_ROLE_PROFILE_ENTRY_BYTES }, loc)}\n\`\`\`markdown\n${entry.content}\n\`\`\``).join('\n\n');
340
+ await sessionReply(rootId, `${t('role.profile.show_all_header', { profile: profileId }, loc)}\n${body}`);
341
+ return;
342
+ }
343
+ const content = readRoleProfileEntry(dataDir, profileId, larkAppId);
344
+ if (content === null) {
345
+ await sessionReply(rootId, t('role.profile.entry_empty', { profile: profileId }, loc));
346
+ return;
347
+ }
348
+ await sessionReply(rootId, `${t('role.profile.entry_current', { profile: profileId }, loc)}\n\`\`\`markdown\n${content}\n\`\`\`\n${t('role.byte_count', { bytes: Buffer.byteLength(content, 'utf-8'), max: MAX_ROLE_PROFILE_ENTRY_BYTES }, loc)}`);
349
+ return;
350
+ }
351
+ if (sub === 'set') {
352
+ const content = subBody.slice(profileId.length).trim();
353
+ if (!content) {
354
+ await sessionReply(rootId, t('role.profile.set_empty', undefined, loc));
355
+ return;
356
+ }
357
+ writeRoleProfileEntry(dataDir, profileId, larkAppId, content);
358
+ await sessionReply(rootId, t('role.profile.entry_saved', {
359
+ profile: profileId,
360
+ bytes: Math.min(Buffer.byteLength(content.trim(), 'utf-8'), MAX_ROLE_PROFILE_ENTRY_BYTES),
361
+ max: MAX_ROLE_PROFILE_ENTRY_BYTES,
362
+ }, loc));
363
+ return;
364
+ }
365
+ if (sub === 'save') {
366
+ const { content, source } = resolveRole(larkAppId, chatId);
367
+ if (!content) {
368
+ await sessionReply(rootId, t('role.profile.save_no_effective', { profile: profileId }, loc));
369
+ return;
370
+ }
371
+ writeRoleProfileEntry(dataDir, profileId, larkAppId, content);
372
+ await sessionReply(rootId, t('role.profile.saved_effective', {
373
+ profile: profileId,
374
+ source,
375
+ bytes: Buffer.byteLength(content, 'utf-8'),
376
+ max: MAX_ROLE_PROFILE_ENTRY_BYTES,
377
+ }, loc));
378
+ return;
379
+ }
380
+ if (sub === 'delete' || sub === 'del' || sub === 'rm' || sub === '删除') {
381
+ const existed = deleteRoleProfileEntry(dataDir, profileId, larkAppId);
382
+ deleteRoleProfileIfEmpty(dataDir, profileId);
383
+ await sessionReply(rootId, existed
384
+ ? t('role.profile.entry_deleted', { profile: profileId }, loc)
385
+ : t('role.profile.entry_nothing', { profile: profileId }, loc));
386
+ return;
387
+ }
388
+ if (sub === 'apply') {
389
+ const flags = new Set(afterProfile);
390
+ const preview = flags.has('--preview');
391
+ const force = flags.has('--force');
392
+ const quiet = flags.has('--quiet');
393
+ const content = readRoleProfileEntry(dataDir, profileId, larkAppId);
394
+ if (content === null) {
395
+ await sessionReply(rootId, t('role.profile.apply_missing', { profile: profileId }, loc));
396
+ return;
397
+ }
398
+ const existing = resolveRoleFile(larkAppId, chatId);
399
+ const bytes = Buffer.byteLength(content, 'utf-8');
400
+ if (preview) {
401
+ const overwriteLine = existing && !force
402
+ ? `\n${t('role.profile.apply_would_refuse', undefined, loc)}`
403
+ : '';
404
+ await sessionReply(rootId, `${t('role.profile.apply_preview', { profile: profileId, bytes, max: MAX_ROLE_PROFILE_ENTRY_BYTES }, loc)}${overwriteLine}\n\`\`\`markdown\n${content}\n\`\`\``);
405
+ return;
406
+ }
407
+ if (existing && !force) {
408
+ // An empty entry would *clear* the chat role, not overwrite it — phrase
409
+ // the --force refusal accordingly so the intent is not misread.
410
+ const refusedKey = content ? 'role.profile.apply_refused' : 'role.profile.apply_refused_clear';
411
+ await sessionReply(rootId, t(refusedKey, { profile: profileId }, loc));
412
+ return;
413
+ }
414
+ if (!content) {
415
+ deleteRoleFile(larkAppId, chatId);
416
+ if (!quiet) {
417
+ await sessionReply(rootId, t('role.profile.applied', { profile: profileId, bytes, max: MAX_ROLE_PROFILE_ENTRY_BYTES }, loc));
418
+ }
419
+ return;
420
+ }
421
+ writeRoleFile(larkAppId, chatId, content);
422
+ if (!quiet) {
423
+ await sessionReply(rootId, t('role.profile.applied', { profile: profileId, bytes, max: MAX_ROLE_PROFILE_ENTRY_BYTES }, loc));
424
+ }
425
+ return;
426
+ }
427
+ await sessionReply(rootId, t('role.profile.help', undefined, loc));
428
+ return;
429
+ }
295
430
  // /role team [...] — manage the team-level (per-bot, cross-chat) role
296
431
  const teamMatch = trimmed.match(/^team\b([\s\S]*)$/);
297
432
  if (teamMatch) {
@@ -886,6 +1021,38 @@ export async function handleTermLinkCommand(rootId, larkAppId, chatId, senderOpe
886
1021
  }
887
1022
  // channel === 'ephemeral': the visible-to-you card IS the response; no extra msg.
888
1023
  }
1024
+ /** Format a SafeInsightReport into a compact owner-facing summary for the
1025
+ * `/insight` command. Spans are never rendered here — the dashboard Insight tab
1026
+ * owns span detail; the chat card stays a one-glance summary (aggregate + the
1027
+ * severity-sorted rule suggestions, top first). */
1028
+ function formatInsightCard(report, loc) {
1029
+ if (report.status === 'unsupported_cli')
1030
+ return t('cmd.insight.unsupported', undefined, loc);
1031
+ if (report.status === 'transcript_missing')
1032
+ return t('cmd.insight.no_transcript', undefined, loc);
1033
+ if (report.status !== 'ok')
1034
+ return t('cmd.insight.parse_error', undefined, loc);
1035
+ const a = report.agg;
1036
+ if (a.totalSpans === 0)
1037
+ return t('cmd.insight.no_spans', undefined, loc);
1038
+ const icon = (s) => (s === 'bad' ? '🔴' : s === 'warn' ? '🟡' : 'ℹ️');
1039
+ const header = t('cmd.insight.header', undefined, loc);
1040
+ const lines = [report.meta.asOf ? `${header} · ${report.meta.asOf}` : header];
1041
+ lines.push(t('cmd.insight.metrics_line', {
1042
+ total: String(a.totalSpans),
1043
+ failed: String(a.failedSpans),
1044
+ slow: String(a.slowSpans),
1045
+ rw: a.readWriteRatio === null ? '—' : String(a.readWriteRatio),
1046
+ compactions: String(a.compactions),
1047
+ }, loc));
1048
+ lines.push('', `${t('cmd.insight.suggestions_label', undefined, loc)}:`);
1049
+ for (const s of report.suggestions) {
1050
+ lines.push(`${icon(s.severity)} ${s.title} — ${s.action}`);
1051
+ if (s.evidence.length)
1052
+ lines.push(` · ${s.evidence.join(';')}`);
1053
+ }
1054
+ return lines.join('\n');
1055
+ }
889
1056
  export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
890
1057
  const { activeSessions, getActiveCount, lastRepoScan } = deps;
891
1058
  // Command replies carry the triggering messageId as the turnId so a shared
@@ -919,6 +1086,28 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
919
1086
  }
920
1087
  break;
921
1088
  }
1089
+ case '/insight': {
1090
+ if (!ds) {
1091
+ await sessionReply(rootId, t('cmd.no_active_session', undefined, loc));
1092
+ break;
1093
+ }
1094
+ // owner-only:与 /card /term 同一 operator 门(开放模式下 owner 通过;
1095
+ // 仅对话授权的 grantee 不算 operator)。无权限直接不回内容。
1096
+ if (!canOperate(larkAppId, ds.chatId, message.senderId)) {
1097
+ await sessionReply(rootId, t('cmd.insight.operator_only', undefined, loc));
1098
+ break;
1099
+ }
1100
+ // 卡片只取 summary(聚合 + 规则建议);span 明细留给 dashboard Insight tab。
1101
+ // buildSafeInsightReport 同步、只读、自带 fail-closed 脱敏,raw 永不进结构。
1102
+ const report = buildSafeInsightReport({
1103
+ cliId: ds.session.cliId ?? 'unknown',
1104
+ sessionId: ds.session.sessionId,
1105
+ cliSessionId: ds.session.cliSessionId,
1106
+ cwd: ds.session.workingDir,
1107
+ }, { detail: 'summary' });
1108
+ await sessionReply(rootId, formatInsightCard(report, loc));
1109
+ break;
1110
+ }
922
1111
  case '/land': {
923
1112
  // 把沙盒会话副本里 agent 的改动落回真实仓库。owner 审阅 diff 卡后点「应用到磁盘」。
924
1113
  // agent 在沙盒里无感(以为改的就是真文件),所以只能由 owner 在此手动触发。
@@ -1811,6 +2000,16 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
1811
2000
  if (m.name)
1812
2001
  rawArgs = rawArgs.split(`@${m.name}`).join(' ');
1813
2002
  }
2003
+ let roleProfileId;
2004
+ const roleProfileArg = rawArgs.match(/(?:^|\s)--role-profile(?:=|\s+)(\S+)/);
2005
+ if (roleProfileArg) {
2006
+ if (!isValidRoleProfileId(roleProfileArg[1])) {
2007
+ await sessionReply(rootId, t('role.profile.invalid', undefined, loc));
2008
+ break;
2009
+ }
2010
+ roleProfileId = roleProfileArg[1];
2011
+ rawArgs = rawArgs.replace(roleProfileArg[0], ' ');
2012
+ }
1814
2013
  const firstLine = rawArgs.split(/\r?\n/).map(s => s.trim()).find(Boolean) ?? '';
1815
2014
  const MAX_NAME = 50; // Lark group names cap around 60; leave headroom for '…'
1816
2015
  let groupName;
@@ -1834,6 +2033,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
1834
2033
  userOpenIds: [senderOpenId],
1835
2034
  transferOwnerTo: senderOpenId,
1836
2035
  notifyOwnerOpenId: senderOpenId,
2036
+ roleProfileId,
1837
2037
  });
1838
2038
  // Prefer the shareable join link (others can click to *join*); fall
1839
2039
  // back to the member-only applink URL when Lark's link API failed.
@@ -1866,6 +2066,14 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
1866
2066
  if (result.invalidBotIds.length > 0) {
1867
2067
  hints.push(t('cmd.group.warn_bots_rejected', { bots: result.invalidBotIds.map(nameOf).join('、') }, loc));
1868
2068
  }
2069
+ if (roleProfileId) {
2070
+ if (result.roleProfileBootstrapError) {
2071
+ hints.push(t('cmd.group.role_profile_bootstrap_failed', { profile: roleProfileId, reason: result.roleProfileBootstrapError ?? 'unknown' }, loc));
2072
+ }
2073
+ else {
2074
+ hints.push(t('cmd.group.role_profile_bootstrap_sent', { profile: roleProfileId }, loc));
2075
+ }
2076
+ }
1869
2077
  const hintsText = hints.length > 0 ? '\n' + hints.join('\n') : '';
1870
2078
  await sessionReply(rootId, t('cmd.group.created', { name: groupName, link, hints: hintsText }, loc));
1871
2079
  logger.info(`[${logTag}] /group created chat=${result.chatId} name="${groupName}" bots=[${larkAppIdsForGroup.join(',')}] invitee=${senderOpenId}`);