botmux 2.62.0 → 2.63.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 (128) hide show
  1. package/dist/config.d.ts +12 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +12 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/core/command-handler.d.ts.map +1 -1
  6. package/dist/core/command-handler.js +5 -1
  7. package/dist/core/command-handler.js.map +1 -1
  8. package/dist/core/dashboard-rows.d.ts +11 -0
  9. package/dist/core/dashboard-rows.d.ts.map +1 -1
  10. package/dist/core/dashboard-rows.js +4 -0
  11. package/dist/core/dashboard-rows.js.map +1 -1
  12. package/dist/core/session-activity.d.ts +16 -0
  13. package/dist/core/session-activity.d.ts.map +1 -1
  14. package/dist/core/session-activity.js +35 -0
  15. package/dist/core/session-activity.js.map +1 -1
  16. package/dist/core/trigger-session.d.ts.map +1 -1
  17. package/dist/core/trigger-session.js +9 -10
  18. package/dist/core/trigger-session.js.map +1 -1
  19. package/dist/core/worker-pool.d.ts.map +1 -1
  20. package/dist/core/worker-pool.js +3 -0
  21. package/dist/core/worker-pool.js.map +1 -1
  22. package/dist/daemon.d.ts.map +1 -1
  23. package/dist/daemon.js +8 -1
  24. package/dist/daemon.js.map +1 -1
  25. package/dist/dashboard/auth.d.ts +6 -0
  26. package/dist/dashboard/auth.d.ts.map +1 -1
  27. package/dist/dashboard/auth.js +33 -12
  28. package/dist/dashboard/auth.js.map +1 -1
  29. package/dist/dashboard/connector-api.d.ts.map +1 -1
  30. package/dist/dashboard/connector-api.js +22 -17
  31. package/dist/dashboard/connector-api.js.map +1 -1
  32. package/dist/dashboard/public-redact.d.ts +16 -0
  33. package/dist/dashboard/public-redact.d.ts.map +1 -0
  34. package/dist/dashboard/public-redact.js +62 -0
  35. package/dist/dashboard/public-redact.js.map +1 -0
  36. package/dist/dashboard/web/app.d.ts +1 -0
  37. package/dist/dashboard/web/app.d.ts.map +1 -1
  38. package/dist/dashboard/web/app.js +171 -25
  39. package/dist/dashboard/web/app.js.map +1 -1
  40. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  41. package/dist/dashboard/web/bot-defaults.js +136 -64
  42. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  43. package/dist/dashboard/web/connectors.d.ts.map +1 -1
  44. package/dist/dashboard/web/connectors.js +120 -25
  45. package/dist/dashboard/web/connectors.js.map +1 -1
  46. package/dist/dashboard/web/cyber-fx.d.ts +6 -0
  47. package/dist/dashboard/web/cyber-fx.d.ts.map +1 -0
  48. package/dist/dashboard/web/cyber-fx.js +289 -0
  49. package/dist/dashboard/web/cyber-fx.js.map +1 -0
  50. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  51. package/dist/dashboard/web/i18n.js +146 -8
  52. package/dist/dashboard/web/i18n.js.map +1 -1
  53. package/dist/dashboard/web/overview.d.ts.map +1 -1
  54. package/dist/dashboard/web/overview.js +206 -48
  55. package/dist/dashboard/web/overview.js.map +1 -1
  56. package/dist/dashboard/web/preferences.d.ts +16 -0
  57. package/dist/dashboard/web/preferences.d.ts.map +1 -1
  58. package/dist/dashboard/web/preferences.js +72 -1
  59. package/dist/dashboard/web/preferences.js.map +1 -1
  60. package/dist/dashboard/web/roles.js +3 -3
  61. package/dist/dashboard/web/roles.js.map +1 -1
  62. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  63. package/dist/dashboard/web/sessions.js +404 -64
  64. package/dist/dashboard/web/sessions.js.map +1 -1
  65. package/dist/dashboard/web/settings.d.ts +2 -0
  66. package/dist/dashboard/web/settings.d.ts.map +1 -0
  67. package/dist/dashboard/web/settings.js +135 -0
  68. package/dist/dashboard/web/settings.js.map +1 -0
  69. package/dist/dashboard/web/skin-intro.d.ts +4 -0
  70. package/dist/dashboard/web/skin-intro.d.ts.map +1 -0
  71. package/dist/dashboard/web/skin-intro.js +49 -0
  72. package/dist/dashboard/web/skin-intro.js.map +1 -0
  73. package/dist/dashboard/web/team-federation.js +4 -4
  74. package/dist/dashboard/web/team-federation.js.map +1 -1
  75. package/dist/dashboard/web/theme-menu.d.ts +3 -0
  76. package/dist/dashboard/web/theme-menu.d.ts.map +1 -0
  77. package/dist/dashboard/web/theme-menu.js +97 -0
  78. package/dist/dashboard/web/theme-menu.js.map +1 -0
  79. package/dist/dashboard/web/ui.d.ts +16 -2
  80. package/dist/dashboard/web/ui.d.ts.map +1 -1
  81. package/dist/dashboard/web/ui.js +137 -7
  82. package/dist/dashboard/web/ui.js.map +1 -1
  83. package/dist/dashboard/webhook-routes.d.ts +1 -5
  84. package/dist/dashboard/webhook-routes.d.ts.map +1 -1
  85. package/dist/dashboard/webhook-routes.js +127 -76
  86. package/dist/dashboard/webhook-routes.js.map +1 -1
  87. package/dist/dashboard-web/app.js +704 -512
  88. package/dist/dashboard-web/index.html +19 -14
  89. package/dist/dashboard-web/skins/bluearchive-hero.webp +0 -0
  90. package/dist/dashboard-web/skins/dragonball-goku.webp +0 -0
  91. package/dist/dashboard-web/skins/dragonball-wukong.webp +0 -0
  92. package/dist/dashboard-web/skins/fallout-vaultboy.webp +0 -0
  93. package/dist/dashboard-web/skins/genshin-breeze.webp +0 -0
  94. package/dist/dashboard-web/skins/ikun-hero.webp +0 -0
  95. package/dist/dashboard-web/skins/prts-priestess.webp +0 -0
  96. package/dist/dashboard-web/skins/zzz-hero.webp +0 -0
  97. package/dist/dashboard-web/skins/zzz-pattern.svg +25 -0
  98. package/dist/dashboard-web/style.css +2870 -16
  99. package/dist/dashboard.js +91 -31
  100. package/dist/dashboard.js.map +1 -1
  101. package/dist/global-config.d.ts +15 -0
  102. package/dist/global-config.d.ts.map +1 -1
  103. package/dist/global-config.js +51 -2
  104. package/dist/global-config.js.map +1 -1
  105. package/dist/im/lark/card-builder.d.ts.map +1 -1
  106. package/dist/im/lark/card-builder.js +17 -3
  107. package/dist/im/lark/card-builder.js.map +1 -1
  108. package/dist/im/lark/card-handler.d.ts.map +1 -1
  109. package/dist/im/lark/card-handler.js +5 -0
  110. package/dist/im/lark/card-handler.js.map +1 -1
  111. package/dist/services/connector-store.d.ts +2 -3
  112. package/dist/services/connector-store.d.ts.map +1 -1
  113. package/dist/services/connector-store.js.map +1 -1
  114. package/dist/services/trigger-types.d.ts +1 -0
  115. package/dist/services/trigger-types.d.ts.map +1 -1
  116. package/dist/services/trigger-types.js.map +1 -1
  117. package/dist/services/webhook-lifecycle-extractors.d.ts +4 -13
  118. package/dist/services/webhook-lifecycle-extractors.d.ts.map +1 -1
  119. package/dist/services/webhook-lifecycle-extractors.js +8 -23
  120. package/dist/services/webhook-lifecycle-extractors.js.map +1 -1
  121. package/dist/utils/screen-analyzer.d.ts +8 -0
  122. package/dist/utils/screen-analyzer.d.ts.map +1 -1
  123. package/dist/utils/screen-analyzer.js +32 -11
  124. package/dist/utils/screen-analyzer.js.map +1 -1
  125. package/dist/workflows/trigger-from-envelope.d.ts.map +1 -1
  126. package/dist/workflows/trigger-from-envelope.js +1 -0
  127. package/dist/workflows/trigger-from-envelope.js.map +1 -1
  128. package/package.json +2 -2
