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.
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +5 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +11 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +4 -0
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/session-activity.d.ts +16 -0
- package/dist/core/session-activity.d.ts.map +1 -1
- package/dist/core/session-activity.js +35 -0
- package/dist/core/session-activity.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +9 -10
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +3 -0
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +8 -1
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +6 -0
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +33 -12
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/connector-api.d.ts.map +1 -1
- package/dist/dashboard/connector-api.js +22 -17
- package/dist/dashboard/connector-api.js.map +1 -1
- package/dist/dashboard/public-redact.d.ts +16 -0
- package/dist/dashboard/public-redact.d.ts.map +1 -0
- package/dist/dashboard/public-redact.js +62 -0
- package/dist/dashboard/public-redact.js.map +1 -0
- package/dist/dashboard/web/app.d.ts +1 -0
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +171 -25
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +136 -64
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/connectors.d.ts.map +1 -1
- package/dist/dashboard/web/connectors.js +120 -25
- package/dist/dashboard/web/connectors.js.map +1 -1
- package/dist/dashboard/web/cyber-fx.d.ts +6 -0
- package/dist/dashboard/web/cyber-fx.d.ts.map +1 -0
- package/dist/dashboard/web/cyber-fx.js +289 -0
- package/dist/dashboard/web/cyber-fx.js.map +1 -0
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +146 -8
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/overview.d.ts.map +1 -1
- package/dist/dashboard/web/overview.js +206 -48
- package/dist/dashboard/web/overview.js.map +1 -1
- package/dist/dashboard/web/preferences.d.ts +16 -0
- package/dist/dashboard/web/preferences.d.ts.map +1 -1
- package/dist/dashboard/web/preferences.js +72 -1
- package/dist/dashboard/web/preferences.js.map +1 -1
- package/dist/dashboard/web/roles.js +3 -3
- package/dist/dashboard/web/roles.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +404 -64
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/settings.d.ts +2 -0
- package/dist/dashboard/web/settings.d.ts.map +1 -0
- package/dist/dashboard/web/settings.js +135 -0
- package/dist/dashboard/web/settings.js.map +1 -0
- package/dist/dashboard/web/skin-intro.d.ts +4 -0
- package/dist/dashboard/web/skin-intro.d.ts.map +1 -0
- package/dist/dashboard/web/skin-intro.js +49 -0
- package/dist/dashboard/web/skin-intro.js.map +1 -0
- package/dist/dashboard/web/team-federation.js +4 -4
- package/dist/dashboard/web/team-federation.js.map +1 -1
- package/dist/dashboard/web/theme-menu.d.ts +3 -0
- package/dist/dashboard/web/theme-menu.d.ts.map +1 -0
- package/dist/dashboard/web/theme-menu.js +97 -0
- package/dist/dashboard/web/theme-menu.js.map +1 -0
- package/dist/dashboard/web/ui.d.ts +16 -2
- package/dist/dashboard/web/ui.d.ts.map +1 -1
- package/dist/dashboard/web/ui.js +137 -7
- package/dist/dashboard/web/ui.js.map +1 -1
- package/dist/dashboard/webhook-routes.d.ts +1 -5
- package/dist/dashboard/webhook-routes.d.ts.map +1 -1
- package/dist/dashboard/webhook-routes.js +127 -76
- package/dist/dashboard/webhook-routes.js.map +1 -1
- package/dist/dashboard-web/app.js +704 -512
- package/dist/dashboard-web/index.html +19 -14
- package/dist/dashboard-web/skins/bluearchive-hero.webp +0 -0
- package/dist/dashboard-web/skins/dragonball-goku.webp +0 -0
- package/dist/dashboard-web/skins/dragonball-wukong.webp +0 -0
- package/dist/dashboard-web/skins/fallout-vaultboy.webp +0 -0
- package/dist/dashboard-web/skins/genshin-breeze.webp +0 -0
- package/dist/dashboard-web/skins/ikun-hero.webp +0 -0
- package/dist/dashboard-web/skins/prts-priestess.webp +0 -0
- package/dist/dashboard-web/skins/zzz-hero.webp +0 -0
- package/dist/dashboard-web/skins/zzz-pattern.svg +25 -0
- package/dist/dashboard-web/style.css +2870 -16
- package/dist/dashboard.js +91 -31
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +15 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +51 -2
- package/dist/global-config.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +17 -3
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +5 -0
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/services/connector-store.d.ts +2 -3
- package/dist/services/connector-store.d.ts.map +1 -1
- package/dist/services/connector-store.js.map +1 -1
- package/dist/services/trigger-types.d.ts +1 -0
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/services/webhook-lifecycle-extractors.d.ts +4 -13
- package/dist/services/webhook-lifecycle-extractors.d.ts.map +1 -1
- package/dist/services/webhook-lifecycle-extractors.js +8 -23
- package/dist/services/webhook-lifecycle-extractors.js.map +1 -1
- package/dist/utils/screen-analyzer.d.ts +8 -0
- package/dist/utils/screen-analyzer.d.ts.map +1 -1
- package/dist/utils/screen-analyzer.js +32 -11
- package/dist/utils/screen-analyzer.js.map +1 -1
- package/dist/workflows/trigger-from-envelope.d.ts.map +1 -1
- package/dist/workflows/trigger-from-envelope.js +1 -0
- package/dist/workflows/trigger-from-envelope.js.map +1 -1
- 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
|
-
|
|
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,
|
|
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"}
|
package/dist/dashboard/web/ui.js
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.themeMode
|
|
39
|
-
|
|
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
|
-
|
|
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":"AAAA,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,yBAAyB,GAE1B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,
|
|
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;
|
|
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 {
|
|
6
|
-
import { activateWebhookLifecycleGroup, beginWebhookLifecycleFiring, failWebhookLifecycleGroup,
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
210
|
-
if (
|
|
211
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|