@richwats/webex 0.2.3

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/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # OpenClaw Webex Channel Plugin
2
+
3
+ Webex channel plugin for OpenClaw using webex-node-bot-framework.
4
+
5
+ ## Required Configuration
6
+
7
+ Set both values in your OpenClaw config under channels.webex:
8
+
9
+ - token: Webex bot token
10
+ - webhookUrl: Public callback URL that Webex calls for events
11
+ - listenPort (optional): Local port for the plugin webhook server if default/fallback port is already in use
12
+
13
+ Example:
14
+
15
+ ```json
16
+ {
17
+ "channels": {
18
+ "webex": {
19
+ "enabled": true,
20
+ "token": "YOUR_WEBEX_BOT_TOKEN",
21
+ "webhookUrl": "https://your-public-host.example.com/webex/webhook",
22
+ "defaultTo": "Y2lzY29zcGFyazovL3VzL1JPT00v...",
23
+ "listenPort": 3988
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## Enable in OpenClaw
30
+
31
+ 1. Place this plugin in your OpenClaw extensions path (for example `.openclaw/extensions/webex`).
32
+ 2. Ensure OpenClaw loads the plugin entry from `index.ts` (or built output if your runtime requires compiled JS).
33
+ 3. Enable the channel in your OpenClaw config by setting `channels.webex.enabled: true` and providing `token` + `webhookUrl`.
34
+ 4. Restart the OpenClaw gateway so the channel plugin is registered and started.
35
+
36
+ Minimal enablement example:
37
+
38
+ ```json
39
+ {
40
+ "channels": {
41
+ "webex": {
42
+ "enabled": true,
43
+ "token": "YOUR_WEBEX_BOT_TOKEN",
44
+ "webhookUrl": "https://your-public-host.example.com/webex/webhook"
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Behavior
51
+
52
+ - Receives inbound messages from Webex via webhook middleware from webex-node-bot-framework.
53
+ - Sends outbound text messages to a target roomId, or person:<personId>.
54
+ - Ignores bot-authored inbound messages to prevent loops.
55
+
56
+ ## Local OpenShell Notes
57
+
58
+ - Ensure webhookUrl is publicly reachable from Webex cloud.
59
+ - The plugin listens on `PORT` when set, otherwise uses webhookUrl explicit port, otherwise falls back to `3978`.
60
+ - To avoid `EADDRINUSE` port conflicts (common when another channel uses 3978), set `channels.webex.listenPort`.
61
+ - If running behind a reverse proxy in OpenShell, route webhookUrl path to this plugin service.
62
+ - The plugin includes a Node compatibility shim for legacy Webex dependencies that mutate `globalThis.navigator`.
@@ -0,0 +1,2 @@
1
+ export { WebexChannelConfigSchema } from "./src/config-schema.js";
2
+ //# sourceMappingURL=channel-config-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-config-api.d.ts","sourceRoot":"","sources":["../channel-config-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { WebexChannelConfigSchema } from "./src/config-schema.js";
2
+ //# sourceMappingURL=channel-config-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-config-api.js","sourceRoot":"","sources":["../channel-config-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { webexPlugin } from "./src/channel.js";
2
+ export { setWebexRuntime } from "./src/runtime.js";
3
+ declare const _default: any;
4
+ export default _default;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;;AAEnD,wBAMG"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import * as OpenClawCore from "openclaw/plugin-sdk/core";
2
+ import { webexPlugin } from "./src/channel.js";
3
+ import { setWebexRuntime } from "./src/runtime.js";
4
+ const defineChannelPluginEntryCompat = OpenClawCore.defineChannelPluginEntry ??
5
+ ((params) => ({
6
+ id: params.id,
7
+ name: params.name,
8
+ description: params.description,
9
+ configSchema: typeof params.configSchema === "function"
10
+ ? params.configSchema()
11
+ : (params.configSchema ?? {
12
+ type: "object",
13
+ additionalProperties: false,
14
+ properties: {},
15
+ }),
16
+ register(api) {
17
+ params.setRuntime?.(api?.runtime ?? api);
18
+ api.registerChannel?.({ plugin: params.plugin });
19
+ params.registerCliMetadata?.(api);
20
+ params.registerFull?.(api);
21
+ },
22
+ channelPlugin: params.plugin,
23
+ ...(params.setRuntime ? { setChannelRuntime: params.setRuntime } : {}),
24
+ }));
25
+ export { webexPlugin } from "./src/channel.js";
26
+ export { setWebexRuntime } from "./src/runtime.js";
27
+ export default defineChannelPluginEntryCompat({
28
+ id: "webex",
29
+ name: "Cisco Webex",
30
+ description: "Cisco Webex channel plugin (webex-node-bot-framework)",
31
+ plugin: webexPlugin,
32
+ setRuntime: setWebexRuntime,
33
+ });
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,8BAA8B,GACjC,YAAoE,CAAC,wBAAwB;IAC9F,CAAC,CAAC,MAAW,EAAE,EAAE,CAAC,CAAC;QACjB,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,YAAY,EACV,OAAO,MAAM,CAAC,YAAY,KAAK,UAAU;YACvC,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE;YACvB,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,IAAI;gBACtB,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,KAAK;gBAC3B,UAAU,EAAE,EAAE;aACf,CAAC;QACR,QAAQ,CAAC,GAAQ;YACf,MAAM,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC;YACzC,GAAG,CAAC,eAAe,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YACjD,MAAM,CAAC,mBAAmB,EAAE,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,aAAa,EAAE,MAAM,CAAC,MAAM;QAC5B,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvE,CAAC,CAAC,CAAC;AAEN,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,eAAe,8BAA8B,CAAC;IAC5C,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE,uDAAuD;IACpE,MAAM,EAAE,WAAW;IACnB,UAAU,EAAE,eAAe;CAC5B,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-contract";
2
+ export declare const webexPlugin: ChannelPlugin<any, any>;
3
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/channel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AA4B1E,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CA+G9C,CAAC"}
@@ -0,0 +1,129 @@
1
+ import * as OpenClawCore from "openclaw/plugin-sdk/core";
2
+ import { WebexChannelConfigSchema, hasConfiguredWebexChannel, resolveWebexChannelConfig } from "./config-schema.js";
3
+ import { monitorWebexProvider } from "./monitor.js";
4
+ import { sendTextWebex } from "./outbound.js";
5
+ import { setWebexRuntime, webexLogDebug, webexLogError, webexLogInfo } from "./runtime.js";
6
+ const createChatChannelPluginCompat = OpenClawCore.createChatChannelPlugin ??
7
+ ((config) => config?.base ?? config);
8
+ const state = {};
9
+ async function waitUntilAbort(signal) {
10
+ if (!signal) {
11
+ return await new Promise(() => { });
12
+ }
13
+ if (signal.aborted) {
14
+ return;
15
+ }
16
+ await new Promise((resolve) => {
17
+ signal.addEventListener("abort", () => resolve(), { once: true });
18
+ });
19
+ }
20
+ export const webexPlugin = createChatChannelPluginCompat({
21
+ base: {
22
+ id: "webex",
23
+ meta: {
24
+ id: "webex",
25
+ label: "Cisco Webex",
26
+ selectionLabel: "Cisco Webex Bot",
27
+ docsPath: "/channels/webex",
28
+ docsLabel: "webex",
29
+ blurb: "Webex bot channel powered by webex-node-bot-framework.",
30
+ aliases: ["cisco-webex"],
31
+ order: 80,
32
+ },
33
+ capabilities: {
34
+ chatTypes: ["direct", "channel"],
35
+ threads: false,
36
+ media: false,
37
+ polls: false,
38
+ },
39
+ reload: { configPrefixes: ["channels.webex"] },
40
+ configSchema: WebexChannelConfigSchema,
41
+ config: {
42
+ // Keep both modern and legacy config adapter fields for broad runtime compatibility.
43
+ sectionKey: "webex",
44
+ listAccountIds: () => ["default"],
45
+ resolveAccount: (cfg) => ({
46
+ accountId: "default",
47
+ enabled: cfg?.channels?.webex?.enabled !== false,
48
+ configured: hasConfiguredWebexChannel(cfg),
49
+ }),
50
+ resolveAccessorAccount: ({ cfg }) => resolveWebexChannelConfig(cfg),
51
+ resolveAllowFrom: () => undefined,
52
+ resolveDefaultTo: (account) => account?.defaultTo,
53
+ isConfigured: (_account, cfg) => hasConfiguredWebexChannel(cfg),
54
+ },
55
+ gateway: {
56
+ startAccount: async (ctx) => {
57
+ setWebexRuntime(ctx?.runtime ?? ctx);
58
+ webexLogInfo("webex gateway startAccount begin", {
59
+ accountId: String(ctx.accountId ?? "default"),
60
+ });
61
+ if (state.provider) {
62
+ webexLogDebug("webex gateway shutting down previous provider before restart");
63
+ await state.provider.shutdown();
64
+ state.provider = undefined;
65
+ }
66
+ let provider;
67
+ try {
68
+ provider = await monitorWebexProvider({
69
+ cfg: ctx.cfg,
70
+ abortSignal: ctx.abortSignal,
71
+ });
72
+ }
73
+ catch (err) {
74
+ webexLogError("webex gateway startAccount failed", { error: String(err) });
75
+ throw err;
76
+ }
77
+ state.provider = provider ?? undefined;
78
+ const configured = hasConfiguredWebexChannel(ctx.cfg);
79
+ ctx.setStatus({ accountId: ctx.accountId, configured, running: Boolean(provider) });
80
+ webexLogInfo("webex gateway status updated", {
81
+ accountId: String(ctx.accountId ?? "default"),
82
+ configured,
83
+ running: Boolean(provider),
84
+ });
85
+ if (!provider) {
86
+ return null;
87
+ }
88
+ try {
89
+ await waitUntilAbort(ctx.abortSignal);
90
+ return null;
91
+ }
92
+ finally {
93
+ if (state.provider) {
94
+ webexLogInfo("webex gateway abort detected; shutting down provider", {
95
+ accountId: String(ctx.accountId ?? "default"),
96
+ });
97
+ await state.provider.shutdown();
98
+ state.provider = undefined;
99
+ }
100
+ webexLogInfo("webex gateway startAccount complete", {
101
+ accountId: String(ctx.accountId ?? "default"),
102
+ });
103
+ }
104
+ },
105
+ },
106
+ outbound: {
107
+ deliveryMode: "direct",
108
+ sendText: async (params) => {
109
+ const provider = state.provider;
110
+ if (!provider) {
111
+ throw new Error("Webex provider not running.");
112
+ }
113
+ const cfg = resolveWebexChannelConfig(params.cfg);
114
+ const to = (params.to || cfg.defaultTo || "").trim();
115
+ if (!to) {
116
+ throw new Error("Missing Webex target. Provide `to` or configure channels.webex.defaultTo.");
117
+ }
118
+ await sendTextWebex({
119
+ frameworkRuntime: provider.runtime,
120
+ to,
121
+ text: params.text,
122
+ });
123
+ webexLogDebug("webex outbound sent", { to });
124
+ return { messageId: "framework", conversationId: to };
125
+ },
126
+ },
127
+ },
128
+ });
129
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export type WebexChannelConfig = {
2
+ enabled?: boolean;
3
+ token?: string;
4
+ webhookUrl?: string;
5
+ defaultTo?: string;
6
+ listenPort?: number;
7
+ };
8
+ type JsonSchemaObject = {
9
+ type: "object";
10
+ additionalProperties?: boolean;
11
+ properties?: Record<string, unknown>;
12
+ required?: string[];
13
+ };
14
+ export declare const WebexChannelConfigSchema: JsonSchemaObject;
15
+ export declare function resolveWebexChannelConfig(cfg: unknown): WebexChannelConfig;
16
+ export declare function hasConfiguredWebexChannel(cfg: unknown): boolean;
17
+ export declare function parseWebhookUrl(raw: string): {
18
+ url: URL;
19
+ path: string;
20
+ port?: number;
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=config-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-schema.d.ts","sourceRoot":"","sources":["../../src/config-schema.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,QAAQ,CAAC;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAiCF,eAAO,MAAM,wBAAwB,kBAAS,CAAC;AAE/C,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,CAG1E;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAG/D;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAKtF"}
@@ -0,0 +1,45 @@
1
+ const schema = {
2
+ type: "object",
3
+ additionalProperties: false,
4
+ properties: {
5
+ enabled: { type: "boolean", default: true },
6
+ token: {
7
+ type: "string",
8
+ minLength: 1,
9
+ description: "Webex bot access token.",
10
+ },
11
+ webhookUrl: {
12
+ type: "string",
13
+ minLength: 1,
14
+ description: "Public Webex webhook callback URL for this plugin.",
15
+ },
16
+ defaultTo: {
17
+ type: "string",
18
+ minLength: 1,
19
+ description: "Default Webex roomId target for outbound messages.",
20
+ },
21
+ listenPort: {
22
+ type: "number",
23
+ minimum: 1,
24
+ maximum: 65535,
25
+ description: "Local HTTP listen port override for the Webex webhook server (useful when 3978 is already in use).",
26
+ },
27
+ },
28
+ required: ["token", "webhookUrl"],
29
+ };
30
+ export const WebexChannelConfigSchema = schema;
31
+ export function resolveWebexChannelConfig(cfg) {
32
+ const channelCfg = cfg?.channels?.webex;
33
+ return channelCfg ?? {};
34
+ }
35
+ export function hasConfiguredWebexChannel(cfg) {
36
+ const channelCfg = resolveWebexChannelConfig(cfg);
37
+ return Boolean(channelCfg.token?.trim() && channelCfg.webhookUrl?.trim());
38
+ }
39
+ export function parseWebhookUrl(raw) {
40
+ const url = new URL(raw);
41
+ const path = url.pathname?.trim() ? url.pathname : "/webex/webhook";
42
+ const port = url.port ? Number(url.port) : undefined;
43
+ return { url, path, port };
44
+ }
45
+ //# sourceMappingURL=config-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-schema.js","sourceRoot":"","sources":["../../src/config-schema.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,GAAqB;IAC/B,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,KAAK;IAC3B,UAAU,EAAE;QACV,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE;QAC3C,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,CAAC;YACZ,WAAW,EAAE,yBAAyB;SACvC;QACD,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,CAAC;YACZ,WAAW,EAAE,oDAAoD;SAClE;QACD,SAAS,EAAE;YACT,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,CAAC;YACZ,WAAW,EAAE,oDAAoD;SAClE;QACD,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,KAAK;YACd,WAAW,EACT,oGAAoG;SACvG;KACF;IACD,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC;CAClC,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAE/C,MAAM,UAAU,yBAAyB,CAAC,GAAY;IACpD,MAAM,UAAU,GAAI,GAAiE,EAAE,QAAQ,EAAE,KAAK,CAAC;IACvG,OAAO,UAAU,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,GAAY;IACpD,MAAM,UAAU,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC;IACpE,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { Express } from "express";
2
+ type FrameworkLike = {
3
+ email?: string;
4
+ start: () => Promise<boolean> | boolean;
5
+ stop: () => Promise<boolean> | boolean;
6
+ on: (eventName: string, cb: (...args: any[]) => void) => void;
7
+ hears?: (matcher: string | RegExp, cb: (...args: any[]) => void) => void;
8
+ getBotByRoomId: (roomId: string) => {
9
+ say: (message: string | Record<string, unknown>) => Promise<unknown>;
10
+ } | null;
11
+ getWebexSDK: () => {
12
+ messages: {
13
+ create: (payload: Record<string, unknown>) => Promise<unknown>;
14
+ };
15
+ };
16
+ };
17
+ export type WebexFrameworkConfig = {
18
+ token: string;
19
+ webhookUrl: string;
20
+ };
21
+ type AttachHandlersOpts = {
22
+ cfg: unknown;
23
+ send: (roomId: string, text: string) => Promise<void>;
24
+ };
25
+ export type WebexFrameworkRuntime = {
26
+ framework: FrameworkLike;
27
+ webhookMiddleware: (req: unknown, res: unknown, next?: (err?: unknown) => void) => void;
28
+ attachInboundHandlers: (opts: AttachHandlersOpts) => void;
29
+ };
30
+ export declare function createWebexFrameworkRuntime(config: WebexFrameworkConfig): WebexFrameworkRuntime;
31
+ export declare function sendFrameworkMessage(framework: FrameworkLike, to: string, text: string): Promise<void>;
32
+ export declare function bindFrameworkWebhook(app: Express, path: string, middleware: WebexFrameworkRuntime["webhookMiddleware"]): void;
33
+ export {};
34
+ //# sourceMappingURL=framework-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-runtime.d.ts","sourceRoot":"","sources":["../../src/framework-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA4BvC,KAAK,aAAa,GAAG;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACvC,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,IAAI,CAAC;IAC9D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,IAAI,CAAC;IACzE,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IACpH,WAAW,EAAE,MAAM;QACjB,QAAQ,EAAE;YACR,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;SAChE,CAAC;KACH,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,aAAa,CAAC;IACzB,iBAAiB,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;IACxF,qBAAqB,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAC3D,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,oBAAoB,GAAG,qBAAqB,CA4F/F;AAED,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,aAAa,EACxB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAG7H"}
@@ -0,0 +1,271 @@
1
+ import { createRequire } from "node:module";
2
+ import { dispatchInboundToAgent, webexLogDebug, webexLogError, webexLogInfo } from "./runtime.js";
3
+ const require = createRequire(import.meta.url);
4
+ function ensureNavigatorWritableForLegacyDeps() {
5
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis, "navigator");
6
+ if (!descriptor) {
7
+ return;
8
+ }
9
+ // Some legacy Webex transitive dependencies assign to globalThis.navigator.
10
+ // Newer Node versions expose navigator via a getter-only property.
11
+ const isGetterOnly = typeof descriptor.get === "function" && descriptor.set === undefined;
12
+ if (!isGetterOnly || descriptor.configurable !== true) {
13
+ return;
14
+ }
15
+ const current = descriptor.get?.call(globalThis);
16
+ Object.defineProperty(globalThis, "navigator", {
17
+ value: current,
18
+ writable: true,
19
+ configurable: true,
20
+ enumerable: descriptor.enumerable ?? true,
21
+ });
22
+ }
23
+ export function createWebexFrameworkRuntime(config) {
24
+ webexLogInfo("webex framework init starting", {
25
+ webhookHost: safeWebhookHost(config.webhookUrl),
26
+ hasToken: Boolean(config.token?.trim()),
27
+ });
28
+ ensureNavigatorWritableForLegacyDeps();
29
+ const FrameworkCtor = require("webex-node-bot-framework");
30
+ const webhookFactory = require("webex-node-bot-framework/webhook");
31
+ const framework = new FrameworkCtor({
32
+ token: config.token,
33
+ webhookUrl: config.webhookUrl,
34
+ // Prevent startup discovery from spawning bot objects in existing/default rooms.
35
+ maxStartupSpaces: 0,
36
+ });
37
+ framework.on("log", (msg) => {
38
+ webexLogDebug("webex framework log", { msg: String(msg ?? "") });
39
+ });
40
+ framework.on("start", () => {
41
+ webexLogInfo("webex framework start event");
42
+ });
43
+ framework.on("initialized", () => {
44
+ webexLogInfo("webex framework initialized event");
45
+ });
46
+ framework.on("stop", () => {
47
+ webexLogInfo("webex framework stop event");
48
+ });
49
+ framework.on("spawn", (_bot, id, addedBy) => {
50
+ webexLogDebug("webex framework spawn event", {
51
+ id: String(id ?? ""),
52
+ hasAddedBy: Boolean(addedBy),
53
+ });
54
+ });
55
+ const webhookMiddleware = webhookFactory(framework);
56
+ const attachInboundHandlers = (opts) => {
57
+ if (typeof framework.hears === "function") {
58
+ framework.hears(/[\s\S]*/, () => {
59
+ // Keep this empty catch-all so the framework does not emit "No Hears Called" noise.
60
+ });
61
+ webexLogDebug("webex framework hears catch-all registered");
62
+ }
63
+ framework.on("message", (_bot, trigger) => {
64
+ const botEmail = framework.email?.toLowerCase().trim();
65
+ const senderEmail = String(trigger?.person?.emails?.[0] ?? trigger?.personEmail ?? "").toLowerCase().trim();
66
+ if (botEmail && senderEmail && botEmail === senderEmail) {
67
+ webexLogDebug("webex inbound ignored bot-authored message", { senderEmail });
68
+ return;
69
+ }
70
+ const text = String(trigger?.message?.text ?? "").trim();
71
+ const roomId = String(trigger?.message?.roomId ?? "").trim();
72
+ if (!text || !roomId) {
73
+ webexLogDebug("webex inbound ignored: missing text or roomId", {
74
+ hasText: Boolean(text),
75
+ hasRoomId: Boolean(roomId),
76
+ });
77
+ return;
78
+ }
79
+ webexLogDebug("webex inbound received", {
80
+ roomId,
81
+ messageId: typeof trigger?.message?.id === "string" ? trigger.message.id : undefined,
82
+ senderEmail,
83
+ textLength: text.length,
84
+ textPreview: truncate(text, 280),
85
+ });
86
+ const event = {
87
+ text,
88
+ roomId,
89
+ personId: typeof trigger?.personId === "string" ? trigger.personId : undefined,
90
+ personEmail: senderEmail || undefined,
91
+ messageId: typeof trigger?.message?.id === "string" ? trigger.message.id : undefined,
92
+ raw: trigger,
93
+ };
94
+ void dispatchInboundToAgent(event, opts.cfg, (text) => opts.send(event.roomId, text)).catch((err) => {
95
+ webexLogError("webex inbound dispatch failed", { error: String(err) });
96
+ });
97
+ });
98
+ };
99
+ return {
100
+ framework,
101
+ webhookMiddleware,
102
+ attachInboundHandlers,
103
+ };
104
+ }
105
+ export async function sendFrameworkMessage(framework, to, text) {
106
+ webexLogDebug("webex outbound dispatch begin", {
107
+ target: to,
108
+ textLength: text.length,
109
+ });
110
+ const bot = framework.getBotByRoomId(to);
111
+ if (bot) {
112
+ const callMeta = {
113
+ api: "bot.say",
114
+ target: to,
115
+ textLength: text.length,
116
+ textPreview: truncate(text, 280),
117
+ };
118
+ await retryWebexApiCall(callMeta, () => bot.say(text));
119
+ webexLogInfo("webex outbound sent via bot room context", { target: to });
120
+ return;
121
+ }
122
+ const sdk = framework.getWebexSDK();
123
+ if (to.startsWith("person:")) {
124
+ const payload = { toPersonId: to.slice("person:".length), markdown: text };
125
+ const callMeta = {
126
+ api: "messages.create",
127
+ mode: "direct",
128
+ target: payload.toPersonId,
129
+ textLength: text.length,
130
+ textPreview: truncate(text, 280),
131
+ };
132
+ await retryWebexApiCall(callMeta, () => sdk.messages.create(payload));
133
+ webexLogInfo("webex outbound sent via direct person target", { target: to });
134
+ return;
135
+ }
136
+ const payload = { roomId: to, markdown: text };
137
+ const callMeta = {
138
+ api: "messages.create",
139
+ mode: "room",
140
+ target: payload.roomId,
141
+ textLength: text.length,
142
+ textPreview: truncate(text, 280),
143
+ };
144
+ await retryWebexApiCall(callMeta, () => sdk.messages.create(payload));
145
+ webexLogInfo("webex outbound sent via room target", { target: to });
146
+ }
147
+ export function bindFrameworkWebhook(app, path, middleware) {
148
+ webexLogDebug("webex webhook route bound", { path });
149
+ app.post(path, (req, res, next) => middleware(req, res, next));
150
+ }
151
+ function safeWebhookHost(rawUrl) {
152
+ try {
153
+ return new URL(rawUrl).host;
154
+ }
155
+ catch {
156
+ return "invalid";
157
+ }
158
+ }
159
+ function truncate(text, maxLength) {
160
+ return text.length <= maxLength ? text : `${text.slice(0, Math.max(0, maxLength - 1))}...`;
161
+ }
162
+ async function retryWebexApiCall(meta, operation) {
163
+ const maxAttempts = 4;
164
+ let lastError;
165
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
166
+ webexLogInfo("webex api call begin", {
167
+ ...meta,
168
+ attempt,
169
+ maxAttempts,
170
+ });
171
+ try {
172
+ const result = await operation();
173
+ webexLogInfo("webex api call success", {
174
+ api: meta.api,
175
+ mode: meta.mode,
176
+ target: meta.target,
177
+ attempt,
178
+ });
179
+ return result;
180
+ }
181
+ catch (err) {
182
+ lastError = err;
183
+ const retriable = isRetriableWebexError(err);
184
+ const isLastAttempt = attempt >= maxAttempts;
185
+ webexLogError("webex api call failed", {
186
+ api: meta.api,
187
+ mode: meta.mode,
188
+ target: meta.target,
189
+ attempt,
190
+ maxAttempts,
191
+ retriable,
192
+ error: formatErrorForLog(err),
193
+ });
194
+ if (!retriable || isLastAttempt) {
195
+ throw err;
196
+ }
197
+ const delayMs = getRetryDelayMs(attempt);
198
+ webexLogInfo("webex api call retry scheduled", {
199
+ api: meta.api,
200
+ mode: meta.mode,
201
+ target: meta.target,
202
+ nextAttempt: attempt + 1,
203
+ delayMs,
204
+ });
205
+ await sleep(delayMs);
206
+ }
207
+ }
208
+ throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "Unknown Webex API error"));
209
+ }
210
+ function isRetriableWebexError(err) {
211
+ const details = extractErrorDetails(err);
212
+ if (details.statusCode !== undefined) {
213
+ if (details.statusCode === 429 || details.statusCode >= 500) {
214
+ return true;
215
+ }
216
+ return false;
217
+ }
218
+ const haystack = [details.message, details.code, details.causeMessage]
219
+ .filter((value) => Boolean(value))
220
+ .join(" ")
221
+ .toLowerCase();
222
+ return [
223
+ "networkorcorserror",
224
+ "socket hang up",
225
+ "tunneling socket could not be established",
226
+ "econnreset",
227
+ "etimedout",
228
+ "econnrefused",
229
+ "timeout",
230
+ "temporarily unavailable",
231
+ "fetch failed",
232
+ "network error",
233
+ ].some((needle) => haystack.includes(needle));
234
+ }
235
+ function extractErrorDetails(err) {
236
+ if (!err || typeof err !== "object") {
237
+ return { message: String(err ?? "") };
238
+ }
239
+ const record = err;
240
+ const cause = record.cause && typeof record.cause === "object" ? record.cause : undefined;
241
+ const response = record.response && typeof record.response === "object" ? record.response : undefined;
242
+ return {
243
+ message: typeof record.message === "string" ? record.message : String(err),
244
+ code: typeof record.code === "string" ? record.code : undefined,
245
+ causeMessage: typeof cause?.message === "string" ? cause.message : undefined,
246
+ statusCode: typeof record.statusCode === "number"
247
+ ? record.statusCode
248
+ : typeof record.status === "number"
249
+ ? record.status
250
+ : typeof response?.statusCode === "number"
251
+ ? response.statusCode
252
+ : typeof response?.status === "number"
253
+ ? response.status
254
+ : undefined,
255
+ };
256
+ }
257
+ function formatErrorForLog(err) {
258
+ const details = extractErrorDetails(err);
259
+ return [details.message, details.code, details.causeMessage].filter(Boolean).join(" | ");
260
+ }
261
+ function getRetryDelayMs(attempt) {
262
+ const baseMs = 750;
263
+ const jitterMs = Math.floor(Math.random() * 250);
264
+ return baseMs * 2 ** (attempt - 1) + jitterMs;
265
+ }
266
+ async function sleep(delayMs) {
267
+ await new Promise((resolve) => {
268
+ setTimeout(resolve, delayMs);
269
+ });
270
+ }
271
+ //# sourceMappingURL=framework-runtime.js.map