@@ -1,25 +1,39 @@
1
1
  import { type DashboardLocale } from './i18n.js';
2
- import { type ResolvedTheme, type ThemeMode } from './preferences.js';
2
+ import { type ResolvedTheme, type ThemeMode, type SkinId } from './preferences.js';
3
3
  type UiListener = () => void;
4
4
  declare class DashboardUiState {
5
5
  locale: DashboardLocale;
6
6
  themeMode: ThemeMode;
7
7
  resolvedTheme: ResolvedTheme;
8
+ skin: SkinId;
8
9
  private listeners;
9
10
  private translate;
10
11
  private mediaQuery;
11
12
  init(): void;
12
13
  t(key: string, params?: Record<string, string | number>): string;
13
14
  setLocale(locale: DashboardLocale): void;
14
- setThemeMode(mode: ThemeMode): void;
15
+ get theme(): string;
16
+ setTheme(value: string): void;
15
17
  on(fn: UiListener): () => void;
16
18
  private emit;
17
19
  private applyTheme;
20
+ private applySkin;
18
21
  private applyLocale;
19
22
  }
20
23
  export declare const ui: DashboardUiState;
21
24
  export declare function t(key: string, params?: Record<string, string | number>): string;
22
25
  export declare function escapeHtml(s: string): string;
23
26
  export declare function relTime(ms: number): string;
27
+ export declare function botOrbStyle(name: string): string;
28
+ export declare function loadNameMaps(): Promise<void>;
29
+ /** 会话所属 bot 的显示名:注册表友好名 → 会话自带 botName(非 id 时)→ id。 */
30
+ export declare function botDisplayName(s: Record<string, any>): string;
31
+ /** 会话所在群聊的标题;单聊或群列表里查不到时返回 null(由调用方回退)。 */
32
+ export declare function chatDisplayTitle(s: Record<string, any>): string | null;
33
+ /** 话题首条消息常以 "@bot " 开头(群里要 @ 才能触发)——展示时剥掉开头的
34
+ * 连续 mention,只留真正的消息内容;剥空了(纯 @ 消息)就保留原文。 */
35
+ export declare function stripMentionPrefix(title: unknown): string;
36
+ /** 会话当前是否卡在等人,以及等什么(全局 strip 和工作台共用同一判定)。 */
37
+ export declare function attentionReason(s: Record<string, any>): string | null;
24
38
  export {};
