@alfe.ai/openclaw-whatsapp 0.0.26 → 0.0.28
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 +24 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin.cjs +2 -111
- package/dist/plugin.d.cts +13 -34
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.ts +13 -34
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -114
- package/dist/plugin2.cjs +291 -0
- package/dist/plugin2.d.cts +2 -2
- package/dist/plugin2.d.ts +2 -2
- package/dist/plugin2.js +288 -0
- package/dist/plugin2.js.map +1 -0
- package/package.json +8 -6
- package/dist/plugin.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @alfe.ai/openclaw-whatsapp
|
|
2
|
+
|
|
3
|
+
OpenClaw tools for sending free-form and approved-template WhatsApp messages
|
|
4
|
+
through an Alfe agent's active Twilio number, plus a bounded template catalog.
|
|
5
|
+
|
|
6
|
+
The plugin registers `whatsapp_send_message`, `whatsapp_send_template`, and
|
|
7
|
+
`whatsapp_list_templates` synchronously on every OpenClaw registry pass. Local
|
|
8
|
+
config resolves only when a tool executes, so transient config problems do not
|
|
9
|
+
remove the catalog.
|
|
10
|
+
|
|
11
|
+
## Boundaries
|
|
12
|
+
|
|
13
|
+
- Recipients must be E.164 numbers. Free-form bodies, Content SIDs, previews,
|
|
14
|
+
and template-variable maps mirror the independent `services/mobile` limits.
|
|
15
|
+
- A free-form send fails closed unless the session lookup proves the recipient's
|
|
16
|
+
24-hour WhatsApp window is active. The service repeats this check before
|
|
17
|
+
billing and Twilio delivery.
|
|
18
|
+
- Send/session acknowledgements and template responses are runtime-projected.
|
|
19
|
+
At most 200 templates enter model context, with unknown provider fields
|
|
20
|
+
removed and provider-authored text labeled untrusted.
|
|
21
|
+
- Raw config, Agent API, and Twilio diagnostics are never model-visible or
|
|
22
|
+
written to plugin logs.
|
|
23
|
+
|
|
24
|
+
See [Alfe](https://alfe.ai) and the [documentation](https://docs.alfe.ai).
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const require_plugin = require("./
|
|
2
|
-
module.exports = require_plugin;
|
|
1
|
+
const require_plugin = require("./plugin2.cjs");
|
|
2
|
+
module.exports = require_plugin.plugin_default;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
export {
|
|
1
|
+
import { t as _default } from "./plugin.cjs";
|
|
2
|
+
export { _default as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
export {
|
|
1
|
+
import { t as _default } from "./plugin.js";
|
|
2
|
+
export { _default as default };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
export {
|
|
1
|
+
import { t as plugin_default } from "./plugin2.js";
|
|
2
|
+
export { plugin_default as default };
|
package/dist/plugin.cjs
CHANGED
|
@@ -1,111 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
|
|
4
|
-
let _alfe_ai_openclaw_plugin_kit = require("@alfe.ai/openclaw-plugin-kit");
|
|
5
|
-
//#region src/plugin.ts
|
|
6
|
-
/**
|
|
7
|
-
* @alfe/openclaw-whatsapp — OpenClaw native plugin
|
|
8
|
-
*
|
|
9
|
-
* Registers WhatsApp tools with OpenClaw. Tools call the Twilio service
|
|
10
|
-
* API using the agent's API key for authentication.
|
|
11
|
-
*
|
|
12
|
-
* This plugin provides:
|
|
13
|
-
* - whatsapp_send_message — send a free-form WhatsApp message (requires active session)
|
|
14
|
-
* - whatsapp_send_template — send a template message to initiate conversation
|
|
15
|
-
* - whatsapp_list_templates — list available approved templates
|
|
16
|
-
*/
|
|
17
|
-
const pkg = (0, require("node:module").createRequire)(require("url").pathToFileURL(__filename).href)("../package.json");
|
|
18
|
-
const WHATSAPP_ACTIVATION_KEY = (0, _alfe_ai_openclaw_plugin_kit.getActivationKey)("whatsapp");
|
|
19
|
-
let client;
|
|
20
|
-
function getClient() {
|
|
21
|
-
if (!client) throw new Error("WhatsApp tools unavailable — no API config resolved");
|
|
22
|
-
return client;
|
|
23
|
-
}
|
|
24
|
-
const whatsappTools = [
|
|
25
|
-
(0, _alfe_ai_openclaw_plugin_kit.defineTool)({
|
|
26
|
-
name: "whatsapp_send_message",
|
|
27
|
-
description: "Send a free-form WhatsApp message from your phone number. This only works if the recipient has messaged you within the last 24 hours (active session). If there is no active session, you must use whatsapp_send_template to initiate the conversation first.",
|
|
28
|
-
parameters: _sinclair_typebox.Type.Object({
|
|
29
|
-
to: _sinclair_typebox.Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
|
|
30
|
-
body: _sinclair_typebox.Type.String({ description: "The message content to send" })
|
|
31
|
-
}),
|
|
32
|
-
handler: async (params) => {
|
|
33
|
-
const { to, body } = params;
|
|
34
|
-
try {
|
|
35
|
-
if (!(await getClient().getWhatsAppSession(to)).active) throw new Error("No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation first, then the user can reply and you can send free-form messages.");
|
|
36
|
-
} catch (e) {
|
|
37
|
-
const msg = e.message;
|
|
38
|
-
if (msg.includes("No active WhatsApp session") || msg.includes("whatsapp_send_template")) throw e;
|
|
39
|
-
}
|
|
40
|
-
return getClient().sendWhatsAppMessage({
|
|
41
|
-
to,
|
|
42
|
-
body
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}),
|
|
46
|
-
(0, _alfe_ai_openclaw_plugin_kit.defineTool)({
|
|
47
|
-
name: "whatsapp_send_template",
|
|
48
|
-
description: "Send a pre-approved WhatsApp template message to initiate a conversation. Use this when you need to message a user who hasn't contacted you recently (no active 24-hour session). First call whatsapp_list_templates to see available templates and their variables, then call this tool with the chosen template. After the user replies to your template, you can use whatsapp_send_message for free-form messages.",
|
|
49
|
-
parameters: _sinclair_typebox.Type.Object({
|
|
50
|
-
to: _sinclair_typebox.Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
|
|
51
|
-
contentSid: _sinclair_typebox.Type.String({ description: "The template content SID (from whatsapp_list_templates)" }),
|
|
52
|
-
contentVariables: _sinclair_typebox.Type.Object({}, {
|
|
53
|
-
description: "Variables to fill in the template, e.g. {\"1\": \"Kevin\", \"2\": \"Agent Name\"}",
|
|
54
|
-
additionalProperties: _sinclair_typebox.Type.String()
|
|
55
|
-
}),
|
|
56
|
-
bodyPreview: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "The filled template text for conversation context" }))
|
|
57
|
-
}),
|
|
58
|
-
handler: async (params) => {
|
|
59
|
-
const { to, contentSid, contentVariables, bodyPreview } = params;
|
|
60
|
-
return getClient().sendWhatsAppTemplate({
|
|
61
|
-
to,
|
|
62
|
-
contentSid,
|
|
63
|
-
contentVariables,
|
|
64
|
-
bodyPreview
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}),
|
|
68
|
-
(0, _alfe_ai_openclaw_plugin_kit.defineTool)({
|
|
69
|
-
name: "whatsapp_list_templates",
|
|
70
|
-
description: "List all approved WhatsApp message templates available for outreach. Each template has a contentSid, name, body with {{1}}, {{2}} placeholders, and variables showing what each placeholder represents. Use this to find the right template before calling whatsapp_send_template.",
|
|
71
|
-
parameters: _sinclair_typebox.Type.Object({}),
|
|
72
|
-
handler: async () => {
|
|
73
|
-
return getClient().listWhatsAppTemplates();
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
];
|
|
77
|
-
const plugin = {
|
|
78
|
-
id: "@alfe.ai/openclaw-whatsapp",
|
|
79
|
-
name: "Alfe WhatsApp Plugin",
|
|
80
|
-
description: "WhatsApp integration — messaging and templates via Twilio",
|
|
81
|
-
version: pkg.version,
|
|
82
|
-
activate(api) {
|
|
83
|
-
(0, _alfe_ai_agent_api_client.installToolErrorCapture)(api, { plugin: "openclaw-whatsapp" });
|
|
84
|
-
const log = api.logger;
|
|
85
|
-
for (const tool of whatsappTools) api.registerTool(tool);
|
|
86
|
-
log.info(`Registered ${String(whatsappTools.length)} WhatsApp tools: ${whatsappTools.map((t) => t.name).join(", ")}`);
|
|
87
|
-
(0, _alfe_ai_openclaw_plugin_kit.guardedStart)(WHATSAPP_ACTIVATION_KEY, log, () => {
|
|
88
|
-
log.info("Alfe WhatsApp plugin activating...");
|
|
89
|
-
try {
|
|
90
|
-
const config = (0, _alfe_ai_config.resolveConfig)();
|
|
91
|
-
client = new _alfe_ai_agent_api_client.AgentApiClient({
|
|
92
|
-
apiUrl: config.apiUrl,
|
|
93
|
-
apiKey: config.apiKey
|
|
94
|
-
});
|
|
95
|
-
log.info(`WhatsApp API: ${config.apiUrl}`);
|
|
96
|
-
} catch (err) {
|
|
97
|
-
log.error(`Failed to resolve config: ${err instanceof Error ? err.message : String(err)}`);
|
|
98
|
-
log.warn("WhatsApp tools will fail — no API config available");
|
|
99
|
-
(0, _alfe_ai_openclaw_plugin_kit.resetActivation)(WHATSAPP_ACTIVATION_KEY);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
log.info("Alfe WhatsApp plugin activated");
|
|
103
|
-
},
|
|
104
|
-
deactivate(api) {
|
|
105
|
-
client = void 0;
|
|
106
|
-
(0, _alfe_ai_openclaw_plugin_kit.resetActivation)(WHATSAPP_ACTIVATION_KEY);
|
|
107
|
-
api.logger.info("Alfe WhatsApp plugin deactivated");
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
//#endregion
|
|
111
|
-
module.exports = plugin;
|
|
1
|
+
const require_plugin = require("./plugin2.cjs");
|
|
2
|
+
module.exports = require_plugin.plugin_default;
|
package/dist/plugin.d.cts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { TSchema } from "@sinclair/typebox";
|
|
2
|
-
|
|
3
1
|
//#region ../openclaw-plugin-kit/dist/index.d.ts
|
|
4
2
|
|
|
5
3
|
//# sourceMappingURL=types.d.ts.map
|
|
6
4
|
//#endregion
|
|
7
5
|
//#region src/tools.d.ts
|
|
8
|
-
/**
|
|
9
|
-
* Tool-definition helpers — the `defineTool` / `ok` / `errResult` trio that
|
|
10
|
-
* was copy-pasted across 8 plugins (identity, google, teams, mobile,
|
|
11
|
-
* whatsapp, voice, chat a2a-tools, base openclaw).
|
|
12
|
-
*
|
|
13
|
-
* Error handling is standardized on the openclaw-google variant — the only
|
|
14
|
-
* copy that survived non-`Error` throws (`e instanceof Error ? e.message :
|
|
15
|
-
* "Unknown error"`). The other copies did `(e as Error).message`, which
|
|
16
|
-
* crashes the tool executor when a handler throws a string/object.
|
|
17
|
-
*/
|
|
18
6
|
/** Shape returned to OpenClaw from a tool `execute`. */
|
|
19
7
|
interface ToolResult {
|
|
20
8
|
content: {
|
|
@@ -22,15 +10,8 @@ interface ToolResult {
|
|
|
22
10
|
text: string;
|
|
23
11
|
}[];
|
|
24
12
|
details: unknown;
|
|
13
|
+
isError?: boolean;
|
|
25
14
|
}
|
|
26
|
-
/**
|
|
27
|
-
* An OpenClaw tool definition.
|
|
28
|
-
*
|
|
29
|
-
* `parameters` is generic because the fleet is split between TypeBox
|
|
30
|
-
* `TSchema` schemas (identity/google/teams/mobile/whatsapp) and plain
|
|
31
|
-
* JSON-Schema objects (chat a2a-tools). Instantiate with whichever schema
|
|
32
|
-
* type the plugin uses — the kit itself has no schema dependency.
|
|
33
|
-
*/
|
|
34
15
|
interface ToolDef<TParameters = unknown> {
|
|
35
16
|
name: string;
|
|
36
17
|
description: string;
|
|
@@ -38,25 +19,23 @@ interface ToolDef<TParameters = unknown> {
|
|
|
38
19
|
parameters: TParameters;
|
|
39
20
|
execute: (toolCallId: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
40
21
|
}
|
|
41
|
-
/**
|
|
22
|
+
/** Deliberately model-safe validation/usage failure. Other exceptions stay private. */
|
|
42
23
|
//#endregion
|
|
43
|
-
//#region src/
|
|
24
|
+
//#region src/types.d.ts
|
|
44
25
|
interface Logger {
|
|
45
|
-
info(
|
|
46
|
-
warn(
|
|
47
|
-
error(
|
|
48
|
-
debug(
|
|
26
|
+
info(message: string, ...args: unknown[]): void;
|
|
27
|
+
warn(message: string, ...args: unknown[]): void;
|
|
28
|
+
error(message: string, ...args: unknown[]): void;
|
|
29
|
+
debug(message: string, ...args: unknown[]): void;
|
|
49
30
|
}
|
|
50
31
|
interface OpenClawPluginApi {
|
|
51
32
|
logger: Logger;
|
|
52
|
-
|
|
53
|
-
registerTool(tool: ToolDef<TSchema>): void;
|
|
54
|
-
registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
|
|
55
|
-
on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
|
|
56
|
-
priority?: number;
|
|
57
|
-
}): void;
|
|
33
|
+
registerTool(tool: ToolDef): void;
|
|
58
34
|
}
|
|
59
|
-
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/plugin.d.ts
|
|
38
|
+
declare const _default: {
|
|
60
39
|
id: string;
|
|
61
40
|
name: string;
|
|
62
41
|
description: string;
|
|
@@ -65,5 +44,5 @@ declare const plugin: {
|
|
|
65
44
|
deactivate(api: OpenClawPluginApi): void;
|
|
66
45
|
};
|
|
67
46
|
//#endregion
|
|
68
|
-
export {
|
|
47
|
+
export { _default as t };
|
|
69
48
|
//# sourceMappingURL=plugin.d.cts.map
|
package/dist/plugin.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.cts","names":["KitLogger","ToolResult","ToolDef","TParameters","Record","Promise","ok","errResult","defineTool","IpcResponse","IpcClient","ConnectToDaemonOptions","connectToDaemon","ResolveOpenClawSdkOptions","resolveOpenClawSdk","T","getActivationKey","isActivated","resetActivation","guardedStart"
|
|
1
|
+
{"version":3,"file":"plugin.d.cts","names":["KitLogger","ToolResult","ToolDef","TParameters","Record","Promise","PublicToolError","Error","publicToolError","ok","errResult","defineTool","IpcResponse","IpcClient","IpcModule","ConnectToDaemonOptions","ConnectToDaemonDependencies","connectToDaemon","ResolveOpenClawSdkOptions","ResolveOpenClawSdkDependencies","resolveOpenClawSdk","T","getActivationKey","isActivated","resetActivation","guardedStart"],"sources":["../../openclaw-plugin-kit/dist/index.d.ts","../src/types.ts","../src/plugin.ts"],"sourcesContent":["//#region src/types.d.ts\n/**\n * Minimal logger contract shared by every helper in the kit.\n *\n * Deliberately the *narrowest* shape in the fleet: some plugins declare\n * variadic loggers (`info(msg: string, ...args: unknown[])`), others\n * single-arg (`info(msg: string)`). Both are assignable to this. The kit\n * only ever calls with a single string.\n */\ninterface KitLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n debug(msg: string): void;\n}\n//# sourceMappingURL=types.d.ts.map\n//#endregion\n//#region src/tools.d.ts\n/** Shape returned to OpenClaw from a tool `execute`. */\ninterface ToolResult {\n content: {\n type: \"text\";\n text: string;\n }[];\n details: unknown;\n isError?: boolean;\n}\ninterface ToolDef<TParameters = unknown> {\n name: string;\n description: string;\n label: string;\n parameters: TParameters;\n execute: (toolCallId: string, params: Record<string, unknown>) => Promise<ToolResult>;\n}\n/** Deliberately model-safe validation/usage failure. Other exceptions stay private. */\ndeclare class PublicToolError extends Error {\n readonly name = \"PublicToolError\";\n}\ndeclare function publicToolError(message: string): PublicToolError;\n/** Wrap bounded JSON in the OpenClaw tool-result envelope. */\ndeclare function ok(data: unknown): ToolResult;\n/** Wrap an explicitly public error in a captured OpenClaw error envelope. */\ndeclare function errResult(message: string): ToolResult;\n/**\n * Define a tool whose unexpected exceptions never expose provider, filesystem,\n * credential, or transport diagnostics to the model. Throw `PublicToolError`\n * only for a message deliberately safe for model display.\n */\ndeclare function defineTool<TParameters>(def: {\n name: string;\n description: string;\n parameters: TParameters;\n handler: (params: Record<string, unknown>) => Promise<unknown>;\n}): ToolDef<TParameters>;\n//# sourceMappingURL=tools.d.ts.map\n//#endregion\n//#region src/daemon.d.ts\ninterface IpcResponse {\n ok: boolean;\n error?: {\n message?: string;\n };\n [key: string]: unknown;\n}\ninterface IpcClient {\n on(event: string, handler: (...args: unknown[]) => void): void;\n request(method: string, params: Record<string, unknown>): Promise<IpcResponse>;\n start(): void;\n stop(): void;\n}\ninterface IpcModule {\n IPCClient: new (socketPath: string, log: KitLogger) => IpcClient;\n}\ninterface ConnectToDaemonOptions {\n pluginId: string;\n capabilities?: readonly string[];\n onMessage?: (msg: Record<string, unknown>) => void;\n standaloneNote?: string;\n}\ninterface ConnectToDaemonDependencies {\n loadIpcModule?: () => Promise<IpcModule>;\n}\ndeclare function connectToDaemon(socketPath: string, log: KitLogger, options: ConnectToDaemonOptions, dependencies?: ConnectToDaemonDependencies): Promise<IpcClient | null>;\n//#endregion\n//#region src/sdk.d.ts\ninterface ResolveOpenClawSdkOptions {\n specifier?: string;\n exportName?: string;\n unresolvableNote?: string;\n}\ninterface ResolveOpenClawSdkDependencies {\n anchors?: readonly string[];\n globalPackageJsonPath?: string;\n requireFrom?: (anchor: string) => (specifier: string) => unknown;\n}\ndeclare function resolveOpenClawSdk<T = unknown>(log: KitLogger, options?: ResolveOpenClawSdkOptions, dependencies?: ResolveOpenClawSdkDependencies): T | null;\n//# sourceMappingURL=sdk.d.ts.map\n\n//#endregion\n//#region src/activation.d.ts\n/**\n * Canonical activation-flag key for a plugin: `__alfe<Name>PluginActivated`.\n *\n * Accepts a short name (`\"google\"`, `\"google-chat\"`) or a full package name\n * (`\"@alfe.ai/openclaw-teams\"`); scope + `openclaw-` prefix are stripped\n * and the remainder PascalCased:\n *\n * getActivationKey(\"google\") → \"__alfeGooglePluginActivated\"\n * getActivationKey(\"google-chat\") → \"__alfeGoogleChatPluginActivated\"\n * getActivationKey(\"@alfe.ai/openclaw-teams\") → \"__alfeTeamsPluginActivated\"\n */\ndeclare function getActivationKey(name: string): string;\n/** True when the activation flag for `key` is currently set. */\ndeclare function isActivated(key: string): boolean;\n/**\n * Clear the activation flag so a later start can run again.\n *\n * Call this LAST in stop/deactivate paths — after side effects are stopped\n * (see the ordering rule in the module doc). Also exported for soft-failure\n * paths inside a `guardedStart` fn that want to log at a custom level and\n * return normally instead of throwing.\n */\ndeclare function resetActivation(key: string): void;\n/**\n * Run a service start exactly once per activation cycle.\n *\n * - If the flag is already set, logs at debug and returns `false` (skipped).\n * - Otherwise sets the flag and runs `fn`.\n * - If `fn` throws synchronously OR returns a promise that rejects, the\n * flag is RESET (so a later activate can retry) and the error is logged.\n * Errors are not rethrown — matching fleet behavior where a failed start\n * must never crash the host's plugin loader.\n *\n * Returns `true` when the start was initiated (even if an async portion\n * later fails), `false` when skipped or when `fn` threw synchronously.\n *\n * Note: cleaning up partial side effects on failure is `fn`'s job (throw\n * only after tearing down what was started); the kit only guarantees the\n * flag reset happens after `fn` has failed — i.e. after `fn`'s own cleanup.\n */\ndeclare function guardedStart(key: string, log: KitLogger, fn: () => void | Promise<void>): boolean;\n//# sourceMappingURL=activation.d.ts.map\n\n//#endregion\nexport { type ConnectToDaemonDependencies, type ConnectToDaemonOptions, type IpcClient, type IpcResponse, type KitLogger, PublicToolError, type ResolveOpenClawSdkDependencies, type ResolveOpenClawSdkOptions, type ToolDef, type ToolResult, connectToDaemon, defineTool, errResult, getActivationKey, guardedStart, isActivated, ok, publicToolError, resetActivation, resolveOpenClawSdk };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;UAmBUC,UAAAA;;;;;;;;UAQAC;;;;cAIIC;wCAC0BC,4BAA4BC,QAAQJ;;;;;AAblEA,UCjBO,MAAA,CDiBG;EAQVC,IAAAA,CAAAA,OAAO,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAA,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAIHC,CAAAA,OAAAA,EAAAA,MAAAA,EAAAA,GAAAA,IAAAA,EAAAA,OAAAA,EAAAA,CAAAA,EAAAA,IAAAA;OAC0BC,CAAAA,OAAAA,EAAAA,MAAAA,EAAAA,GAAAA,IAAAA,EAAAA,OAAAA,EAAAA,CAAAA,EAAAA,IAAAA;;UCPvB,iBAAA;UACP;qBACW;;;;;;EDRXH,EAAAA,EAAAA,MAAAA;EAQAC,IAAAA,EAAAA,MAAO;EAAA,WAAA,EAAA,MAAA;SAIHC,EAAAA,MAAAA;UAC0BC,CAAAA,GAAAA,mBAAAA,CAAAA,EAAAA,IAAAA;YAAoCH,CAAAA,GAAAA,mBAAAA,CAAAA,EAAAA,IAAAA"}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { TSchema } from "@sinclair/typebox";
|
|
2
|
-
|
|
3
1
|
//#region ../openclaw-plugin-kit/dist/index.d.ts
|
|
4
2
|
|
|
5
3
|
//# sourceMappingURL=types.d.ts.map
|
|
6
4
|
//#endregion
|
|
7
5
|
//#region src/tools.d.ts
|
|
8
|
-
/**
|
|
9
|
-
* Tool-definition helpers — the `defineTool` / `ok` / `errResult` trio that
|
|
10
|
-
* was copy-pasted across 8 plugins (identity, google, teams, mobile,
|
|
11
|
-
* whatsapp, voice, chat a2a-tools, base openclaw).
|
|
12
|
-
*
|
|
13
|
-
* Error handling is standardized on the openclaw-google variant — the only
|
|
14
|
-
* copy that survived non-`Error` throws (`e instanceof Error ? e.message :
|
|
15
|
-
* "Unknown error"`). The other copies did `(e as Error).message`, which
|
|
16
|
-
* crashes the tool executor when a handler throws a string/object.
|
|
17
|
-
*/
|
|
18
6
|
/** Shape returned to OpenClaw from a tool `execute`. */
|
|
19
7
|
interface ToolResult {
|
|
20
8
|
content: {
|
|
@@ -22,15 +10,8 @@ interface ToolResult {
|
|
|
22
10
|
text: string;
|
|
23
11
|
}[];
|
|
24
12
|
details: unknown;
|
|
13
|
+
isError?: boolean;
|
|
25
14
|
}
|
|
26
|
-
/**
|
|
27
|
-
* An OpenClaw tool definition.
|
|
28
|
-
*
|
|
29
|
-
* `parameters` is generic because the fleet is split between TypeBox
|
|
30
|
-
* `TSchema` schemas (identity/google/teams/mobile/whatsapp) and plain
|
|
31
|
-
* JSON-Schema objects (chat a2a-tools). Instantiate with whichever schema
|
|
32
|
-
* type the plugin uses — the kit itself has no schema dependency.
|
|
33
|
-
*/
|
|
34
15
|
interface ToolDef<TParameters = unknown> {
|
|
35
16
|
name: string;
|
|
36
17
|
description: string;
|
|
@@ -38,25 +19,23 @@ interface ToolDef<TParameters = unknown> {
|
|
|
38
19
|
parameters: TParameters;
|
|
39
20
|
execute: (toolCallId: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
40
21
|
}
|
|
41
|
-
/**
|
|
22
|
+
/** Deliberately model-safe validation/usage failure. Other exceptions stay private. */
|
|
42
23
|
//#endregion
|
|
43
|
-
//#region src/
|
|
24
|
+
//#region src/types.d.ts
|
|
44
25
|
interface Logger {
|
|
45
|
-
info(
|
|
46
|
-
warn(
|
|
47
|
-
error(
|
|
48
|
-
debug(
|
|
26
|
+
info(message: string, ...args: unknown[]): void;
|
|
27
|
+
warn(message: string, ...args: unknown[]): void;
|
|
28
|
+
error(message: string, ...args: unknown[]): void;
|
|
29
|
+
debug(message: string, ...args: unknown[]): void;
|
|
49
30
|
}
|
|
50
31
|
interface OpenClawPluginApi {
|
|
51
32
|
logger: Logger;
|
|
52
|
-
|
|
53
|
-
registerTool(tool: ToolDef<TSchema>): void;
|
|
54
|
-
registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
|
|
55
|
-
on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
|
|
56
|
-
priority?: number;
|
|
57
|
-
}): void;
|
|
33
|
+
registerTool(tool: ToolDef): void;
|
|
58
34
|
}
|
|
59
|
-
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/plugin.d.ts
|
|
38
|
+
declare const _default: {
|
|
60
39
|
id: string;
|
|
61
40
|
name: string;
|
|
62
41
|
description: string;
|
|
@@ -65,5 +44,5 @@ declare const plugin: {
|
|
|
65
44
|
deactivate(api: OpenClawPluginApi): void;
|
|
66
45
|
};
|
|
67
46
|
//#endregion
|
|
68
|
-
export {
|
|
47
|
+
export { _default as t };
|
|
69
48
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","names":["KitLogger","ToolResult","ToolDef","TParameters","Record","Promise","ok","errResult","defineTool","IpcResponse","IpcClient","ConnectToDaemonOptions","connectToDaemon","ResolveOpenClawSdkOptions","resolveOpenClawSdk","T","getActivationKey","isActivated","resetActivation","guardedStart"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":["KitLogger","ToolResult","ToolDef","TParameters","Record","Promise","PublicToolError","Error","publicToolError","ok","errResult","defineTool","IpcResponse","IpcClient","IpcModule","ConnectToDaemonOptions","ConnectToDaemonDependencies","connectToDaemon","ResolveOpenClawSdkOptions","ResolveOpenClawSdkDependencies","resolveOpenClawSdk","T","getActivationKey","isActivated","resetActivation","guardedStart"],"sources":["../../openclaw-plugin-kit/dist/index.d.ts","../src/types.ts","../src/plugin.ts"],"sourcesContent":["//#region src/types.d.ts\n/**\n * Minimal logger contract shared by every helper in the kit.\n *\n * Deliberately the *narrowest* shape in the fleet: some plugins declare\n * variadic loggers (`info(msg: string, ...args: unknown[])`), others\n * single-arg (`info(msg: string)`). Both are assignable to this. The kit\n * only ever calls with a single string.\n */\ninterface KitLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n debug(msg: string): void;\n}\n//# sourceMappingURL=types.d.ts.map\n//#endregion\n//#region src/tools.d.ts\n/** Shape returned to OpenClaw from a tool `execute`. */\ninterface ToolResult {\n content: {\n type: \"text\";\n text: string;\n }[];\n details: unknown;\n isError?: boolean;\n}\ninterface ToolDef<TParameters = unknown> {\n name: string;\n description: string;\n label: string;\n parameters: TParameters;\n execute: (toolCallId: string, params: Record<string, unknown>) => Promise<ToolResult>;\n}\n/** Deliberately model-safe validation/usage failure. Other exceptions stay private. */\ndeclare class PublicToolError extends Error {\n readonly name = \"PublicToolError\";\n}\ndeclare function publicToolError(message: string): PublicToolError;\n/** Wrap bounded JSON in the OpenClaw tool-result envelope. */\ndeclare function ok(data: unknown): ToolResult;\n/** Wrap an explicitly public error in a captured OpenClaw error envelope. */\ndeclare function errResult(message: string): ToolResult;\n/**\n * Define a tool whose unexpected exceptions never expose provider, filesystem,\n * credential, or transport diagnostics to the model. Throw `PublicToolError`\n * only for a message deliberately safe for model display.\n */\ndeclare function defineTool<TParameters>(def: {\n name: string;\n description: string;\n parameters: TParameters;\n handler: (params: Record<string, unknown>) => Promise<unknown>;\n}): ToolDef<TParameters>;\n//# sourceMappingURL=tools.d.ts.map\n//#endregion\n//#region src/daemon.d.ts\ninterface IpcResponse {\n ok: boolean;\n error?: {\n message?: string;\n };\n [key: string]: unknown;\n}\ninterface IpcClient {\n on(event: string, handler: (...args: unknown[]) => void): void;\n request(method: string, params: Record<string, unknown>): Promise<IpcResponse>;\n start(): void;\n stop(): void;\n}\ninterface IpcModule {\n IPCClient: new (socketPath: string, log: KitLogger) => IpcClient;\n}\ninterface ConnectToDaemonOptions {\n pluginId: string;\n capabilities?: readonly string[];\n onMessage?: (msg: Record<string, unknown>) => void;\n standaloneNote?: string;\n}\ninterface ConnectToDaemonDependencies {\n loadIpcModule?: () => Promise<IpcModule>;\n}\ndeclare function connectToDaemon(socketPath: string, log: KitLogger, options: ConnectToDaemonOptions, dependencies?: ConnectToDaemonDependencies): Promise<IpcClient | null>;\n//#endregion\n//#region src/sdk.d.ts\ninterface ResolveOpenClawSdkOptions {\n specifier?: string;\n exportName?: string;\n unresolvableNote?: string;\n}\ninterface ResolveOpenClawSdkDependencies {\n anchors?: readonly string[];\n globalPackageJsonPath?: string;\n requireFrom?: (anchor: string) => (specifier: string) => unknown;\n}\ndeclare function resolveOpenClawSdk<T = unknown>(log: KitLogger, options?: ResolveOpenClawSdkOptions, dependencies?: ResolveOpenClawSdkDependencies): T | null;\n//# sourceMappingURL=sdk.d.ts.map\n\n//#endregion\n//#region src/activation.d.ts\n/**\n * Canonical activation-flag key for a plugin: `__alfe<Name>PluginActivated`.\n *\n * Accepts a short name (`\"google\"`, `\"google-chat\"`) or a full package name\n * (`\"@alfe.ai/openclaw-teams\"`); scope + `openclaw-` prefix are stripped\n * and the remainder PascalCased:\n *\n * getActivationKey(\"google\") → \"__alfeGooglePluginActivated\"\n * getActivationKey(\"google-chat\") → \"__alfeGoogleChatPluginActivated\"\n * getActivationKey(\"@alfe.ai/openclaw-teams\") → \"__alfeTeamsPluginActivated\"\n */\ndeclare function getActivationKey(name: string): string;\n/** True when the activation flag for `key` is currently set. */\ndeclare function isActivated(key: string): boolean;\n/**\n * Clear the activation flag so a later start can run again.\n *\n * Call this LAST in stop/deactivate paths — after side effects are stopped\n * (see the ordering rule in the module doc). Also exported for soft-failure\n * paths inside a `guardedStart` fn that want to log at a custom level and\n * return normally instead of throwing.\n */\ndeclare function resetActivation(key: string): void;\n/**\n * Run a service start exactly once per activation cycle.\n *\n * - If the flag is already set, logs at debug and returns `false` (skipped).\n * - Otherwise sets the flag and runs `fn`.\n * - If `fn` throws synchronously OR returns a promise that rejects, the\n * flag is RESET (so a later activate can retry) and the error is logged.\n * Errors are not rethrown — matching fleet behavior where a failed start\n * must never crash the host's plugin loader.\n *\n * Returns `true` when the start was initiated (even if an async portion\n * later fails), `false` when skipped or when `fn` threw synchronously.\n *\n * Note: cleaning up partial side effects on failure is `fn`'s job (throw\n * only after tearing down what was started); the kit only guarantees the\n * flag reset happens after `fn` has failed — i.e. after `fn`'s own cleanup.\n */\ndeclare function guardedStart(key: string, log: KitLogger, fn: () => void | Promise<void>): boolean;\n//# sourceMappingURL=activation.d.ts.map\n\n//#endregion\nexport { type ConnectToDaemonDependencies, type ConnectToDaemonOptions, type IpcClient, type IpcResponse, type KitLogger, PublicToolError, type ResolveOpenClawSdkDependencies, type ResolveOpenClawSdkOptions, type ToolDef, type ToolResult, connectToDaemon, defineTool, errResult, getActivationKey, guardedStart, isActivated, ok, publicToolError, resetActivation, resolveOpenClawSdk };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;UAmBUC,UAAAA;;;;;;;;UAQAC;;;;cAIIC;wCAC0BC,4BAA4BC,QAAQJ;;;;;AAblEA,UCjBO,MAAA,CDiBG;EAQVC,IAAAA,CAAAA,OAAO,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAA,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAIHC,CAAAA,OAAAA,EAAAA,MAAAA,EAAAA,GAAAA,IAAAA,EAAAA,OAAAA,EAAAA,CAAAA,EAAAA,IAAAA;OAC0BC,CAAAA,OAAAA,EAAAA,MAAAA,EAAAA,GAAAA,IAAAA,EAAAA,OAAAA,EAAAA,CAAAA,EAAAA,IAAAA;;UCPvB,iBAAA;UACP;qBACW;;;;;;EDRXH,EAAAA,EAAAA,MAAAA;EAQAC,IAAAA,EAAAA,MAAO;EAAA,WAAA,EAAA,MAAA;SAIHC,EAAAA,MAAAA;UAC0BC,CAAAA,GAAAA,mBAAAA,CAAAA,EAAAA,IAAAA;YAAoCH,CAAAA,GAAAA,mBAAAA,CAAAA,EAAAA,IAAAA"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,114 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { resolveConfig } from "@alfe.ai/config";
|
|
4
|
-
import { AgentApiClient, installToolErrorCapture } from "@alfe.ai/agent-api-client";
|
|
5
|
-
import { defineTool, getActivationKey, guardedStart, resetActivation } from "@alfe.ai/openclaw-plugin-kit";
|
|
6
|
-
//#region src/plugin.ts
|
|
7
|
-
/**
|
|
8
|
-
* @alfe/openclaw-whatsapp — OpenClaw native plugin
|
|
9
|
-
*
|
|
10
|
-
* Registers WhatsApp tools with OpenClaw. Tools call the Twilio service
|
|
11
|
-
* API using the agent's API key for authentication.
|
|
12
|
-
*
|
|
13
|
-
* This plugin provides:
|
|
14
|
-
* - whatsapp_send_message — send a free-form WhatsApp message (requires active session)
|
|
15
|
-
* - whatsapp_send_template — send a template message to initiate conversation
|
|
16
|
-
* - whatsapp_list_templates — list available approved templates
|
|
17
|
-
*/
|
|
18
|
-
const pkg = createRequire(import.meta.url)("../package.json");
|
|
19
|
-
const WHATSAPP_ACTIVATION_KEY = getActivationKey("whatsapp");
|
|
20
|
-
let client;
|
|
21
|
-
function getClient() {
|
|
22
|
-
if (!client) throw new Error("WhatsApp tools unavailable — no API config resolved");
|
|
23
|
-
return client;
|
|
24
|
-
}
|
|
25
|
-
const whatsappTools = [
|
|
26
|
-
defineTool({
|
|
27
|
-
name: "whatsapp_send_message",
|
|
28
|
-
description: "Send a free-form WhatsApp message from your phone number. This only works if the recipient has messaged you within the last 24 hours (active session). If there is no active session, you must use whatsapp_send_template to initiate the conversation first.",
|
|
29
|
-
parameters: Type.Object({
|
|
30
|
-
to: Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
|
|
31
|
-
body: Type.String({ description: "The message content to send" })
|
|
32
|
-
}),
|
|
33
|
-
handler: async (params) => {
|
|
34
|
-
const { to, body } = params;
|
|
35
|
-
try {
|
|
36
|
-
if (!(await getClient().getWhatsAppSession(to)).active) throw new Error("No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation first, then the user can reply and you can send free-form messages.");
|
|
37
|
-
} catch (e) {
|
|
38
|
-
const msg = e.message;
|
|
39
|
-
if (msg.includes("No active WhatsApp session") || msg.includes("whatsapp_send_template")) throw e;
|
|
40
|
-
}
|
|
41
|
-
return getClient().sendWhatsAppMessage({
|
|
42
|
-
to,
|
|
43
|
-
body
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}),
|
|
47
|
-
defineTool({
|
|
48
|
-
name: "whatsapp_send_template",
|
|
49
|
-
description: "Send a pre-approved WhatsApp template message to initiate a conversation. Use this when you need to message a user who hasn't contacted you recently (no active 24-hour session). First call whatsapp_list_templates to see available templates and their variables, then call this tool with the chosen template. After the user replies to your template, you can use whatsapp_send_message for free-form messages.",
|
|
50
|
-
parameters: Type.Object({
|
|
51
|
-
to: Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
|
|
52
|
-
contentSid: Type.String({ description: "The template content SID (from whatsapp_list_templates)" }),
|
|
53
|
-
contentVariables: Type.Object({}, {
|
|
54
|
-
description: "Variables to fill in the template, e.g. {\"1\": \"Kevin\", \"2\": \"Agent Name\"}",
|
|
55
|
-
additionalProperties: Type.String()
|
|
56
|
-
}),
|
|
57
|
-
bodyPreview: Type.Optional(Type.String({ description: "The filled template text for conversation context" }))
|
|
58
|
-
}),
|
|
59
|
-
handler: async (params) => {
|
|
60
|
-
const { to, contentSid, contentVariables, bodyPreview } = params;
|
|
61
|
-
return getClient().sendWhatsAppTemplate({
|
|
62
|
-
to,
|
|
63
|
-
contentSid,
|
|
64
|
-
contentVariables,
|
|
65
|
-
bodyPreview
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}),
|
|
69
|
-
defineTool({
|
|
70
|
-
name: "whatsapp_list_templates",
|
|
71
|
-
description: "List all approved WhatsApp message templates available for outreach. Each template has a contentSid, name, body with {{1}}, {{2}} placeholders, and variables showing what each placeholder represents. Use this to find the right template before calling whatsapp_send_template.",
|
|
72
|
-
parameters: Type.Object({}),
|
|
73
|
-
handler: async () => {
|
|
74
|
-
return getClient().listWhatsAppTemplates();
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
];
|
|
78
|
-
const plugin = {
|
|
79
|
-
id: "@alfe.ai/openclaw-whatsapp",
|
|
80
|
-
name: "Alfe WhatsApp Plugin",
|
|
81
|
-
description: "WhatsApp integration — messaging and templates via Twilio",
|
|
82
|
-
version: pkg.version,
|
|
83
|
-
activate(api) {
|
|
84
|
-
installToolErrorCapture(api, { plugin: "openclaw-whatsapp" });
|
|
85
|
-
const log = api.logger;
|
|
86
|
-
for (const tool of whatsappTools) api.registerTool(tool);
|
|
87
|
-
log.info(`Registered ${String(whatsappTools.length)} WhatsApp tools: ${whatsappTools.map((t) => t.name).join(", ")}`);
|
|
88
|
-
guardedStart(WHATSAPP_ACTIVATION_KEY, log, () => {
|
|
89
|
-
log.info("Alfe WhatsApp plugin activating...");
|
|
90
|
-
try {
|
|
91
|
-
const config = resolveConfig();
|
|
92
|
-
client = new AgentApiClient({
|
|
93
|
-
apiUrl: config.apiUrl,
|
|
94
|
-
apiKey: config.apiKey
|
|
95
|
-
});
|
|
96
|
-
log.info(`WhatsApp API: ${config.apiUrl}`);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
log.error(`Failed to resolve config: ${err instanceof Error ? err.message : String(err)}`);
|
|
99
|
-
log.warn("WhatsApp tools will fail — no API config available");
|
|
100
|
-
resetActivation(WHATSAPP_ACTIVATION_KEY);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
log.info("Alfe WhatsApp plugin activated");
|
|
104
|
-
},
|
|
105
|
-
deactivate(api) {
|
|
106
|
-
client = void 0;
|
|
107
|
-
resetActivation(WHATSAPP_ACTIVATION_KEY);
|
|
108
|
-
api.logger.info("Alfe WhatsApp plugin deactivated");
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
//#endregion
|
|
112
|
-
export { plugin as default };
|
|
113
|
-
|
|
114
|
-
//# sourceMappingURL=plugin.js.map
|
|
1
|
+
import { t as plugin_default } from "./plugin2.js";
|
|
2
|
+
export { plugin_default as default };
|
package/dist/plugin2.cjs
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
let node_module = require("node:module");
|
|
2
|
+
let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
|
|
3
|
+
let _alfe_ai_config = require("@alfe.ai/config");
|
|
4
|
+
let _alfe_ai_openclaw_plugin_kit = require("@alfe.ai/openclaw-plugin-kit");
|
|
5
|
+
let _sinclair_typebox = require("@sinclair/typebox");
|
|
6
|
+
const MAX_MESSAGE_CHARS = 4400;
|
|
7
|
+
const MAX_CONTENT_VARIABLE_VALUE_CHARS = 1024;
|
|
8
|
+
const MAX_BODY_PREVIEW_CHARS = 4e3;
|
|
9
|
+
const MAX_TEMPLATES = 200;
|
|
10
|
+
const MAX_INSPECTED_TEMPLATES = 1e3;
|
|
11
|
+
const MAX_TEMPLATE_SID_CHARS = 64;
|
|
12
|
+
const MAX_TEMPLATE_NAME_CHARS = 128;
|
|
13
|
+
const MAX_TEMPLATE_BODY_CHARS = 4096;
|
|
14
|
+
const MAX_TEMPLATE_LANGUAGE_CHARS = 16;
|
|
15
|
+
const MAX_TEMPLATE_CATEGORY_CHARS = 64;
|
|
16
|
+
const MAX_MESSAGE_SID_CHARS = 64;
|
|
17
|
+
const E164 = /^\+\d{7,15}$/u;
|
|
18
|
+
const FORBIDDEN_KEYS = new Set([
|
|
19
|
+
"__proto__",
|
|
20
|
+
"constructor",
|
|
21
|
+
"prototype"
|
|
22
|
+
]);
|
|
23
|
+
function parseMessageInput(params) {
|
|
24
|
+
requireExactFields(params, new Set(["to", "body"]), "whatsapp_send_message");
|
|
25
|
+
return {
|
|
26
|
+
to: requirePhone(params.to),
|
|
27
|
+
body: requireBoundedText(params.body, "body", MAX_MESSAGE_CHARS, true)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function parseTemplateInput(params) {
|
|
31
|
+
requireExactFields(params, new Set([
|
|
32
|
+
"to",
|
|
33
|
+
"contentSid",
|
|
34
|
+
"contentVariables",
|
|
35
|
+
"bodyPreview"
|
|
36
|
+
]), "whatsapp_send_template");
|
|
37
|
+
const bodyPreview = params.bodyPreview === void 0 ? void 0 : requireBoundedText(params.bodyPreview, "bodyPreview", MAX_BODY_PREVIEW_CHARS, true);
|
|
38
|
+
return {
|
|
39
|
+
to: requirePhone(params.to),
|
|
40
|
+
contentSid: requireBoundedText(params.contentSid, "contentSid", 128, false),
|
|
41
|
+
contentVariables: requireStringRecord(params.contentVariables, "contentVariables"),
|
|
42
|
+
...bodyPreview === void 0 ? {} : { bodyPreview }
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function parseListInput(params) {
|
|
46
|
+
requireExactFields(params, /* @__PURE__ */ new Set(), "whatsapp_list_templates");
|
|
47
|
+
}
|
|
48
|
+
function projectSessionResponse(raw) {
|
|
49
|
+
const response = asRecord(raw);
|
|
50
|
+
if (typeof response?.active !== "boolean") throw new Error("WhatsApp session response did not match the expected contract");
|
|
51
|
+
return { active: response.active };
|
|
52
|
+
}
|
|
53
|
+
function projectSendResponse(raw) {
|
|
54
|
+
const response = asRecord(raw);
|
|
55
|
+
const sid = boundedExternalText(response?.sid, MAX_MESSAGE_SID_CHARS);
|
|
56
|
+
if (response?.sent !== true || !sid) throw new Error("WhatsApp send response did not match the expected contract");
|
|
57
|
+
return {
|
|
58
|
+
sent: true,
|
|
59
|
+
sid
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function projectTemplatesResponse(raw) {
|
|
63
|
+
const response = asRecord(raw);
|
|
64
|
+
if (!Array.isArray(response?.templates)) throw new Error("WhatsApp template response did not contain a template list");
|
|
65
|
+
const templates = [];
|
|
66
|
+
const inspectionLimit = Math.min(response.templates.length, MAX_INSPECTED_TEMPLATES);
|
|
67
|
+
for (let index = 0; index < inspectionLimit && templates.length < MAX_TEMPLATES; index += 1) {
|
|
68
|
+
const template = projectTemplate(response.templates[index]);
|
|
69
|
+
if (template) templates.push(template);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
templates,
|
|
73
|
+
truncated: response.templates.length > templates.length,
|
|
74
|
+
notice: "WhatsApp template names, bodies, and variables are untrusted external content."
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function projectTemplate(value) {
|
|
78
|
+
const template = asRecord(value);
|
|
79
|
+
const contentSid = boundedExternalText(template?.contentSid, MAX_TEMPLATE_SID_CHARS);
|
|
80
|
+
const name = boundedExternalText(template?.name, MAX_TEMPLATE_NAME_CHARS);
|
|
81
|
+
const body = boundedExternalText(template?.body, MAX_TEMPLATE_BODY_CHARS);
|
|
82
|
+
const language = boundedExternalText(template?.language, MAX_TEMPLATE_LANGUAGE_CHARS);
|
|
83
|
+
if (!contentSid || !name || body === void 0 || !language) return null;
|
|
84
|
+
const variables = projectStringRecord(template?.variables);
|
|
85
|
+
if (!variables) return null;
|
|
86
|
+
const category = boundedExternalText(template?.category, MAX_TEMPLATE_CATEGORY_CHARS);
|
|
87
|
+
return {
|
|
88
|
+
contentSid,
|
|
89
|
+
name,
|
|
90
|
+
body,
|
|
91
|
+
variables,
|
|
92
|
+
language,
|
|
93
|
+
...category ? { category } : {}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function requirePhone(value) {
|
|
97
|
+
if (typeof value !== "string" || value.length > 16 || !E164.test(value)) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("to must be an E.164 phone number such as +12025551234");
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function requireStringRecord(value, label) {
|
|
101
|
+
if (!isPlainRecord(value)) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)(`${label} must be an object of string values`);
|
|
102
|
+
const keys = Object.keys(value);
|
|
103
|
+
if (keys.length > 32 || Object.getOwnPropertyNames(value).length !== keys.length || Object.getOwnPropertySymbols(value).length > 0) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)(`${label} must contain at most ${String(32)} fields`);
|
|
104
|
+
const output = Object.create(null);
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
107
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable || key.length < 1 || key.length > 32 || hasAnyControl(key) || FORBIDDEN_KEYS.has(key)) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)(`${label} contains an invalid key`);
|
|
108
|
+
output[key] = requireBoundedText(descriptor.value, `${label}.${key}`, MAX_CONTENT_VARIABLE_VALUE_CHARS, true);
|
|
109
|
+
}
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
112
|
+
function projectStringRecord(value) {
|
|
113
|
+
if (!isPlainRecord(value)) return void 0;
|
|
114
|
+
const keys = Object.keys(value);
|
|
115
|
+
if (keys.length > 32) return void 0;
|
|
116
|
+
const output = Object.create(null);
|
|
117
|
+
for (const key of keys) {
|
|
118
|
+
if (key.length < 1 || key.length > 32 || hasAnyControl(key) || FORBIDDEN_KEYS.has(key)) return void 0;
|
|
119
|
+
const projected = boundedExternalText(value[key], MAX_CONTENT_VARIABLE_VALUE_CHARS);
|
|
120
|
+
if (projected === void 0) return void 0;
|
|
121
|
+
output[key] = projected;
|
|
122
|
+
}
|
|
123
|
+
return output;
|
|
124
|
+
}
|
|
125
|
+
function requireExactFields(params, allowed, toolName) {
|
|
126
|
+
if (Object.keys(params).some((key) => !allowed.has(key))) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)(`${toolName} contains unsupported fields`);
|
|
127
|
+
}
|
|
128
|
+
function requireBoundedText(value, label, maximum, allowFormattingControls) {
|
|
129
|
+
if (typeof value !== "string" || value.length < 1 || value.length > maximum || (allowFormattingControls ? hasForbiddenTextControl(value) : hasAnyControl(value))) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)(`${label} must contain 1 to ${String(maximum)} safe characters`);
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
function boundedExternalText(value, maximum) {
|
|
133
|
+
if (typeof value !== "string") return void 0;
|
|
134
|
+
const flattened = flattenExternalControls(value).replace(/\s+/gu, " ").trim();
|
|
135
|
+
if (value.length > 0 && flattened.length === 0) return void 0;
|
|
136
|
+
return flattened.slice(0, maximum);
|
|
137
|
+
}
|
|
138
|
+
function isPlainRecord(value) {
|
|
139
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
140
|
+
const prototype = Object.getPrototypeOf(value);
|
|
141
|
+
return prototype === Object.prototype || prototype === null;
|
|
142
|
+
}
|
|
143
|
+
function flattenExternalControls(value) {
|
|
144
|
+
let output = "";
|
|
145
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
146
|
+
const code = value.charCodeAt(index);
|
|
147
|
+
output += code < 32 || code >= 127 && code <= 159 ? " " : value[index];
|
|
148
|
+
}
|
|
149
|
+
return output;
|
|
150
|
+
}
|
|
151
|
+
function hasAnyControl(value) {
|
|
152
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
153
|
+
const code = value.charCodeAt(index);
|
|
154
|
+
if (code < 32 || code >= 127 && code <= 159) return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
function hasForbiddenTextControl(value) {
|
|
159
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
160
|
+
const code = value.charCodeAt(index);
|
|
161
|
+
if (code < 32 && code !== 9 && code !== 10 && code !== 13 || code >= 127 && code <= 159) return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
function asRecord(value) {
|
|
166
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/tools.ts
|
|
170
|
+
function createWhatsAppTools(resolver) {
|
|
171
|
+
return [
|
|
172
|
+
(0, _alfe_ai_openclaw_plugin_kit.defineTool)({
|
|
173
|
+
name: "whatsapp_send_message",
|
|
174
|
+
description: "Send a free-form WhatsApp message during the recipient's active 24-hour session. Use whatsapp_send_template when no session is active.",
|
|
175
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
176
|
+
to: _sinclair_typebox.Type.String({
|
|
177
|
+
minLength: 8,
|
|
178
|
+
maxLength: 16,
|
|
179
|
+
pattern: "^\\+\\d{7,15}$"
|
|
180
|
+
}),
|
|
181
|
+
body: _sinclair_typebox.Type.String({
|
|
182
|
+
minLength: 1,
|
|
183
|
+
maxLength: MAX_MESSAGE_CHARS
|
|
184
|
+
})
|
|
185
|
+
}, { additionalProperties: false }),
|
|
186
|
+
handler: async (params) => {
|
|
187
|
+
const input = parseMessageInput(params);
|
|
188
|
+
const client = resolver.getClient();
|
|
189
|
+
if (!projectSessionResponse(await client.getWhatsAppSession(input.to)).active) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation, then send free-form messages after the user replies.");
|
|
190
|
+
return projectSendResponse(await client.sendWhatsAppMessage(input));
|
|
191
|
+
}
|
|
192
|
+
}),
|
|
193
|
+
(0, _alfe_ai_openclaw_plugin_kit.defineTool)({
|
|
194
|
+
name: "whatsapp_send_template",
|
|
195
|
+
description: "Send a pre-approved WhatsApp template to initiate or re-engage a conversation. List templates first and fill its declared variables.",
|
|
196
|
+
parameters: _sinclair_typebox.Type.Object({
|
|
197
|
+
to: _sinclair_typebox.Type.String({
|
|
198
|
+
minLength: 8,
|
|
199
|
+
maxLength: 16,
|
|
200
|
+
pattern: "^\\+\\d{7,15}$"
|
|
201
|
+
}),
|
|
202
|
+
contentSid: _sinclair_typebox.Type.String({
|
|
203
|
+
minLength: 1,
|
|
204
|
+
maxLength: 128
|
|
205
|
+
}),
|
|
206
|
+
contentVariables: _sinclair_typebox.Type.Record(_sinclair_typebox.Type.String({
|
|
207
|
+
minLength: 1,
|
|
208
|
+
maxLength: 32
|
|
209
|
+
}), _sinclair_typebox.Type.String({
|
|
210
|
+
minLength: 1,
|
|
211
|
+
maxLength: MAX_CONTENT_VARIABLE_VALUE_CHARS
|
|
212
|
+
}), { maxProperties: 32 }),
|
|
213
|
+
bodyPreview: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({
|
|
214
|
+
minLength: 1,
|
|
215
|
+
maxLength: MAX_BODY_PREVIEW_CHARS
|
|
216
|
+
}))
|
|
217
|
+
}, { additionalProperties: false }),
|
|
218
|
+
handler: async (params) => {
|
|
219
|
+
const input = parseTemplateInput(params);
|
|
220
|
+
return projectSendResponse(await resolver.getClient().sendWhatsAppTemplate(input));
|
|
221
|
+
}
|
|
222
|
+
}),
|
|
223
|
+
(0, _alfe_ai_openclaw_plugin_kit.defineTool)({
|
|
224
|
+
name: "whatsapp_list_templates",
|
|
225
|
+
description: "List bounded approved WhatsApp templates and their variables before sending one.",
|
|
226
|
+
parameters: _sinclair_typebox.Type.Object({}, { additionalProperties: false }),
|
|
227
|
+
handler: async (params) => {
|
|
228
|
+
parseListInput(params);
|
|
229
|
+
return projectTemplatesResponse(await resolver.getClient().listWhatsAppTemplates());
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
const PLUGIN_VERSION = validatePackageVersion((0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../package.json").version);
|
|
235
|
+
function createWhatsAppPlugin(dependencies = {}) {
|
|
236
|
+
const resolveRuntimeConfig = dependencies.resolveConfig ?? _alfe_ai_config.resolveConfig;
|
|
237
|
+
const createClient = dependencies.createClient ?? ((config) => new _alfe_ai_agent_api_client.AgentApiClient(config));
|
|
238
|
+
const installErrorCapture = dependencies.installErrorCapture ?? _alfe_ai_agent_api_client.installToolErrorCapture;
|
|
239
|
+
const resolver = createWhatsAppClientResolver(resolveRuntimeConfig, createClient);
|
|
240
|
+
return {
|
|
241
|
+
id: "@alfe.ai/openclaw-whatsapp",
|
|
242
|
+
name: "Alfe WhatsApp Plugin",
|
|
243
|
+
description: "WhatsApp integration — messaging and templates via Twilio",
|
|
244
|
+
version: PLUGIN_VERSION,
|
|
245
|
+
activate(api) {
|
|
246
|
+
installErrorCapture(api, { plugin: "openclaw-whatsapp" });
|
|
247
|
+
const tools = createWhatsAppTools(resolver);
|
|
248
|
+
for (const tool of tools) api.registerTool(tool);
|
|
249
|
+
api.logger.info(`Registered ${String(tools.length)} WhatsApp tools`);
|
|
250
|
+
},
|
|
251
|
+
deactivate(api) {
|
|
252
|
+
api.logger.info("WhatsApp plugin deactivated");
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function createWhatsAppClientResolver(resolveRuntimeConfig, createClient) {
|
|
257
|
+
let cached;
|
|
258
|
+
return { getClient() {
|
|
259
|
+
let config;
|
|
260
|
+
try {
|
|
261
|
+
config = resolveRuntimeConfig();
|
|
262
|
+
} catch {
|
|
263
|
+
throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("WhatsApp is not configured; run alfe setup or repair ~/.alfe/config.toml, then retry.");
|
|
264
|
+
}
|
|
265
|
+
if (cached?.apiKey === config.apiKey && cached.apiUrl === config.apiUrl) return cached.client;
|
|
266
|
+
const client = createClient({
|
|
267
|
+
apiKey: config.apiKey,
|
|
268
|
+
apiUrl: config.apiUrl
|
|
269
|
+
});
|
|
270
|
+
cached = {
|
|
271
|
+
apiKey: config.apiKey,
|
|
272
|
+
apiUrl: config.apiUrl,
|
|
273
|
+
client
|
|
274
|
+
};
|
|
275
|
+
return client;
|
|
276
|
+
} };
|
|
277
|
+
}
|
|
278
|
+
function validatePackageVersion(value) {
|
|
279
|
+
if (typeof value !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value)) throw new Error("openclaw-whatsapp package version is invalid");
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/plugin.ts
|
|
284
|
+
var plugin_default = createWhatsAppPlugin();
|
|
285
|
+
//#endregion
|
|
286
|
+
Object.defineProperty(exports, "plugin_default", {
|
|
287
|
+
enumerable: true,
|
|
288
|
+
get: function() {
|
|
289
|
+
return plugin_default;
|
|
290
|
+
}
|
|
291
|
+
});
|
package/dist/plugin2.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
export {
|
|
1
|
+
import { t as _default } from "./plugin.cjs";
|
|
2
|
+
export { _default as default };
|
package/dist/plugin2.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
export {
|
|
1
|
+
import { t as _default } from "./plugin.js";
|
|
2
|
+
export { _default as default };
|
package/dist/plugin2.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { AgentApiClient, installToolErrorCapture } from "@alfe.ai/agent-api-client";
|
|
3
|
+
import { resolveConfig } from "@alfe.ai/config";
|
|
4
|
+
import { defineTool, publicToolError } from "@alfe.ai/openclaw-plugin-kit";
|
|
5
|
+
import { Type } from "@sinclair/typebox";
|
|
6
|
+
const MAX_MESSAGE_CHARS = 4400;
|
|
7
|
+
const MAX_CONTENT_VARIABLE_VALUE_CHARS = 1024;
|
|
8
|
+
const MAX_BODY_PREVIEW_CHARS = 4e3;
|
|
9
|
+
const MAX_TEMPLATES = 200;
|
|
10
|
+
const MAX_INSPECTED_TEMPLATES = 1e3;
|
|
11
|
+
const MAX_TEMPLATE_SID_CHARS = 64;
|
|
12
|
+
const MAX_TEMPLATE_NAME_CHARS = 128;
|
|
13
|
+
const MAX_TEMPLATE_BODY_CHARS = 4096;
|
|
14
|
+
const MAX_TEMPLATE_LANGUAGE_CHARS = 16;
|
|
15
|
+
const MAX_TEMPLATE_CATEGORY_CHARS = 64;
|
|
16
|
+
const MAX_MESSAGE_SID_CHARS = 64;
|
|
17
|
+
const E164 = /^\+\d{7,15}$/u;
|
|
18
|
+
const FORBIDDEN_KEYS = new Set([
|
|
19
|
+
"__proto__",
|
|
20
|
+
"constructor",
|
|
21
|
+
"prototype"
|
|
22
|
+
]);
|
|
23
|
+
function parseMessageInput(params) {
|
|
24
|
+
requireExactFields(params, new Set(["to", "body"]), "whatsapp_send_message");
|
|
25
|
+
return {
|
|
26
|
+
to: requirePhone(params.to),
|
|
27
|
+
body: requireBoundedText(params.body, "body", MAX_MESSAGE_CHARS, true)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function parseTemplateInput(params) {
|
|
31
|
+
requireExactFields(params, new Set([
|
|
32
|
+
"to",
|
|
33
|
+
"contentSid",
|
|
34
|
+
"contentVariables",
|
|
35
|
+
"bodyPreview"
|
|
36
|
+
]), "whatsapp_send_template");
|
|
37
|
+
const bodyPreview = params.bodyPreview === void 0 ? void 0 : requireBoundedText(params.bodyPreview, "bodyPreview", MAX_BODY_PREVIEW_CHARS, true);
|
|
38
|
+
return {
|
|
39
|
+
to: requirePhone(params.to),
|
|
40
|
+
contentSid: requireBoundedText(params.contentSid, "contentSid", 128, false),
|
|
41
|
+
contentVariables: requireStringRecord(params.contentVariables, "contentVariables"),
|
|
42
|
+
...bodyPreview === void 0 ? {} : { bodyPreview }
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function parseListInput(params) {
|
|
46
|
+
requireExactFields(params, /* @__PURE__ */ new Set(), "whatsapp_list_templates");
|
|
47
|
+
}
|
|
48
|
+
function projectSessionResponse(raw) {
|
|
49
|
+
const response = asRecord(raw);
|
|
50
|
+
if (typeof response?.active !== "boolean") throw new Error("WhatsApp session response did not match the expected contract");
|
|
51
|
+
return { active: response.active };
|
|
52
|
+
}
|
|
53
|
+
function projectSendResponse(raw) {
|
|
54
|
+
const response = asRecord(raw);
|
|
55
|
+
const sid = boundedExternalText(response?.sid, MAX_MESSAGE_SID_CHARS);
|
|
56
|
+
if (response?.sent !== true || !sid) throw new Error("WhatsApp send response did not match the expected contract");
|
|
57
|
+
return {
|
|
58
|
+
sent: true,
|
|
59
|
+
sid
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function projectTemplatesResponse(raw) {
|
|
63
|
+
const response = asRecord(raw);
|
|
64
|
+
if (!Array.isArray(response?.templates)) throw new Error("WhatsApp template response did not contain a template list");
|
|
65
|
+
const templates = [];
|
|
66
|
+
const inspectionLimit = Math.min(response.templates.length, MAX_INSPECTED_TEMPLATES);
|
|
67
|
+
for (let index = 0; index < inspectionLimit && templates.length < MAX_TEMPLATES; index += 1) {
|
|
68
|
+
const template = projectTemplate(response.templates[index]);
|
|
69
|
+
if (template) templates.push(template);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
templates,
|
|
73
|
+
truncated: response.templates.length > templates.length,
|
|
74
|
+
notice: "WhatsApp template names, bodies, and variables are untrusted external content."
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function projectTemplate(value) {
|
|
78
|
+
const template = asRecord(value);
|
|
79
|
+
const contentSid = boundedExternalText(template?.contentSid, MAX_TEMPLATE_SID_CHARS);
|
|
80
|
+
const name = boundedExternalText(template?.name, MAX_TEMPLATE_NAME_CHARS);
|
|
81
|
+
const body = boundedExternalText(template?.body, MAX_TEMPLATE_BODY_CHARS);
|
|
82
|
+
const language = boundedExternalText(template?.language, MAX_TEMPLATE_LANGUAGE_CHARS);
|
|
83
|
+
if (!contentSid || !name || body === void 0 || !language) return null;
|
|
84
|
+
const variables = projectStringRecord(template?.variables);
|
|
85
|
+
if (!variables) return null;
|
|
86
|
+
const category = boundedExternalText(template?.category, MAX_TEMPLATE_CATEGORY_CHARS);
|
|
87
|
+
return {
|
|
88
|
+
contentSid,
|
|
89
|
+
name,
|
|
90
|
+
body,
|
|
91
|
+
variables,
|
|
92
|
+
language,
|
|
93
|
+
...category ? { category } : {}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function requirePhone(value) {
|
|
97
|
+
if (typeof value !== "string" || value.length > 16 || !E164.test(value)) throw publicToolError("to must be an E.164 phone number such as +12025551234");
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function requireStringRecord(value, label) {
|
|
101
|
+
if (!isPlainRecord(value)) throw publicToolError(`${label} must be an object of string values`);
|
|
102
|
+
const keys = Object.keys(value);
|
|
103
|
+
if (keys.length > 32 || Object.getOwnPropertyNames(value).length !== keys.length || Object.getOwnPropertySymbols(value).length > 0) throw publicToolError(`${label} must contain at most ${String(32)} fields`);
|
|
104
|
+
const output = Object.create(null);
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
107
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable || key.length < 1 || key.length > 32 || hasAnyControl(key) || FORBIDDEN_KEYS.has(key)) throw publicToolError(`${label} contains an invalid key`);
|
|
108
|
+
output[key] = requireBoundedText(descriptor.value, `${label}.${key}`, MAX_CONTENT_VARIABLE_VALUE_CHARS, true);
|
|
109
|
+
}
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
112
|
+
function projectStringRecord(value) {
|
|
113
|
+
if (!isPlainRecord(value)) return void 0;
|
|
114
|
+
const keys = Object.keys(value);
|
|
115
|
+
if (keys.length > 32) return void 0;
|
|
116
|
+
const output = Object.create(null);
|
|
117
|
+
for (const key of keys) {
|
|
118
|
+
if (key.length < 1 || key.length > 32 || hasAnyControl(key) || FORBIDDEN_KEYS.has(key)) return void 0;
|
|
119
|
+
const projected = boundedExternalText(value[key], MAX_CONTENT_VARIABLE_VALUE_CHARS);
|
|
120
|
+
if (projected === void 0) return void 0;
|
|
121
|
+
output[key] = projected;
|
|
122
|
+
}
|
|
123
|
+
return output;
|
|
124
|
+
}
|
|
125
|
+
function requireExactFields(params, allowed, toolName) {
|
|
126
|
+
if (Object.keys(params).some((key) => !allowed.has(key))) throw publicToolError(`${toolName} contains unsupported fields`);
|
|
127
|
+
}
|
|
128
|
+
function requireBoundedText(value, label, maximum, allowFormattingControls) {
|
|
129
|
+
if (typeof value !== "string" || value.length < 1 || value.length > maximum || (allowFormattingControls ? hasForbiddenTextControl(value) : hasAnyControl(value))) throw publicToolError(`${label} must contain 1 to ${String(maximum)} safe characters`);
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
function boundedExternalText(value, maximum) {
|
|
133
|
+
if (typeof value !== "string") return void 0;
|
|
134
|
+
const flattened = flattenExternalControls(value).replace(/\s+/gu, " ").trim();
|
|
135
|
+
if (value.length > 0 && flattened.length === 0) return void 0;
|
|
136
|
+
return flattened.slice(0, maximum);
|
|
137
|
+
}
|
|
138
|
+
function isPlainRecord(value) {
|
|
139
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
140
|
+
const prototype = Object.getPrototypeOf(value);
|
|
141
|
+
return prototype === Object.prototype || prototype === null;
|
|
142
|
+
}
|
|
143
|
+
function flattenExternalControls(value) {
|
|
144
|
+
let output = "";
|
|
145
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
146
|
+
const code = value.charCodeAt(index);
|
|
147
|
+
output += code < 32 || code >= 127 && code <= 159 ? " " : value[index];
|
|
148
|
+
}
|
|
149
|
+
return output;
|
|
150
|
+
}
|
|
151
|
+
function hasAnyControl(value) {
|
|
152
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
153
|
+
const code = value.charCodeAt(index);
|
|
154
|
+
if (code < 32 || code >= 127 && code <= 159) return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
function hasForbiddenTextControl(value) {
|
|
159
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
160
|
+
const code = value.charCodeAt(index);
|
|
161
|
+
if (code < 32 && code !== 9 && code !== 10 && code !== 13 || code >= 127 && code <= 159) return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
function asRecord(value) {
|
|
166
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/tools.ts
|
|
170
|
+
function createWhatsAppTools(resolver) {
|
|
171
|
+
return [
|
|
172
|
+
defineTool({
|
|
173
|
+
name: "whatsapp_send_message",
|
|
174
|
+
description: "Send a free-form WhatsApp message during the recipient's active 24-hour session. Use whatsapp_send_template when no session is active.",
|
|
175
|
+
parameters: Type.Object({
|
|
176
|
+
to: Type.String({
|
|
177
|
+
minLength: 8,
|
|
178
|
+
maxLength: 16,
|
|
179
|
+
pattern: "^\\+\\d{7,15}$"
|
|
180
|
+
}),
|
|
181
|
+
body: Type.String({
|
|
182
|
+
minLength: 1,
|
|
183
|
+
maxLength: MAX_MESSAGE_CHARS
|
|
184
|
+
})
|
|
185
|
+
}, { additionalProperties: false }),
|
|
186
|
+
handler: async (params) => {
|
|
187
|
+
const input = parseMessageInput(params);
|
|
188
|
+
const client = resolver.getClient();
|
|
189
|
+
if (!projectSessionResponse(await client.getWhatsAppSession(input.to)).active) throw publicToolError("No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation, then send free-form messages after the user replies.");
|
|
190
|
+
return projectSendResponse(await client.sendWhatsAppMessage(input));
|
|
191
|
+
}
|
|
192
|
+
}),
|
|
193
|
+
defineTool({
|
|
194
|
+
name: "whatsapp_send_template",
|
|
195
|
+
description: "Send a pre-approved WhatsApp template to initiate or re-engage a conversation. List templates first and fill its declared variables.",
|
|
196
|
+
parameters: Type.Object({
|
|
197
|
+
to: Type.String({
|
|
198
|
+
minLength: 8,
|
|
199
|
+
maxLength: 16,
|
|
200
|
+
pattern: "^\\+\\d{7,15}$"
|
|
201
|
+
}),
|
|
202
|
+
contentSid: Type.String({
|
|
203
|
+
minLength: 1,
|
|
204
|
+
maxLength: 128
|
|
205
|
+
}),
|
|
206
|
+
contentVariables: Type.Record(Type.String({
|
|
207
|
+
minLength: 1,
|
|
208
|
+
maxLength: 32
|
|
209
|
+
}), Type.String({
|
|
210
|
+
minLength: 1,
|
|
211
|
+
maxLength: MAX_CONTENT_VARIABLE_VALUE_CHARS
|
|
212
|
+
}), { maxProperties: 32 }),
|
|
213
|
+
bodyPreview: Type.Optional(Type.String({
|
|
214
|
+
minLength: 1,
|
|
215
|
+
maxLength: MAX_BODY_PREVIEW_CHARS
|
|
216
|
+
}))
|
|
217
|
+
}, { additionalProperties: false }),
|
|
218
|
+
handler: async (params) => {
|
|
219
|
+
const input = parseTemplateInput(params);
|
|
220
|
+
return projectSendResponse(await resolver.getClient().sendWhatsAppTemplate(input));
|
|
221
|
+
}
|
|
222
|
+
}),
|
|
223
|
+
defineTool({
|
|
224
|
+
name: "whatsapp_list_templates",
|
|
225
|
+
description: "List bounded approved WhatsApp templates and their variables before sending one.",
|
|
226
|
+
parameters: Type.Object({}, { additionalProperties: false }),
|
|
227
|
+
handler: async (params) => {
|
|
228
|
+
parseListInput(params);
|
|
229
|
+
return projectTemplatesResponse(await resolver.getClient().listWhatsAppTemplates());
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
const PLUGIN_VERSION = validatePackageVersion(createRequire(import.meta.url)("../package.json").version);
|
|
235
|
+
function createWhatsAppPlugin(dependencies = {}) {
|
|
236
|
+
const resolveRuntimeConfig = dependencies.resolveConfig ?? resolveConfig;
|
|
237
|
+
const createClient = dependencies.createClient ?? ((config) => new AgentApiClient(config));
|
|
238
|
+
const installErrorCapture = dependencies.installErrorCapture ?? installToolErrorCapture;
|
|
239
|
+
const resolver = createWhatsAppClientResolver(resolveRuntimeConfig, createClient);
|
|
240
|
+
return {
|
|
241
|
+
id: "@alfe.ai/openclaw-whatsapp",
|
|
242
|
+
name: "Alfe WhatsApp Plugin",
|
|
243
|
+
description: "WhatsApp integration — messaging and templates via Twilio",
|
|
244
|
+
version: PLUGIN_VERSION,
|
|
245
|
+
activate(api) {
|
|
246
|
+
installErrorCapture(api, { plugin: "openclaw-whatsapp" });
|
|
247
|
+
const tools = createWhatsAppTools(resolver);
|
|
248
|
+
for (const tool of tools) api.registerTool(tool);
|
|
249
|
+
api.logger.info(`Registered ${String(tools.length)} WhatsApp tools`);
|
|
250
|
+
},
|
|
251
|
+
deactivate(api) {
|
|
252
|
+
api.logger.info("WhatsApp plugin deactivated");
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function createWhatsAppClientResolver(resolveRuntimeConfig, createClient) {
|
|
257
|
+
let cached;
|
|
258
|
+
return { getClient() {
|
|
259
|
+
let config;
|
|
260
|
+
try {
|
|
261
|
+
config = resolveRuntimeConfig();
|
|
262
|
+
} catch {
|
|
263
|
+
throw publicToolError("WhatsApp is not configured; run alfe setup or repair ~/.alfe/config.toml, then retry.");
|
|
264
|
+
}
|
|
265
|
+
if (cached?.apiKey === config.apiKey && cached.apiUrl === config.apiUrl) return cached.client;
|
|
266
|
+
const client = createClient({
|
|
267
|
+
apiKey: config.apiKey,
|
|
268
|
+
apiUrl: config.apiUrl
|
|
269
|
+
});
|
|
270
|
+
cached = {
|
|
271
|
+
apiKey: config.apiKey,
|
|
272
|
+
apiUrl: config.apiUrl,
|
|
273
|
+
client
|
|
274
|
+
};
|
|
275
|
+
return client;
|
|
276
|
+
} };
|
|
277
|
+
}
|
|
278
|
+
function validatePackageVersion(value) {
|
|
279
|
+
if (typeof value !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value)) throw new Error("openclaw-whatsapp package version is invalid");
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/plugin.ts
|
|
284
|
+
var plugin_default = createWhatsAppPlugin();
|
|
285
|
+
//#endregion
|
|
286
|
+
export { plugin_default as t };
|
|
287
|
+
|
|
288
|
+
//# sourceMappingURL=plugin2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin2.js","names":[],"sources":["../src/boundary.ts","../src/tools.ts","../src/runtime.ts","../src/plugin.ts"],"sourcesContent":["import { publicToolError } from \"@alfe.ai/openclaw-plugin-kit\";\n\nexport const MAX_PHONE_CHARS = 16;\nexport const MAX_MESSAGE_CHARS = 4_400;\nexport const MAX_CONTENT_SID_CHARS = 128;\nexport const MAX_CONTENT_VARIABLES = 32;\nexport const MAX_CONTENT_VARIABLE_KEY_CHARS = 32;\nexport const MAX_CONTENT_VARIABLE_VALUE_CHARS = 1_024;\nexport const MAX_BODY_PREVIEW_CHARS = 4_000;\nconst MAX_TEMPLATES = 200;\nconst MAX_INSPECTED_TEMPLATES = 1_000;\nconst MAX_TEMPLATE_SID_CHARS = 64;\nconst MAX_TEMPLATE_NAME_CHARS = 128;\nconst MAX_TEMPLATE_BODY_CHARS = 4_096;\nconst MAX_TEMPLATE_LANGUAGE_CHARS = 16;\nconst MAX_TEMPLATE_CATEGORY_CHARS = 64;\nconst MAX_MESSAGE_SID_CHARS = 64;\nconst E164 = /^\\+\\d{7,15}$/u;\nconst FORBIDDEN_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nexport function parseMessageInput(params: Record<string, unknown>): {\n to: string;\n body: string;\n} {\n requireExactFields(params, new Set([\"to\", \"body\"]), \"whatsapp_send_message\");\n return {\n to: requirePhone(params.to),\n body: requireBoundedText(params.body, \"body\", MAX_MESSAGE_CHARS, true),\n };\n}\n\nexport function parseTemplateInput(params: Record<string, unknown>): {\n to: string;\n contentSid: string;\n contentVariables: Record<string, string>;\n bodyPreview?: string;\n} {\n requireExactFields(\n params,\n new Set([\"to\", \"contentSid\", \"contentVariables\", \"bodyPreview\"]),\n \"whatsapp_send_template\",\n );\n const bodyPreview = params.bodyPreview === undefined\n ? undefined\n : requireBoundedText(params.bodyPreview, \"bodyPreview\", MAX_BODY_PREVIEW_CHARS, true);\n return {\n to: requirePhone(params.to),\n contentSid: requireBoundedText(\n params.contentSid,\n \"contentSid\",\n MAX_CONTENT_SID_CHARS,\n false,\n ),\n contentVariables: requireStringRecord(params.contentVariables, \"contentVariables\"),\n ...(bodyPreview === undefined ? {} : { bodyPreview }),\n };\n}\n\nexport function parseListInput(params: Record<string, unknown>): void {\n requireExactFields(params, new Set(), \"whatsapp_list_templates\");\n}\n\nexport function projectSessionResponse(raw: unknown): { active: boolean } {\n const response = asRecord(raw);\n if (typeof response?.active !== \"boolean\") {\n throw new Error(\"WhatsApp session response did not match the expected contract\");\n }\n return { active: response.active };\n}\n\nexport function projectSendResponse(raw: unknown): { sent: true; sid: string } {\n const response = asRecord(raw);\n const sid = boundedExternalText(response?.sid, MAX_MESSAGE_SID_CHARS);\n if (response?.sent !== true || !sid) {\n throw new Error(\"WhatsApp send response did not match the expected contract\");\n }\n return { sent: true, sid };\n}\n\nexport function projectTemplatesResponse(raw: unknown): Record<string, unknown> {\n const response = asRecord(raw);\n if (!Array.isArray(response?.templates)) {\n throw new Error(\"WhatsApp template response did not contain a template list\");\n }\n const templates: Record<string, unknown>[] = [];\n const inspectionLimit = Math.min(response.templates.length, MAX_INSPECTED_TEMPLATES);\n for (let index = 0; index < inspectionLimit && templates.length < MAX_TEMPLATES; index += 1) {\n const template = projectTemplate(response.templates[index]);\n if (template) templates.push(template);\n }\n return {\n templates,\n truncated: response.templates.length > templates.length,\n notice: \"WhatsApp template names, bodies, and variables are untrusted external content.\",\n };\n}\n\nfunction projectTemplate(value: unknown): Record<string, unknown> | null {\n const template = asRecord(value);\n const contentSid = boundedExternalText(template?.contentSid, MAX_TEMPLATE_SID_CHARS);\n const name = boundedExternalText(template?.name, MAX_TEMPLATE_NAME_CHARS);\n const body = boundedExternalText(template?.body, MAX_TEMPLATE_BODY_CHARS);\n const language = boundedExternalText(template?.language, MAX_TEMPLATE_LANGUAGE_CHARS);\n if (!contentSid || !name || body === undefined || !language) return null;\n const variables = projectStringRecord(template?.variables);\n if (!variables) return null;\n const category = boundedExternalText(template?.category, MAX_TEMPLATE_CATEGORY_CHARS);\n return {\n contentSid,\n name,\n body,\n variables,\n language,\n ...(category ? { category } : {}),\n };\n}\n\nfunction requirePhone(value: unknown): string {\n if (typeof value !== \"string\" || value.length > MAX_PHONE_CHARS || !E164.test(value)) {\n throw publicToolError(\"to must be an E.164 phone number such as +12025551234\");\n }\n return value;\n}\n\nfunction requireStringRecord(value: unknown, label: string): Record<string, string> {\n if (!isPlainRecord(value)) throw publicToolError(`${label} must be an object of string values`);\n const keys = Object.keys(value);\n if (\n keys.length > MAX_CONTENT_VARIABLES ||\n Object.getOwnPropertyNames(value).length !== keys.length ||\n Object.getOwnPropertySymbols(value).length > 0\n ) {\n throw publicToolError(`${label} must contain at most ${String(MAX_CONTENT_VARIABLES)} fields`);\n }\n const output = Object.create(null) as Record<string, string>;\n for (const key of keys) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (\n !descriptor ||\n !(\"value\" in descriptor) ||\n !descriptor.enumerable ||\n key.length < 1 ||\n key.length > MAX_CONTENT_VARIABLE_KEY_CHARS ||\n hasAnyControl(key) ||\n FORBIDDEN_KEYS.has(key)\n ) {\n throw publicToolError(`${label} contains an invalid key`);\n }\n output[key] = requireBoundedText(\n descriptor.value,\n `${label}.${key}`,\n MAX_CONTENT_VARIABLE_VALUE_CHARS,\n true,\n );\n }\n return output;\n}\n\nfunction projectStringRecord(value: unknown): Record<string, string> | undefined {\n if (!isPlainRecord(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length > MAX_CONTENT_VARIABLES) return undefined;\n const output = Object.create(null) as Record<string, string>;\n for (const key of keys) {\n if (\n key.length < 1 ||\n key.length > MAX_CONTENT_VARIABLE_KEY_CHARS ||\n hasAnyControl(key) ||\n FORBIDDEN_KEYS.has(key)\n ) return undefined;\n const projected = boundedExternalText(value[key], MAX_CONTENT_VARIABLE_VALUE_CHARS);\n if (projected === undefined) return undefined;\n output[key] = projected;\n }\n return output;\n}\n\nfunction requireExactFields(\n params: Record<string, unknown>,\n allowed: ReadonlySet<string>,\n toolName: string,\n): void {\n if (Object.keys(params).some((key) => !allowed.has(key))) {\n throw publicToolError(`${toolName} contains unsupported fields`);\n }\n}\n\nfunction requireBoundedText(\n value: unknown,\n label: string,\n maximum: number,\n allowFormattingControls: boolean,\n): string {\n if (\n typeof value !== \"string\" ||\n value.length < 1 ||\n value.length > maximum ||\n (allowFormattingControls ? hasForbiddenTextControl(value) : hasAnyControl(value))\n ) {\n throw publicToolError(`${label} must contain 1 to ${String(maximum)} safe characters`);\n }\n return value;\n}\n\nfunction boundedExternalText(value: unknown, maximum: number): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const flattened = flattenExternalControls(value).replace(/\\s+/gu, \" \").trim();\n if (value.length > 0 && flattened.length === 0) return undefined;\n return flattened.slice(0, maximum);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value) as unknown;\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction flattenExternalControls(value: string): string {\n let output = \"\";\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n output += code < 32 || (code >= 127 && code <= 159) ? \" \" : value[index];\n }\n return output;\n}\n\nfunction hasAnyControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code < 32 || (code >= 127 && code <= 159)) return true;\n }\n return false;\n}\n\nfunction hasForbiddenTextControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (\n (code < 32 && code !== 9 && code !== 10 && code !== 13) ||\n (code >= 127 && code <= 159)\n ) return true;\n }\n return false;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined;\n}\n","import { Type } from \"@sinclair/typebox\";\nimport { defineTool, publicToolError, type ToolDef } from \"@alfe.ai/openclaw-plugin-kit\";\nimport {\n MAX_BODY_PREVIEW_CHARS,\n MAX_CONTENT_SID_CHARS,\n MAX_CONTENT_VARIABLE_KEY_CHARS,\n MAX_CONTENT_VARIABLE_VALUE_CHARS,\n MAX_MESSAGE_CHARS,\n MAX_PHONE_CHARS,\n parseListInput,\n parseMessageInput,\n parseTemplateInput,\n projectSendResponse,\n projectSessionResponse,\n projectTemplatesResponse,\n} from \"./boundary.js\";\nimport type { WhatsAppClientResolver } from \"./types.js\";\n\nexport function createWhatsAppTools(resolver: WhatsAppClientResolver): ToolDef[] {\n return [\n defineTool({\n name: \"whatsapp_send_message\",\n description:\n \"Send a free-form WhatsApp message during the recipient's active 24-hour session. Use whatsapp_send_template when no session is active.\",\n parameters: Type.Object({\n to: Type.String({ minLength: 8, maxLength: MAX_PHONE_CHARS, pattern: \"^\\\\+\\\\d{7,15}$\" }),\n body: Type.String({ minLength: 1, maxLength: MAX_MESSAGE_CHARS }),\n }, { additionalProperties: false }),\n handler: async (params) => {\n const input = parseMessageInput(params);\n const client = resolver.getClient();\n const session = projectSessionResponse(await client.getWhatsAppSession(input.to));\n if (!session.active) {\n throw publicToolError(\n \"No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation, then send free-form messages after the user replies.\",\n );\n }\n return projectSendResponse(await client.sendWhatsAppMessage(input));\n },\n }),\n defineTool({\n name: \"whatsapp_send_template\",\n description:\n \"Send a pre-approved WhatsApp template to initiate or re-engage a conversation. List templates first and fill its declared variables.\",\n parameters: Type.Object({\n to: Type.String({ minLength: 8, maxLength: MAX_PHONE_CHARS, pattern: \"^\\\\+\\\\d{7,15}$\" }),\n contentSid: Type.String({ minLength: 1, maxLength: MAX_CONTENT_SID_CHARS }),\n contentVariables: Type.Record(\n Type.String({ minLength: 1, maxLength: MAX_CONTENT_VARIABLE_KEY_CHARS }),\n Type.String({ minLength: 1, maxLength: MAX_CONTENT_VARIABLE_VALUE_CHARS }),\n { maxProperties: 32 },\n ),\n bodyPreview: Type.Optional(Type.String({ minLength: 1, maxLength: MAX_BODY_PREVIEW_CHARS })),\n }, { additionalProperties: false }),\n handler: async (params) => {\n const input = parseTemplateInput(params);\n return projectSendResponse(await resolver.getClient().sendWhatsAppTemplate(input));\n },\n }),\n defineTool({\n name: \"whatsapp_list_templates\",\n description:\n \"List bounded approved WhatsApp templates and their variables before sending one.\",\n parameters: Type.Object({}, { additionalProperties: false }),\n handler: async (params) => {\n parseListInput(params);\n return projectTemplatesResponse(await resolver.getClient().listWhatsAppTemplates());\n },\n }),\n ];\n}\n","import { createRequire } from \"node:module\";\nimport { AgentApiClient, installToolErrorCapture } from \"@alfe.ai/agent-api-client\";\nimport { resolveConfig, type ResolvedConfig } from \"@alfe.ai/config\";\nimport { publicToolError } from \"@alfe.ai/openclaw-plugin-kit\";\nimport { createWhatsAppTools } from \"./tools.js\";\nimport type {\n OpenClawPluginApi,\n WhatsAppClient,\n WhatsAppClientResolver,\n} from \"./types.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version?: unknown };\n\nexport const PLUGIN_VERSION = validatePackageVersion(pkg.version);\n\nexport interface WhatsAppPluginDependencies {\n resolveConfig?: () => ResolvedConfig;\n createClient?: (config: { apiKey: string; apiUrl: string }) => WhatsAppClient;\n installErrorCapture?: typeof installToolErrorCapture;\n}\n\nexport function createWhatsAppPlugin(dependencies: WhatsAppPluginDependencies = {}) {\n const resolveRuntimeConfig = dependencies.resolveConfig ?? resolveConfig;\n const createClient = dependencies.createClient ?? ((config) => new AgentApiClient(config));\n const installErrorCapture = dependencies.installErrorCapture ?? installToolErrorCapture;\n const resolver = createWhatsAppClientResolver(resolveRuntimeConfig, createClient);\n\n return {\n id: \"@alfe.ai/openclaw-whatsapp\",\n name: \"Alfe WhatsApp Plugin\",\n description: \"WhatsApp integration — messaging and templates via Twilio\",\n version: PLUGIN_VERSION,\n\n activate(api: OpenClawPluginApi): void {\n installErrorCapture(api, { plugin: \"openclaw-whatsapp\" });\n const tools = createWhatsAppTools(resolver);\n for (const tool of tools) api.registerTool(tool);\n api.logger.info(`Registered ${String(tools.length)} WhatsApp tools`);\n },\n\n deactivate(api: OpenClawPluginApi): void {\n api.logger.info(\"WhatsApp plugin deactivated\");\n },\n };\n}\n\nexport function createWhatsAppClientResolver(\n resolveRuntimeConfig: () => ResolvedConfig,\n createClient: (config: { apiKey: string; apiUrl: string }) => WhatsAppClient,\n): WhatsAppClientResolver {\n let cached: { apiKey: string; apiUrl: string; client: WhatsAppClient } | undefined;\n return {\n getClient(): WhatsAppClient {\n let config: ResolvedConfig;\n try {\n config = resolveRuntimeConfig();\n } catch {\n throw publicToolError(\n \"WhatsApp is not configured; run alfe setup or repair ~/.alfe/config.toml, then retry.\",\n );\n }\n if (cached?.apiKey === config.apiKey && cached.apiUrl === config.apiUrl) {\n return cached.client;\n }\n const client = createClient({ apiKey: config.apiKey, apiUrl: config.apiUrl });\n cached = { apiKey: config.apiKey, apiUrl: config.apiUrl, client };\n return client;\n },\n };\n}\n\nfunction validatePackageVersion(value: unknown): string {\n if (typeof value !== \"string\" || !/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value)) {\n throw new Error(\"openclaw-whatsapp package version is invalid\");\n }\n return value;\n}\n","import { createWhatsAppPlugin } from \"./runtime.js\";\n\nexport default createWhatsAppPlugin();\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-whatsapp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"description": "OpenClaw WhatsApp plugin for Alfe — outbound WhatsApp messaging via Twilio",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,13 +24,14 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
|
-
"openclaw.plugin.json"
|
|
27
|
+
"openclaw.plugin.json",
|
|
28
|
+
"README.md"
|
|
28
29
|
],
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@sinclair/typebox": "^0.34.48",
|
|
31
|
-
"@alfe.ai/agent-api-client": "0.
|
|
32
|
-
"@alfe.ai/config": "0.4.
|
|
33
|
-
"@alfe.ai/openclaw-plugin-kit": "0.
|
|
32
|
+
"@alfe.ai/agent-api-client": "0.16.0",
|
|
33
|
+
"@alfe.ai/config": "0.4.1",
|
|
34
|
+
"@alfe.ai/openclaw-plugin-kit": "0.2.0"
|
|
34
35
|
},
|
|
35
36
|
"homepage": "https://alfe.ai",
|
|
36
37
|
"author": "Alfe (https://alfe.ai)",
|
|
@@ -45,7 +46,8 @@
|
|
|
45
46
|
"scripts": {
|
|
46
47
|
"build": "tsdown",
|
|
47
48
|
"dev": "tsdown --watch",
|
|
48
|
-
"test": "vitest run
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
49
51
|
"lint": "eslint ."
|
|
50
52
|
}
|
|
51
53
|
}
|
package/dist/plugin.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * @alfe/openclaw-whatsapp — OpenClaw native plugin\n *\n * Registers WhatsApp tools with OpenClaw. Tools call the Twilio service\n * API using the agent's API key for authentication.\n *\n * This plugin provides:\n * - whatsapp_send_message — send a free-form WhatsApp message (requires active session)\n * - whatsapp_send_template — send a template message to initiate conversation\n * - whatsapp_list_templates — list available approved templates\n */\n\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { resolveConfig } from \"@alfe.ai/config\";\nimport { AgentApiClient, installToolErrorCapture } from \"@alfe.ai/agent-api-client\";\nimport {\n defineTool,\n getActivationKey,\n guardedStart,\n resetActivation,\n type ToolDef,\n} from \"@alfe.ai/openclaw-plugin-kit\";\nimport { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\ninterface Logger {\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n debug(msg: string, ...args: unknown[]): void;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n registrationMode?: \"full\" | \"setup-only\" | \"setup-runtime\" | \"cli-metadata\";\n registerTool(tool: ToolDef<TSchema>): void;\n registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;\n on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: { priority?: number }): void;\n}\n\n// ── Activation guard ─────────────────────────────────────────\n\nconst WHATSAPP_ACTIVATION_KEY = getActivationKey(\"whatsapp\");\n\n// ── API client ───────────────────────────────────────────────\n\nlet client: AgentApiClient | undefined;\n\nfunction getClient(): AgentApiClient {\n if (!client) {\n throw new Error(\"WhatsApp tools unavailable — no API config resolved\");\n }\n return client;\n}\n\n// ── Tool definitions ─────────────────────────────────────────\n\nconst whatsappTools: ToolDef<TSchema>[] = [\n defineTool({\n name: \"whatsapp_send_message\",\n description:\n \"Send a free-form WhatsApp message from your phone number. \" +\n \"This only works if the recipient has messaged you within the last 24 hours (active session). \" +\n \"If there is no active session, you must use whatsapp_send_template to initiate the conversation first.\",\n parameters: Type.Object({\n to: Type.String({ description: \"Recipient phone number in E.164 format (e.g., +12025551234)\" }),\n body: Type.String({ description: \"The message content to send\" }),\n }),\n handler: async (params) => {\n const { to, body } = params as { to: string; body: string };\n\n // Pre-check session to give a clear error before hitting the API\n try {\n const session = await getClient().getWhatsAppSession(to);\n if (!session.active) {\n throw new Error(\n \"No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation first, then the user can reply and you can send free-form messages.\",\n );\n }\n } catch (e) {\n const msg = (e as Error).message;\n if (msg.includes(\"No active WhatsApp session\") || msg.includes(\"whatsapp_send_template\")) {\n throw e;\n }\n // If session check fails for other reasons, try sending anyway — the API will enforce\n }\n\n return getClient().sendWhatsAppMessage({ to, body });\n },\n }),\n\n defineTool({\n name: \"whatsapp_send_template\",\n description:\n \"Send a pre-approved WhatsApp template message to initiate a conversation. \" +\n \"Use this when you need to message a user who hasn't contacted you recently (no active 24-hour session). \" +\n \"First call whatsapp_list_templates to see available templates and their variables, \" +\n \"then call this tool with the chosen template. \" +\n \"After the user replies to your template, you can use whatsapp_send_message for free-form messages.\",\n parameters: Type.Object({\n to: Type.String({ description: \"Recipient phone number in E.164 format (e.g., +12025551234)\" }),\n contentSid: Type.String({ description: \"The template content SID (from whatsapp_list_templates)\" }),\n contentVariables: Type.Object({}, { description: \"Variables to fill in the template, e.g. {\\\"1\\\": \\\"Kevin\\\", \\\"2\\\": \\\"Agent Name\\\"}\", additionalProperties: Type.String() }),\n bodyPreview: Type.Optional(Type.String({ description: \"The filled template text for conversation context\" })),\n }),\n handler: async (params) => {\n const { to, contentSid, contentVariables, bodyPreview } = params as {\n to: string;\n contentSid: string;\n contentVariables: Record<string, string>;\n bodyPreview?: string;\n };\n return getClient().sendWhatsAppTemplate({\n to,\n contentSid,\n contentVariables,\n bodyPreview,\n });\n },\n }),\n\n defineTool({\n name: \"whatsapp_list_templates\",\n description:\n \"List all approved WhatsApp message templates available for outreach. \" +\n \"Each template has a contentSid, name, body with {{1}}, {{2}} placeholders, \" +\n \"and variables showing what each placeholder represents. \" +\n \"Use this to find the right template before calling whatsapp_send_template.\",\n parameters: Type.Object({}),\n handler: async () => {\n return getClient().listWhatsAppTemplates();\n },\n }),\n];\n\n// ── Plugin definition ────────────────────────────────────────\n\nconst plugin = {\n id: \"@alfe.ai/openclaw-whatsapp\",\n name: \"Alfe WhatsApp Plugin\",\n description: \"WhatsApp integration — messaging and templates via Twilio\",\n version: pkg.version,\n\n activate(api: OpenClawPluginApi) {\n // First thing, before any registerTool call: tool failures emit a\n // deterministic [ERROR] line the gateway's runtime-output monitor captures\n // to Sentry. See @alfe.ai/agent-api-client tool-error-capture.\n installToolErrorCapture(api, { plugin: \"openclaw-whatsapp\" });\n const log = api.logger;\n\n\n // Always register tools — OpenClaw may reload the plugin registry per-session\n for (const tool of whatsappTools) {\n api.registerTool(tool);\n }\n log.info(`Registered ${String(whatsappTools.length)} WhatsApp tools: ${whatsappTools.map((t) => t.name).join(\", \")}`);\n\n // Only initialize config once (side effects)\n guardedStart(WHATSAPP_ACTIVATION_KEY, log, () => {\n log.info(\"Alfe WhatsApp plugin activating...\");\n\n try {\n const config = resolveConfig();\n client = new AgentApiClient({ apiUrl: config.apiUrl, apiKey: config.apiKey });\n log.info(`WhatsApp API: ${config.apiUrl}`);\n } catch (err) {\n log.error(`Failed to resolve config: ${err instanceof Error ? err.message : String(err)}`);\n log.warn(\"WhatsApp tools will fail — no API config available\");\n // Reset so a later activate() can retry once config exists.\n resetActivation(WHATSAPP_ACTIVATION_KEY);\n }\n });\n\n log.info(\"Alfe WhatsApp plugin activated\");\n },\n\n deactivate(api: OpenClawPluginApi) {\n // Stop side effects BEFORE resetting the activation flag.\n client = undefined;\n resetActivation(WHATSAPP_ACTIVATION_KEY);\n api.logger.info(\"Alfe WhatsApp plugin deactivated\");\n },\n};\n\nexport default plugin;\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,MAAM,MADU,cAAc,OAAO,KAAK,IAAI,CAC1B,kBAAkB;AAmBtC,MAAM,0BAA0B,iBAAiB,WAAW;AAI5D,IAAI;AAEJ,SAAS,YAA4B;AACnC,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,sDAAsD;AAExE,QAAO;;AAKT,MAAM,gBAAoC;CACxC,WAAW;EACT,MAAM;EACN,aACE;EAGF,YAAY,KAAK,OAAO;GACtB,IAAI,KAAK,OAAO,EAAE,aAAa,+DAA+D,CAAC;GAC/F,MAAM,KAAK,OAAO,EAAE,aAAa,+BAA+B,CAAC;GAClE,CAAC;EACF,SAAS,OAAO,WAAW;GACzB,MAAM,EAAE,IAAI,SAAS;AAGrB,OAAI;AAEF,QAAI,EADY,MAAM,WAAW,CAAC,mBAAmB,GAAG,EAC3C,OACX,OAAM,IAAI,MACR,yKACD;YAEI,GAAG;IACV,MAAM,MAAO,EAAY;AACzB,QAAI,IAAI,SAAS,6BAA6B,IAAI,IAAI,SAAS,yBAAyB,CACtF,OAAM;;AAKV,UAAO,WAAW,CAAC,oBAAoB;IAAE;IAAI;IAAM,CAAC;;EAEvD,CAAC;CAEF,WAAW;EACT,MAAM;EACN,aACE;EAKF,YAAY,KAAK,OAAO;GACtB,IAAI,KAAK,OAAO,EAAE,aAAa,+DAA+D,CAAC;GAC/F,YAAY,KAAK,OAAO,EAAE,aAAa,2DAA2D,CAAC;GACnG,kBAAkB,KAAK,OAAO,EAAE,EAAE;IAAE,aAAa;IAAqF,sBAAsB,KAAK,QAAQ;IAAE,CAAC;GAC5K,aAAa,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,qDAAqD,CAAC,CAAC;GAC9G,CAAC;EACF,SAAS,OAAO,WAAW;GACzB,MAAM,EAAE,IAAI,YAAY,kBAAkB,gBAAgB;AAM1D,UAAO,WAAW,CAAC,qBAAqB;IACtC;IACA;IACA;IACA;IACD,CAAC;;EAEL,CAAC;CAEF,WAAW;EACT,MAAM;EACN,aACE;EAIF,YAAY,KAAK,OAAO,EAAE,CAAC;EAC3B,SAAS,YAAY;AACnB,UAAO,WAAW,CAAC,uBAAuB;;EAE7C,CAAC;CACH;AAID,MAAM,SAAS;CACb,IAAI;CACJ,MAAM;CACN,aAAa;CACb,SAAS,IAAI;CAEb,SAAS,KAAwB;AAI/B,0BAAwB,KAAK,EAAE,QAAQ,qBAAqB,CAAC;EAC7D,MAAM,MAAM,IAAI;AAIhB,OAAK,MAAM,QAAQ,cACjB,KAAI,aAAa,KAAK;AAExB,MAAI,KAAK,cAAc,OAAO,cAAc,OAAO,CAAC,mBAAmB,cAAc,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,GAAG;AAGrH,eAAa,yBAAyB,WAAW;AAC/C,OAAI,KAAK,qCAAqC;AAE9C,OAAI;IACF,MAAM,SAAS,eAAe;AAC9B,aAAS,IAAI,eAAe;KAAE,QAAQ,OAAO;KAAQ,QAAQ,OAAO;KAAQ,CAAC;AAC7E,QAAI,KAAK,iBAAiB,OAAO,SAAS;YACnC,KAAK;AACZ,QAAI,MAAM,6BAA6B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAC1F,QAAI,KAAK,qDAAqD;AAE9D,oBAAgB,wBAAwB;;IAE1C;AAEF,MAAI,KAAK,iCAAiC;;CAG5C,WAAW,KAAwB;AAEjC,WAAS,KAAA;AACT,kBAAgB,wBAAwB;AACxC,MAAI,OAAO,KAAK,mCAAmC;;CAEtD"}
|