25
39
  //# sourceMappingURL=ui.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../src/dashboard/web/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AAE1B,KAAK,UAAU,GAAG,MAAM,IAAI,CAAC;AAE7B,cAAM,gBAAgB;IACpB,MAAM,EAAE,eAAe,CAAQ;IAC/B,SAAS,EAAE,SAAS,CAAY;IAChC,aAAa,EAAE,aAAa,CAAW;IACvC,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,UAAU,CAA+B;IAEjD,IAAI,IAAI,IAAI;IAcZ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM;IAIhE,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IASxC,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAQnC,EAAE,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,IAAI;IAK9B,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;CAGpB;AAOD,eAAO,MAAM,EAAE,kBAAyB,CAAC;AAEzC,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAE/E;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAO1C"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../src/dashboard/web/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,MAAM,EACZ,MAAM,kBAAkB,CAAC;AAI1B,KAAK,UAAU,GAAG,MAAM,IAAI,CAAC;AAE7B,cAAM,gBAAgB;IACpB,MAAM,EAAE,eAAe,CAAQ;IAC/B,SAAS,EAAE,SAAS,CAAY;IAChC,aAAa,EAAE,aAAa,CAAW;IACvC,IAAI,EAAE,MAAM,CAAa;IACzB,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,UAAU,CAA+B;IAEjD,IAAI,IAAI,IAAI;IAgBZ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM;IAIhE,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAWxC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAiB7B,EAAE,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,IAAI;IAK9B,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,WAAW;CAGpB;AAoBD,eAAO,MAAM,EAAE,kBAAyB,CAAC;AAEzC,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAE/E;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAO1C;AAeD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhD;AAWD,wBAAgB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB5C;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAK7D;AAED,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,IAAI,CAEtE;AAED;6CAC6C;AAC7C,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIzD;AAED,6CAA6C;AAC7C,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,IAAI,CAMrE"}
@@ -1,9 +1,12 @@
1
1
  import { DASHBOARD_LOCALE_STORAGE_KEY, createDashboardTranslator, readStoredDashboardLocale, } from './i18n.js';
2
- import { THEME_STORAGE_KEY, readStoredThemeMode, resolveThemeMode, } from './preferences.js';
2
+ import { THEME_STORAGE_KEY, SKIN_STORAGE_KEY, readStoredThemeMode, readStoredSkin, resolveThemeMode, } from './preferences.js';
3
+ import { applyCyberFx } from './cyber-fx.js';
4
+ import { playSkinIntro } from './skin-intro.js';
3
5
  class DashboardUiState {
4
6
  locale = 'zh';
5
7
  themeMode = 'system';
6
8
  resolvedTheme = 'light';
9
+ skin = 'default';
7
10
  listeners = new Set();
8
11
  translate = createDashboardTranslator(this.locale);
9
12
  mediaQuery = null;
@@ -12,12 +15,14 @@ class DashboardUiState {
12
15
  this.locale = readStoredDashboardLocale(w?.localStorage, navigatorLanguages());
13
16
  this.translate = createDashboardTranslator(this.locale);
14
17
  this.themeMode = readStoredThemeMode(w?.localStorage);
18
+ this.skin = readStoredSkin(w?.localStorage);
15
19
  this.mediaQuery = w?.matchMedia?.('(prefers-color-scheme: dark)') ?? null;
16
20
  this.mediaQuery?.addEventListener('change', () => {
17
21
  this.applyTheme();
18
22
  this.emit();
19
23
  });
20
24
  this.applyTheme();
25
+ this.applySkin();
21
26
  this.applyLocale();
22
27
  }
23
28
  t(key, params) {
@@ -32,12 +37,25 @@ class DashboardUiState {
32
37
  this.applyLocale();
33
38
  this.emit();
34
39
  }
35
- setThemeMode(mode) {
36
- if (this.themeMode === mode)
37
- return;
38
- this.themeMode = mode;
39
- window.localStorage.setItem(THEME_STORAGE_KEY, mode);
40
+ // The topbar exposes a single "Theme" dropdown whose value is either a base
41
+ // colour mode (system/light/dark the `default` skin) or a named skin id.
42
+ get theme() {
43
+ return this.skin === 'default' ? this.themeMode : this.skin;
44
+ }
45
+ setTheme(value) {
46
+ const isMode = value === 'system' || value === 'light' || value === 'dark';
47
+ const nextSkin = isMode ? 'default' : value;
48
+ const skinChanged = nextSkin !== this.skin;
49
+ if (isMode && this.themeMode !== value) {
50
+ this.themeMode = value;
51
+ window.localStorage.setItem(THEME_STORAGE_KEY, this.themeMode);
52
+ }
53
+ if (skinChanged) {
54
+ this.skin = nextSkin;
55
+ window.localStorage.setItem(SKIN_STORAGE_KEY, this.skin);
56
+ }
40
57
  this.applyTheme();
58
+ this.applySkin(skinChanged);
41
59
  this.emit();
42
60
  }
43
61
  on(fn) {
@@ -50,13 +68,40 @@ class DashboardUiState {
50
68
  }
51
69
  applyTheme() {
52
70
  this.resolvedTheme = resolveThemeMode(this.themeMode, !!this.mediaQuery?.matches);
53
- document.documentElement.dataset.theme = this.resolvedTheme;
71
+ // A named skin ships its own light/dark palette, so drive data-theme from the
72
+ // skin's intrinsic mode — that way the base theme's light/dark component rules
73
+ // (incl. PR #123's dark-only overrides) match the skin instead of fighting it.
74
+ // The default skin follows the user's system/light/dark choice.
75
+ const themeAttr = this.skin === 'default' ? this.resolvedTheme : SKIN_THEME[this.skin];
76
+ document.documentElement.dataset.theme = themeAttr;
54
77
  document.documentElement.dataset.themeMode = this.themeMode;
55
78
  }
79
+ // `animate` plays the boot loader — true when the user actively switches in,
80
+ // false on initial load so a refresh doesn't replay the 3s decrypt overlay.
81
+ applySkin(animate = false) {
82
+ document.documentElement.dataset.skin = this.skin;
83
+ applyCyberFx(this.skin === 'cyber', animate);
84
+ // 2077 plays its own boot loader; the other skins get a themed switch-in intro.
85
+ if (animate && this.skin !== 'cyber' && this.skin !== 'default') {
86
+ playSkinIntro(this.skin);
87
+ }
88
+ }
56
89
  applyLocale() {
57
90
  document.documentElement.lang = this.locale === 'zh' ? 'zh-CN' : 'en';
58
91
  }
59
92
  }
93
+ // Each named skin's intrinsic light/dark mode (drives the data-theme attribute).
94
+ const SKIN_THEME = {
95
+ default: 'light',
96
+ cyber: 'dark',
97
+ genshin: 'light',
98
+ fallout: 'dark',
99
+ prts: 'dark',
100
+ bluearchive: 'dark',
101
+ zzz: 'dark',
102
+ dragonball: 'light',
103
+ ikun: 'dark',
104
+ };
60
105
  function navigatorLanguages() {
61
106
  if (typeof navigator === 'undefined')
62
107
  return [];
@@ -81,4 +126,89 @@ export function relTime(ms) {
81
126
  return Math.floor(diff / 3_600_000) + 'h';
82
127
  return Math.floor(diff / 86_400_000) + 'd';
83
128
  }
129
+ // ── 数字员工视觉:每个 bot 一颗专属色相的"数字生命球" ─────────────────────
130
+ // 按名字 hash 从固定色板取渐变对,同名永远同色,跨页面一致。
131
+ const ORB_PALETTE = [
132
+ { c1: '#5be3ff', c2: '#4f8bff' },
133
+ { c1: '#b89bff', c2: '#6b4df0' },
134
+ { c1: '#7ce0c3', c2: '#2e9e8f' },
135
+ { c1: '#8fb4ff', c2: '#3b62d8' },
136
+ { c1: '#ffd28f', c2: '#d8783b' },
137
+ { c1: '#7df0a8', c2: '#1f9e63' },
138
+ { c1: '#9fd0ff', c2: '#4878c8' },
139
+ { c1: '#ff9fb8', c2: '#d84a78' },
140
+ ];
141
+ export function botOrbStyle(name) {
142
+ let h = 0;
143
+ const key = String(name ?? '');
144
+ for (let i = 0; i < key.length; i++)
145
+ h = (h * 31 + key.charCodeAt(i)) >>> 0;
146
+ const { c1, c2 } = ORB_PALETTE[h % ORB_PALETTE.length];
147
+ return `--c1:${c1};--c2:${c2}`;
148
+ }
149
+ // ── 跨页共享的展示名解析(bot 友好名 / 群聊标题)────────────────────────────
150
+ // daemon IPC 上报的 SessionRow.botName 历史上填的是 larkAppId(friendly name
151
+ // probe 回来只回写了注册表 descriptor,没回填 IPC 的 cachedBotName),这里用
152
+ // /api/groups 的注册表 + 群列表把 id 解析成人话。加载失败静默降级显示原值——
153
+ // 纯展示增强,不挡核心功能。
154
+ const botNameByAppId = new Map();
155
+ const chatNameById = new Map();
156
+ let nameMapsPromise = null;
157
+ export function loadNameMaps() {
158
+ nameMapsPromise ??= (async () => {
159
+ try {
160
+ const r = await fetch('/api/groups');
161
+ if (!r.ok)
162
+ throw new Error(`HTTP ${r.status}`);
163
+ const data = await r.json();
164
+ for (const b of data.bots ?? []) {
165
+ if (b.larkAppId && b.botName && b.botName !== b.larkAppId) {
166
+ botNameByAppId.set(b.larkAppId, String(b.botName));
167
+ }
168
+ }
169
+ for (const c of data.chats ?? []) {
170
+ if (c.chatId && c.name)
171
+ chatNameById.set(c.chatId, String(c.name));
172
+ }
173
+ }
174
+ catch {
175
+ // 失败不缓存(dashboard 刚启动 /api/groups 可能短暂 503)——
176
+ // 清掉 memo,下一个页面 mount / strip 重绘再重试;期间显示原始 id。
177
+ nameMapsPromise = null;
178
+ }
179
+ })();
180
+ return nameMapsPromise;
181
+ }
182
+ /** 会话所属 bot 的显示名:注册表友好名 → 会话自带 botName(非 id 时)→ id。 */
183
+ export function botDisplayName(s) {
184
+ const mapped = s.larkAppId ? botNameByAppId.get(s.larkAppId) : undefined;
185
+ if (mapped)
186
+ return mapped;
187
+ if (s.botName && s.botName !== s.larkAppId)
188
+ return String(s.botName);
189
+ return String(s.botName ?? s.larkAppId ?? '-');
190
+ }
191
+ /** 会话所在群聊的标题;单聊或群列表里查不到时返回 null(由调用方回退)。 */
192
+ export function chatDisplayTitle(s) {
193
+ return (s.chatId && chatNameById.get(s.chatId)) || null;
194
+ }
195
+ /** 话题首条消息常以 "@bot " 开头(群里要 @ 才能触发)——展示时剥掉开头的
196
+ * 连续 mention,只留真正的消息内容;剥空了(纯 @ 消息)就保留原文。 */
197
+ export function stripMentionPrefix(title) {
198
+ const raw = String(title ?? '');
199
+ const out = raw.replace(/^(?:@\S+\s*)+/, '').trim();
200
+ return out || raw;
201
+ }
202
+ /** 会话当前是否卡在等人,以及等什么(全局 strip 和工作台共用同一判定)。 */
203
+ export function attentionReason(s) {
204
+ if (s.status === 'closed')
205
+ return null;
206
+ if (s.pendingRepo)
207
+ return t('sessions.board.signalRepo');
208
+ if (s.tuiPromptActive)
209
+ return t('sessions.board.signalPrompt');
210
+ if (s.status === 'limited')
211
+ return t('sessions.board.signalLimited');
212
+ return null;
213
+ }
84
214
  //# sourceMappingURL=ui.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/dashboard/web/ui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/dashboard/web/ui.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { type ConnectorDefinition } from '../services/connector-store.js';
3
- import { type WebhookLifecycleRecord } from '../services/webhook-lifecycle-store.js';
4
3
  import { type TriggerApiDeps } from './trigger-api.js';
5
4
  export type WebhookRouteDeps = TriggerApiDeps & {
6
5
  createLifecycleGroup?: (connector: ConnectorDefinition, args: {
@@ -9,11 +8,8 @@ export type WebhookRouteDeps = TriggerApiDeps & {
9
8
  chatId: string;
10
9
  creatorLarkAppId?: string;
11
10
  }>;
12
- closeLifecycleGroup?: (connector: ConnectorDefinition, record: WebhookLifecycleRecord) => Promise<{
13
- ok: boolean;
14
- error?: string;
15
- }>;
16
11
  };
17
12
  export declare function verifyWebhookSignature(secret: string, ts: string, rawBody: Buffer, sig: string): boolean;
13
+ export declare function verifyWebhookToken(secret: string, presented: string): boolean;
18
14
  export declare function handleWebhookRoute(req: IncomingMessage, res: ServerResponse, url: URL, deps: WebhookRouteDeps): Promise<boolean>;
19
15
  //# sourceMappingURL=webhook-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-routes.d.ts","sourceRoot":"","sources":["../../src/dashboard/webhook-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAKxF,OAAO,EAKL,KAAK,sBAAsB,EAC5B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAK7F,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAC9C,oBAAoB,CAAC,EAAE,CACrB,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KACvB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,mBAAmB,CAAC,EAAE,CACpB,SAAS,EAAE,mBAAmB,EAC9B,MAAM,EAAE,sBAAsB,KAC3B,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAgCF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQxG;AAkGD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,OAAO,CAAC,CAuMlB"}
1
+ {"version":3,"file":"webhook-routes.d.ts","sourceRoot":"","sources":["../../src/dashboard/webhook-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAWxF,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAK7F,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAC9C,oBAAoB,CAAC,EAAE,CACrB,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KACvB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7D,CAAC;AAgCF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQxG;AAKD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAK7E;AAkHD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,OAAO,CAAC,CAuNlB"}
@@ -1,9 +1,9 @@
1
- import { createHmac, timingSafeEqual } from 'node:crypto';
1
+ import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
2
2
  import { getConnector } from '../services/connector-store.js';
3
3
  import { getWebhookSecret } from '../services/webhook-key.js';
4
4
  import { appendTriggerLog } from '../services/trigger-log-store.js';
5
- import { extractWebhookLifecycle } from '../services/webhook-lifecycle-extractors.js';
6
- import { activateWebhookLifecycleGroup, beginWebhookLifecycleFiring, failWebhookLifecycleGroup, resolveWebhookLifecycleGroup, } from '../services/webhook-lifecycle-store.js';
5
+ import { extractDedupKey } from '../services/webhook-lifecycle-extractors.js';
6
+ import { activateWebhookLifecycleGroup, beginWebhookLifecycleFiring, failWebhookLifecycleGroup, } from '../services/webhook-lifecycle-store.js';
7
7
  import { jsonRes } from './workflow-api.js';
8
8
  import { dispatchTriggerRequest, newTriggerId } from './trigger-api.js';
9
9
  const replayNonces = new Map();
@@ -46,6 +46,35 @@ export function verifyWebhookSignature(secret, ts, rawBody, sig) {
46
46
  const got = parseSignature(sig);
47
47
  return !!got && got.length === expected.length && timingSafeEqual(got, expected);
48
48
  }
49
+ // Bearer-token mode: the presented token IS the secret. Constant-time compare,
50
+ // no body integrity / replay protection (that's the usability/security trade —
51
+ // see `token` verify mode). Empty presented token never matches.
52
+ export function verifyWebhookToken(secret, presented) {
53
+ if (!secret || !presented)
54
+ return false;
55
+ const a = Buffer.from(secret, 'utf-8');
56
+ const b = Buffer.from(presented, 'utf-8');
57
+ return a.length === b.length && timingSafeEqual(a, b);
58
+ }
59
+ // Token carriers, in priority order: path segment > ?token= query > Authorization
60
+ // Bearer > x-botmux-token header. Path is the default (whole URL = credential).
61
+ function extractWebhookToken(req, url, pathToken) {
62
+ if (pathToken)
63
+ return pathToken;
64
+ const fromQuery = url.searchParams.get('token');
65
+ if (fromQuery)
66
+ return fromQuery;
67
+ const auth = headerValue(req, 'authorization');
68
+ if (auth) {
69
+ const m = auth.match(/^Bearer\s+(.+)$/i);
70
+ if (m)
71
+ return m[1].trim();
72
+ }
73
+ const fromHeader = headerValue(req, 'x-botmux-token');
74
+ if (fromHeader)
75
+ return fromHeader;
76
+ return undefined;
77
+ }
49
78
  function timestampOk(ts, toleranceSeconds) {
50
79
  const n = Number(ts);
51
80
  if (!Number.isFinite(n))
@@ -136,7 +165,10 @@ function webhookOkLog(connectorId, action, message) {
136
165
  return { ok: true, triggerId, action, message };
137
166
  }
138
167
  export async function handleWebhookRoute(req, res, url, deps) {
139
- const m = url.pathname.match(/^\/webhook\/([^/]+)$/);
168
+ // Second path segment (optional) carries the bearer token for `token` mode:
169
+ // /webhook/<connectorId> → token via query / Authorization header
170
+ // /webhook/<connectorId>/<token> → token baked into the URL (default)
171
+ const m = url.pathname.match(/^\/webhook\/([^/]+)(?:\/([^/]+))?$/);
140
172
  if (!m)
141
173
  return false;
142
174
  if (req.method !== 'POST') {
@@ -144,6 +176,7 @@ export async function handleWebhookRoute(req, res, url, deps) {
144
176
  return true;
145
177
  }
146
178
  const connectorId = decodeURIComponent(m[1]);
179
+ const pathToken = m[2] ? decodeURIComponent(m[2]) : undefined;
147
180
  const connector = getConnector(connectorId);
148
181
  if (!connector || !connector.enabled) {
149
182
  webhookError(res, 404, connectorId, 'bad_request', 'unknown or disabled connector');
@@ -161,92 +194,108 @@ export async function handleWebhookRoute(req, res, url, deps) {
161
194
  webhookError(res, 413, connectorId, 'bad_request', 'request body too large');
162
195
  return true;
163
196
  }
197
+ // `requestId` becomes source.requestId on the trigger. HMAC mode reuses the
198
+ // caller's nonce; token mode has no nonce so we mint one.
164
199
  const verify = connector.verify;
165
- const ts = headerValue(req, verify.timestampHeader);
166
- const nonce = headerValue(req, verify.nonceHeader);
167
- const sig = headerValue(req, verify.signatureHeader);
168
- if (!ts || !nonce || !sig) {
169
- webhookError(res, 401, connectorId, 'invalid_signature', 'missing signature, timestamp, or nonce header');
170
- return true;
171
- }
172
- if (!timestampOk(ts, verify.toleranceSeconds)) {
173
- webhookError(res, 401, connectorId, 'replay', 'timestamp outside tolerance window');
174
- return true;
175
- }
176
- if (!claimNonce(connector.id, nonce, verify.toleranceSeconds)) {
177
- webhookError(res, 409, connectorId, 'replay', 'nonce replay detected');
178
- return true;
179
- }
180
- const secret = getWebhookSecret(verify.secretRef);
181
- if (!secret || !verifyWebhookSignature(secret, ts, rawBody, sig)) {
182
- webhookError(res, 401, connectorId, 'invalid_signature', 'signature verification failed');
183
- return true;
200
+ let requestId;
201
+ if (verify.type === 'token') {
202
+ const presented = extractWebhookToken(req, url, pathToken);
203
+ const secret = getWebhookSecret(verify.secretRef);
204
+ if (!presented || !secret || !verifyWebhookToken(secret, presented)) {
205
+ webhookError(res, 401, connectorId, 'invalid_signature', 'token verification failed');
206
+ return true;
207
+ }
208
+ requestId = `whk_${randomUUID()}`;
184
209
  }
185
- const parsed = parsePayload(rawBody);
186
- if (connector.target.mode === 'new-group') {
187
- const extracted = extractWebhookLifecycle(parsed.payload, connector.lifecycleExtractors);
188
- if (!extracted.ok) {
189
- webhookError(res, 400, connectorId, 'lifecycle_extract_failed', extracted.error);
210
+ else {
211
+ const ts = headerValue(req, verify.timestampHeader);
212
+ const nonce = headerValue(req, verify.nonceHeader);
213
+ const sig = headerValue(req, verify.signatureHeader);
214
+ if (!ts || !nonce || !sig) {
215
+ webhookError(res, 401, connectorId, 'invalid_signature', 'missing signature, timestamp, or nonce header');
190
216
  return true;
191
217
  }
192
- const { dedupKey, status } = extracted.lifecycle;
193
- if (status === 'resolved') {
194
- const resolved = await resolveWebhookLifecycleGroup(connector.id, dedupKey);
195
- let closeResult;
196
- if (resolved.action === 'close' && resolved.record?.chatId) {
197
- closeResult = deps.closeLifecycleGroup
198
- ? await deps.closeLifecycleGroup(connector, resolved.record)
199
- : { ok: false, error: 'closeLifecycleGroup hook not configured' };
200
- }
201
- const body = webhookOkLog(connector.id, 'ignored', `lifecycle ${resolved.action}`);
202
- jsonRes(res, closeResult?.ok === false ? 502 : 200, {
203
- ...body,
204
- lifecycle: { dedupKey, status, action: resolved.action, chatId: resolved.record?.chatId },
205
- ...(closeResult?.ok === false ? { ok: false, errorCode: 'trigger_failed', error: closeResult.error } : {}),
206
- });
218
+ if (!timestampOk(ts, verify.toleranceSeconds)) {
219
+ webhookError(res, 401, connectorId, 'replay', 'timestamp outside tolerance window');
220
+ return true;
221
+ }
222
+ if (!claimNonce(connector.id, nonce, verify.toleranceSeconds)) {
223
+ webhookError(res, 409, connectorId, 'replay', 'nonce replay detected');
207
224
  return true;
208
225
  }
209
- const begun = await beginWebhookLifecycleFiring(connector.id, dedupKey);
210
- if (begun.action === 'creating') {
211
- jsonRes(res, 202, {
212
- ...webhookOkLog(connector.id, 'ignored', 'lifecycle group creation already in progress'),
213
- lifecycle: { dedupKey, status, action: 'creating' },
214
- });
226
+ const secret = getWebhookSecret(verify.secretRef);
227
+ if (!secret || !verifyWebhookSignature(secret, ts, rawBody, sig)) {
228
+ webhookError(res, 401, connectorId, 'invalid_signature', 'signature verification failed');
215
229
  return true;
216
230
  }
217
- let chatId = begun.action === 'reuse' ? begun.record.chatId : undefined;
218
- if (begun.action === 'create') {
231
+ requestId = nonce;
232
+ }
233
+ const parsed = parsePayload(rawBody);
234
+ if (connector.target.mode === 'new-group') {
235
+ // Dedup is optional. Configured → events with the same extracted value share
236
+ // one group (create once, reuse after). Not configured → every event spins
237
+ // up a fresh group. (No firing/resolved status; groups are never auto-closed.)
238
+ const dedupPath = connector.lifecycleExtractors?.dedupKey;
239
+ let chatId;
240
+ let dedupKey;
241
+ let action = 'create';
242
+ if (dedupPath) {
243
+ const value = extractDedupKey(parsed.payload, dedupPath);
244
+ if (!value) {
245
+ webhookError(res, 400, connectorId, 'lifecycle_extract_failed', 'dedup_key_not_found');
246
+ return true;
247
+ }
248
+ dedupKey = value;
249
+ const begun = await beginWebhookLifecycleFiring(connector.id, dedupKey);
250
+ if (begun.action === 'creating') {
251
+ jsonRes(res, 202, {
252
+ ...webhookOkLog(connector.id, 'ignored', 'lifecycle group creation already in progress'),
253
+ lifecycle: { dedupKey, action: 'creating' },
254
+ });
255
+ return true;
256
+ }
257
+ if (begun.action === 'reuse') {
258
+ action = 'reuse';
259
+ chatId = begun.record.chatId;
260
+ }
261
+ else {
262
+ if (!deps.createLifecycleGroup) {
263
+ await failWebhookLifecycleGroup(connector.id, dedupKey, begun.record.lifecycleId);
264
+ webhookError(res, 501, connector.id, 'group_create_failed', 'createLifecycleGroup hook not configured');
265
+ return true;
266
+ }
267
+ let created;
268
+ try {
269
+ created = await deps.createLifecycleGroup(connector, { dedupKey });
270
+ }
271
+ catch (e) {
272
+ await failWebhookLifecycleGroup(connector.id, dedupKey, begun.record.lifecycleId);
273
+ webhookError(res, 502, connector.id, 'group_create_failed', e?.message ?? String(e));
274
+ return true;
275
+ }
276
+ const activated = await activateWebhookLifecycleGroup(connector.id, dedupKey, begun.record.lifecycleId, created.chatId, { creatorLarkAppId: created.creatorLarkAppId });
277
+ if (activated.status !== 'active' || !activated.record?.chatId) {
278
+ webhookError(res, 409, connector.id, 'replay', 'lifecycle record was replaced before activation');
279
+ return true;
280
+ }
281
+ chatId = activated.record.chatId;
282
+ }
283
+ }
284
+ else {
285
+ // No dedup: a brand-new group per event (the group name uses the requestId
286
+ // for uniqueness). No lifecycle store record is kept — nothing to reuse.
219
287
  if (!deps.createLifecycleGroup) {
220
- await failWebhookLifecycleGroup(connector.id, dedupKey, begun.record.lifecycleId);
221
288
  webhookError(res, 501, connector.id, 'group_create_failed', 'createLifecycleGroup hook not configured');
222
289
  return true;
223
290
  }
224
- let created;
225
291
  try {
226
- created = await deps.createLifecycleGroup(connector, { dedupKey });
292
+ const created = await deps.createLifecycleGroup(connector, { dedupKey: requestId.slice(0, 16) });
293
+ chatId = created.chatId;
227
294
  }
228
295
  catch (e) {
229
- await failWebhookLifecycleGroup(connector.id, dedupKey, begun.record.lifecycleId);
230
296
  webhookError(res, 502, connector.id, 'group_create_failed', e?.message ?? String(e));
231
297
  return true;
232
298
  }
233
- const activated = await activateWebhookLifecycleGroup(connector.id, dedupKey, begun.record.lifecycleId, created.chatId, { creatorLarkAppId: created.creatorLarkAppId });
234
- if (activated.status === 'pending_resolved') {
235
- const closeResult = deps.closeLifecycleGroup && activated.record
236
- ? await deps.closeLifecycleGroup(connector, activated.record)
237
- : { ok: true };
238
- jsonRes(res, closeResult.ok ? 200 : 502, {
239
- ...webhookOkLog(connector.id, 'ignored', 'lifecycle resolved before group activation'),
240
- lifecycle: { dedupKey, status: 'resolved', action: 'closed', chatId: created.chatId },
241
- ...(closeResult.ok ? {} : { ok: false, errorCode: 'trigger_failed', error: closeResult.error }),
242
- });
243
- return true;
244
- }
245
- if (activated.status !== 'active' || !activated.record?.chatId) {
246
- webhookError(res, 409, connector.id, 'replay', 'lifecycle record was replaced before activation');
247
- return true;
248
- }
249
- chatId = activated.record.chatId;
250
299
  }
251
300
  if (!chatId) {
252
301
  webhookError(res, 500, connector.id, 'trigger_failed', 'lifecycle group has no chatId');
@@ -256,7 +305,7 @@ export async function handleWebhookRoute(req, res, url, deps) {
256
305
  source: {
257
306
  type: 'webhook',
258
307
  connectorId: connector.id,
259
- requestId: nonce,
308
+ requestId,
260
309
  receivedAt: new Date().toISOString(),
261
310
  },
262
311
  target: {
@@ -273,10 +322,11 @@ export async function handleWebhookRoute(req, res, url, deps) {
273
322
  payload: parsed.payload,
274
323
  ...(connector.promptEnvelope.includeRawText ? { rawText: parsed.rawText } : {}),
275
324
  },
276
- options: { dedupKey, status },
325
+ ...(connector.promptEnvelope.instruction ? { instruction: connector.promptEnvelope.instruction } : {}),
326
+ options: dedupKey ? { dedupKey } : {},
277
327
  };
278
328
  const result = await dispatchTriggerRequest(trigger, deps);
279
- jsonRes(res, result.status, { ...result.body, lifecycle: { dedupKey, action: begun.action, chatId } });
329
+ jsonRes(res, result.status, { ...result.body, lifecycle: { ...(dedupKey ? { dedupKey } : {}), action, chatId } });
280
330
  return true;
281
331
  }
282
332
  const chatId = connector.target.mode === 'fixed'
@@ -295,7 +345,7 @@ export async function handleWebhookRoute(req, res, url, deps) {
295
345
  source: {
296
346
  type: 'webhook',
297
347
  connectorId: connector.id,
298
- requestId: nonce,
348
+ requestId,
299
349
  receivedAt: new Date().toISOString(),
300
350
  },
301
351
  target: {
@@ -312,6 +362,7 @@ export async function handleWebhookRoute(req, res, url, deps) {
312
362
  payload: parsed.payload,
313
363
  ...(connector.promptEnvelope.includeRawText ? { rawText: parsed.rawText } : {}),
314
364
  },
365
+ ...(connector.promptEnvelope.instruction ? { instruction: connector.promptEnvelope.instruction } : {}),
315
366
  options: {},
316
367
  };
317
368
  const result = await dispatchTriggerRequest(trigger, deps);