@poolzin/pool-bot 2026.4.43 → 2026.4.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/model-catalog-gemma4.d.ts +6 -0
- package/dist/agents/model-catalog-gemma4.d.ts.map +1 -0
- package/dist/agents/model-catalog-gemma4.js +17 -0
- package/dist/agents/model-catalog.d.ts.map +1 -1
- package/dist/agents/model-catalog.js +7 -0
- package/dist/agents/tools/music-edit-tool.d.ts +9 -0
- package/dist/agents/tools/music-edit-tool.d.ts.map +1 -0
- package/dist/agents/tools/music-edit-tool.js +46 -0
- package/dist/agents/tools/video-edit-tool.d.ts +9 -0
- package/dist/agents/tools/video-edit-tool.d.ts.map +1 -0
- package/dist/agents/tools/video-edit-tool.js +46 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/completion.d.ts +15 -0
- package/dist/cli/completion.d.ts.map +1 -0
- package/dist/cli/completion.js +183 -0
- package/dist/cli/onboard-cli.d.ts +20 -0
- package/dist/cli/onboard-cli.d.ts.map +1 -0
- package/dist/cli/onboard-cli.js +443 -0
- package/dist/cli/plugins-config-tui.d.ts.map +1 -1
- package/dist/cli/plugins-config-tui.js +1 -1
- package/dist/cli/program/register.subclis.d.ts.map +1 -1
- package/dist/cli/program/register.subclis.js +18 -0
- package/dist/cli/sessions-checkpoints-cli.d.ts +6 -0
- package/dist/cli/sessions-checkpoints-cli.d.ts.map +1 -0
- package/dist/cli/sessions-checkpoints-cli.js +117 -0
- package/dist/cli/webhooks-cli.d.ts +13 -0
- package/dist/cli/webhooks-cli.d.ts.map +1 -1
- package/dist/cli/webhooks-cli.js +323 -130
- package/dist/errors/user-friendly-errors.d.ts +23 -0
- package/dist/errors/user-friendly-errors.d.ts.map +1 -0
- package/dist/errors/user-friendly-errors.js +182 -0
- package/dist/infra/net/fetch-guard.d.ts.map +1 -1
- package/dist/memory/wiki/store.d.ts +53 -0
- package/dist/memory/wiki/store.d.ts.map +1 -0
- package/dist/memory/wiki/store.js +222 -0
- package/dist/memory/wiki/types.d.ts +57 -0
- package/dist/memory/wiki/types.d.ts.map +1 -0
- package/dist/memory/wiki/types.js +6 -0
- package/dist/plugins/webhook-ingress.d.ts +104 -0
- package/dist/plugins/webhook-ingress.d.ts.map +1 -0
- package/dist/plugins/webhook-ingress.js +287 -0
- package/dist/providers/ollama-vision.d.ts +30 -0
- package/dist/providers/ollama-vision.d.ts.map +1 -0
- package/dist/providers/ollama-vision.js +62 -0
- package/dist/sessions/checkpoints.d.ts +76 -0
- package/dist/sessions/checkpoints.d.ts.map +1 -0
- package/dist/sessions/checkpoints.js +162 -0
- package/dist/slack/channel.d.ts.map +1 -1
- package/dist/slack/channel.js +13 -2
- package/package.json +1 -1
package/dist/cli/webhooks-cli.js
CHANGED
|
@@ -1,140 +1,333 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Webhook CLI - Manage webhook ingress routes
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* poolbot webhooks list # List all webhook routes
|
|
6
|
+
* poolbot webhooks create <path> # Create webhook route
|
|
7
|
+
* poolbot webhooks show <id> # Show webhook details
|
|
8
|
+
* poolbot webhooks delete <id> # Delete webhook route
|
|
9
|
+
* poolbot webhooks enable <id> # Enable webhook route
|
|
10
|
+
* poolbot webhooks disable <id> # Disable webhook route
|
|
11
|
+
* poolbot webhooks stats # Show webhook statistics
|
|
12
|
+
* poolbot webhooks serve # Start webhook server
|
|
13
|
+
*/
|
|
14
|
+
import crypto from "node:crypto";
|
|
15
|
+
import { loadConfig } from "../config/config.js";
|
|
16
|
+
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
|
4
17
|
import { defaultRuntime } from "../runtime.js";
|
|
5
|
-
import { formatDocsLink } from "../terminal/links.js";
|
|
6
18
|
import { theme } from "../terminal/theme.js";
|
|
19
|
+
import { danger, info, success } from "../globals.js";
|
|
20
|
+
import { formatDocsLink } from "../terminal/links.js";
|
|
21
|
+
import { createWebhookRoute, getWebhookRoute, listWebhookRoutes, deleteWebhookRoute, updateWebhookRoute, getWebhookStats, startWebhookServer, } from "../plugins/webhook-ingress.js";
|
|
22
|
+
import { runCommandWithRuntime } from "./cli-utils.js";
|
|
23
|
+
import { callGatewayCli } from "../gateway/call.js";
|
|
24
|
+
function runWebhookCommand(action) {
|
|
25
|
+
return runCommandWithRuntime(defaultRuntime, action);
|
|
26
|
+
}
|
|
7
27
|
export function registerWebhooksCli(program) {
|
|
8
28
|
const webhooks = program
|
|
9
29
|
.command("webhooks")
|
|
10
|
-
.description("
|
|
11
|
-
.addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.option("--
|
|
18
|
-
.option("--topic <name>", "Pub/Sub topic name", DEFAULT_GMAIL_TOPIC)
|
|
19
|
-
.option("--subscription <name>", "Pub/Sub subscription name", DEFAULT_GMAIL_SUBSCRIPTION)
|
|
20
|
-
.option("--label <label>", "Gmail label to watch", DEFAULT_GMAIL_LABEL)
|
|
21
|
-
.option("--hook-url <url>", "Poolbot hook URL")
|
|
22
|
-
.option("--hook-token <token>", "Poolbot hook token")
|
|
23
|
-
.option("--push-token <token>", "Push token for gog watch serve")
|
|
24
|
-
.option("--bind <host>", "gog watch serve bind host", DEFAULT_GMAIL_SERVE_BIND)
|
|
25
|
-
.option("--port <port>", "gog watch serve port", String(DEFAULT_GMAIL_SERVE_PORT))
|
|
26
|
-
.option("--path <path>", "gog watch serve path", DEFAULT_GMAIL_SERVE_PATH)
|
|
27
|
-
.option("--include-body", "Include email body snippets", true)
|
|
28
|
-
.option("--max-bytes <n>", "Max bytes for body snippets", String(DEFAULT_GMAIL_MAX_BYTES))
|
|
29
|
-
.option("--renew-minutes <n>", "Renew watch every N minutes", String(DEFAULT_GMAIL_RENEW_MINUTES))
|
|
30
|
-
.option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)", "funnel")
|
|
31
|
-
.option("--tailscale-path <path>", "Path for tailscale serve/funnel")
|
|
32
|
-
.option("--tailscale-target <target>", "Tailscale serve/funnel target (port, host:port, or URL)")
|
|
33
|
-
.option("--push-endpoint <url>", "Explicit Pub/Sub push endpoint")
|
|
34
|
-
.option("--json", "Output JSON summary", false)
|
|
30
|
+
.description("Manage webhook ingress routes (create/list/delete/serve)")
|
|
31
|
+
.addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/plugins/webhooks", "docs.molt.bot/plugins/webhooks")}\n` +
|
|
32
|
+
`${theme.muted("Tip:")} Use 'poolbot webhooks serve' to start the webhook server\n`);
|
|
33
|
+
// webhooks list
|
|
34
|
+
webhooks
|
|
35
|
+
.command("list")
|
|
36
|
+
.description("List webhook routes")
|
|
37
|
+
.option("--json", "Output JSON", false)
|
|
35
38
|
.action(async (opts) => {
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
await runWebhookCommand(async () => {
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
42
|
+
const routes = listWebhookRoutes(workspaceDir);
|
|
43
|
+
if (opts.json) {
|
|
44
|
+
defaultRuntime.log(JSON.stringify(routes, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
defaultRuntime.log("");
|
|
48
|
+
defaultRuntime.log(theme.heading("Webhook Routes"));
|
|
49
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
50
|
+
if (routes.length === 0) {
|
|
51
|
+
defaultRuntime.log(info("No webhook routes configured."));
|
|
52
|
+
defaultRuntime.log("Create one with: poolbot webhooks create /my-webhook");
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
for (const route of routes) {
|
|
56
|
+
const status = route.enabled ? theme.success("●") : theme.muted("○");
|
|
57
|
+
const methods = route.methods.join("/");
|
|
58
|
+
defaultRuntime.log(` ${status} ${route.path} [${methods}] ${route.enabled ? "" : theme.muted("(disabled)")}`);
|
|
59
|
+
defaultRuntime.log(theme.muted(` ID: ${route.id} | Triggers: ${route.triggerCount}`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
defaultRuntime.log("");
|
|
63
|
+
const stats = getWebhookStats(workspaceDir);
|
|
64
|
+
defaultRuntime.log(theme.muted(`Total: ${stats.total} routes | Enabled: ${stats.enabled} | Total triggers: ${stats.totalTriggers}`));
|
|
65
|
+
defaultRuntime.exit(0);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
// webhooks create
|
|
69
|
+
webhooks
|
|
70
|
+
.command("create <path>")
|
|
71
|
+
.description("Create webhook route")
|
|
72
|
+
.requiredOption("--agent <id>", "Agent ID to run")
|
|
73
|
+
.requiredOption("--message <text>", "Message/prompt to send")
|
|
74
|
+
.option("--session <key>", "Session key (optional)")
|
|
75
|
+
.option("--skills <list>", "Comma-separated skills to enable")
|
|
76
|
+
.option("--secret <secret>", "Shared secret (auto-generated if omitted)")
|
|
77
|
+
.option("--methods <methods>", "HTTP methods (default: POST)")
|
|
78
|
+
.option("--rate-limit <n>", "Rate limit per minute", Number.parseInt)
|
|
79
|
+
.action(async (path, opts) => {
|
|
80
|
+
await runWebhookCommand(async () => {
|
|
81
|
+
const config = loadConfig();
|
|
82
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
83
|
+
const secret = opts.secret ?? crypto.randomUUID();
|
|
84
|
+
const methods = opts.methods ? opts.methods.split(",").map((m) => m.trim()) : ["POST"];
|
|
85
|
+
const skills = opts.skills ? opts.skills.split(",").map((s) => s.trim()) : undefined;
|
|
86
|
+
try {
|
|
87
|
+
const routeId = createWebhookRoute(workspaceDir, {
|
|
88
|
+
path,
|
|
89
|
+
secret,
|
|
90
|
+
taskFlow: {
|
|
91
|
+
agentId: opts.agent ?? "main",
|
|
92
|
+
message: opts.message ?? "",
|
|
93
|
+
sessionKey: opts.session,
|
|
94
|
+
skills,
|
|
95
|
+
},
|
|
96
|
+
methods,
|
|
97
|
+
rateLimitPerMin: opts.rateLimit,
|
|
98
|
+
});
|
|
99
|
+
defaultRuntime.log("");
|
|
100
|
+
defaultRuntime.log(success(`Created webhook route: ${path}`));
|
|
101
|
+
defaultRuntime.log(` ID: ${routeId}`);
|
|
102
|
+
defaultRuntime.log(` Secret: ${theme.error(secret)} (keep this secret!)`);
|
|
103
|
+
defaultRuntime.log(` Methods: ${methods.join("/")}`);
|
|
104
|
+
defaultRuntime.log("");
|
|
105
|
+
defaultRuntime.log(info("Usage example:"));
|
|
106
|
+
defaultRuntime.log(` curl -X POST http://localhost:8888${path} \\`);
|
|
107
|
+
defaultRuntime.log(` -H "Content-Type: application/json" \\`);
|
|
108
|
+
defaultRuntime.log(` -H "X-Webhook-Signature: <hmac-sha256>" \\`);
|
|
109
|
+
defaultRuntime.log(` -d '{"key": "value"}'`);
|
|
110
|
+
defaultRuntime.log("");
|
|
111
|
+
defaultRuntime.log(info("Start the webhook server with: poolbot webhooks serve"));
|
|
112
|
+
defaultRuntime.exit(0);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
defaultRuntime.log(danger(`Failed to create webhook: ${error instanceof Error ? error.message : String(error)}`));
|
|
116
|
+
defaultRuntime.exit(1);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
// webhooks show
|
|
121
|
+
webhooks
|
|
122
|
+
.command("show <id>")
|
|
123
|
+
.description("Show webhook details")
|
|
124
|
+
.option("--json", "Output JSON", false)
|
|
125
|
+
.action(async (id, opts) => {
|
|
126
|
+
await runWebhookCommand(async () => {
|
|
127
|
+
const config = loadConfig();
|
|
128
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
129
|
+
const route = getWebhookRoute(workspaceDir, id);
|
|
130
|
+
if (!route) {
|
|
131
|
+
defaultRuntime.log(danger(`Webhook "${id}" not found.`));
|
|
132
|
+
defaultRuntime.exit(1);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (opts.json) {
|
|
136
|
+
defaultRuntime.log(JSON.stringify(route, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
defaultRuntime.log("");
|
|
140
|
+
defaultRuntime.log(theme.heading(`Webhook: ${route.path}`));
|
|
141
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
142
|
+
defaultRuntime.log(` ID: ${route.id}`);
|
|
143
|
+
defaultRuntime.log(` Path: ${route.path}`);
|
|
144
|
+
defaultRuntime.log(` Status: ${route.enabled ? theme.success("enabled") : theme.warn("disabled")}`);
|
|
145
|
+
defaultRuntime.log(` Methods: ${route.methods.join("/")}`);
|
|
146
|
+
defaultRuntime.log(` Rate limit: ${route.rateLimitPerMin ?? "none"} /min`);
|
|
147
|
+
defaultRuntime.log(` Created: ${new Date(route.createdAt).toLocaleString()}`);
|
|
148
|
+
if (route.lastTriggeredAt) {
|
|
149
|
+
defaultRuntime.log(` Last triggered: ${new Date(route.lastTriggeredAt).toLocaleString()}`);
|
|
150
|
+
}
|
|
151
|
+
defaultRuntime.log(` Trigger count: ${route.triggerCount}`);
|
|
152
|
+
defaultRuntime.log("");
|
|
153
|
+
defaultRuntime.log(theme.heading("Task Flow"));
|
|
154
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
155
|
+
defaultRuntime.log(` Agent: ${route.taskFlow.agentId}`);
|
|
156
|
+
defaultRuntime.log(` Message: ${route.taskFlow.message.substring(0, 100)}...`);
|
|
157
|
+
if (route.taskFlow.sessionKey) {
|
|
158
|
+
defaultRuntime.log(` Session: ${route.taskFlow.sessionKey}`);
|
|
159
|
+
}
|
|
160
|
+
if (route.taskFlow.skills?.length) {
|
|
161
|
+
defaultRuntime.log(` Skills: ${route.taskFlow.skills.join(", ")}`);
|
|
162
|
+
}
|
|
163
|
+
defaultRuntime.exit(0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
// webhooks delete
|
|
167
|
+
webhooks
|
|
168
|
+
.command("delete <id>")
|
|
169
|
+
.description("Delete webhook route")
|
|
170
|
+
.option("--yes", "Skip confirmation", false)
|
|
171
|
+
.action(async (id, opts) => {
|
|
172
|
+
await runWebhookCommand(async () => {
|
|
173
|
+
const config = loadConfig();
|
|
174
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
175
|
+
const route = getWebhookRoute(workspaceDir, id);
|
|
176
|
+
if (!route) {
|
|
177
|
+
defaultRuntime.log(danger(`Webhook "${id}" not found.`));
|
|
178
|
+
defaultRuntime.exit(1);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!opts.yes) {
|
|
182
|
+
defaultRuntime.log("");
|
|
183
|
+
defaultRuntime.log(danger(`⚠️ This will permanently delete webhook "${route.path}"`));
|
|
184
|
+
defaultRuntime.log("This action cannot be undone.");
|
|
185
|
+
defaultRuntime.log("");
|
|
186
|
+
defaultRuntime.log(info("Use --yes to skip this confirmation."));
|
|
187
|
+
defaultRuntime.exit(1);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const deleted = deleteWebhookRoute(workspaceDir, id);
|
|
191
|
+
if (deleted) {
|
|
192
|
+
defaultRuntime.log("");
|
|
193
|
+
defaultRuntime.log(success(`Webhook "${route.path}" deleted.`));
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
defaultRuntime.log(danger(`Failed to delete webhook "${id}".`));
|
|
197
|
+
defaultRuntime.exit(1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
// webhooks enable/disable
|
|
202
|
+
webhooks
|
|
203
|
+
.command("enable <id>")
|
|
204
|
+
.description("Enable webhook route")
|
|
205
|
+
.action(async (id) => {
|
|
206
|
+
await runWebhookCommand(async () => {
|
|
207
|
+
const config = loadConfig();
|
|
208
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
209
|
+
const route = updateWebhookRoute(workspaceDir, id, { enabled: true });
|
|
210
|
+
if (!route) {
|
|
211
|
+
defaultRuntime.log(danger(`Webhook "${id}" not found.`));
|
|
212
|
+
defaultRuntime.exit(1);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
defaultRuntime.log("");
|
|
216
|
+
defaultRuntime.log(success(`Webhook "${route.path}" enabled.`));
|
|
217
|
+
defaultRuntime.exit(0);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
webhooks
|
|
221
|
+
.command("disable <id>")
|
|
222
|
+
.description("Disable webhook route")
|
|
223
|
+
.action(async (id) => {
|
|
224
|
+
await runWebhookCommand(async () => {
|
|
225
|
+
const config = loadConfig();
|
|
226
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
227
|
+
const route = updateWebhookRoute(workspaceDir, id, { enabled: false });
|
|
228
|
+
if (!route) {
|
|
229
|
+
defaultRuntime.log(danger(`Webhook "${id}" not found.`));
|
|
230
|
+
defaultRuntime.exit(1);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
defaultRuntime.log("");
|
|
234
|
+
defaultRuntime.log(success(`Webhook "${route.path}" disabled.`));
|
|
235
|
+
defaultRuntime.exit(0);
|
|
236
|
+
});
|
|
44
237
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.option("--
|
|
50
|
-
.option("--subscription <name>", "Pub/Sub subscription name")
|
|
51
|
-
.option("--label <label>", "Gmail label to watch")
|
|
52
|
-
.option("--hook-url <url>", "Poolbot hook URL")
|
|
53
|
-
.option("--hook-token <token>", "Poolbot hook token")
|
|
54
|
-
.option("--push-token <token>", "Push token for gog watch serve")
|
|
55
|
-
.option("--bind <host>", "gog watch serve bind host")
|
|
56
|
-
.option("--port <port>", "gog watch serve port")
|
|
57
|
-
.option("--path <path>", "gog watch serve path")
|
|
58
|
-
.option("--include-body", "Include email body snippets")
|
|
59
|
-
.option("--max-bytes <n>", "Max bytes for body snippets")
|
|
60
|
-
.option("--renew-minutes <n>", "Renew watch every N minutes")
|
|
61
|
-
.option("--tailscale <mode>", "Expose push endpoint via tailscale (funnel|serve|off)")
|
|
62
|
-
.option("--tailscale-path <path>", "Path for tailscale serve/funnel")
|
|
63
|
-
.option("--tailscale-target <target>", "Tailscale serve/funnel target (port, host:port, or URL)")
|
|
238
|
+
// webhooks stats
|
|
239
|
+
webhooks
|
|
240
|
+
.command("stats")
|
|
241
|
+
.description("Show webhook statistics")
|
|
242
|
+
.option("--json", "Output JSON", false)
|
|
64
243
|
.action(async (opts) => {
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
244
|
+
await runWebhookCommand(async () => {
|
|
245
|
+
const config = loadConfig();
|
|
246
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
247
|
+
const stats = getWebhookStats(workspaceDir);
|
|
248
|
+
if (opts.json) {
|
|
249
|
+
defaultRuntime.log(JSON.stringify(stats, null, 2));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
defaultRuntime.log("");
|
|
253
|
+
defaultRuntime.log(theme.heading("Webhook Statistics"));
|
|
254
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
255
|
+
defaultRuntime.log(` Total routes: ${stats.total}`);
|
|
256
|
+
defaultRuntime.log(` Enabled routes: ${stats.enabled}`);
|
|
257
|
+
defaultRuntime.log(` Total triggers: ${stats.totalTriggers}`);
|
|
258
|
+
defaultRuntime.log("");
|
|
259
|
+
if (stats.routes.length > 0) {
|
|
260
|
+
defaultRuntime.log(theme.heading("Top Routes"));
|
|
261
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
262
|
+
const topRoutes = stats.routes.sort((a, b) => b.triggers - a.triggers).slice(0, 5);
|
|
263
|
+
for (const route of topRoutes) {
|
|
264
|
+
defaultRuntime.log(` ${route.path}: ${route.triggers} triggers`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
defaultRuntime.exit(0);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
// webhooks serve
|
|
271
|
+
webhooks
|
|
272
|
+
.command("serve")
|
|
273
|
+
.description("Start webhook server")
|
|
274
|
+
.option("--port <n>", "Port to listen on", Number.parseInt)
|
|
275
|
+
.action(async (opts) => {
|
|
276
|
+
await runWebhookCommand(async () => {
|
|
277
|
+
const config = loadConfig();
|
|
278
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, "main");
|
|
279
|
+
const port = opts.port ?? 8888;
|
|
280
|
+
defaultRuntime.log("");
|
|
281
|
+
defaultRuntime.log(theme.heading("Starting Webhook Server"));
|
|
282
|
+
defaultRuntime.log(theme.muted("─────────────────────────────────────────"));
|
|
283
|
+
const routes = listWebhookRoutes(workspaceDir);
|
|
284
|
+
if (routes.length === 0) {
|
|
285
|
+
defaultRuntime.log(info("No webhook routes configured."));
|
|
286
|
+
defaultRuntime.log("Create one with: poolbot webhooks create /my-webhook");
|
|
287
|
+
defaultRuntime.exit(0);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
defaultRuntime.log(` Port: ${port}`);
|
|
291
|
+
defaultRuntime.log(` Routes: ${routes.length}`);
|
|
292
|
+
defaultRuntime.log("");
|
|
293
|
+
defaultRuntime.log("Registered routes:");
|
|
294
|
+
for (const route of routes) {
|
|
295
|
+
const status = route.enabled ? theme.success("✓") : theme.muted("○");
|
|
296
|
+
defaultRuntime.log(` ${status} ${route.path} [${route.methods.join("/")}]`);
|
|
297
|
+
}
|
|
298
|
+
defaultRuntime.log("");
|
|
299
|
+
// Start server
|
|
300
|
+
const { server } = await startWebhookServer(workspaceDir, port, async (route, payload) => {
|
|
301
|
+
// Execute agent with task flow configuration
|
|
302
|
+
defaultRuntime.log(`Triggered: ${route.path} -> ${route.taskFlow.agentId}`);
|
|
303
|
+
defaultRuntime.log(` Message: ${route.taskFlow.message}`);
|
|
304
|
+
try {
|
|
305
|
+
// Call gateway to run agent
|
|
306
|
+
await callGatewayCli({
|
|
307
|
+
method: "agent.run",
|
|
308
|
+
params: {
|
|
309
|
+
agentId: route.taskFlow.agentId,
|
|
310
|
+
message: route.taskFlow.message,
|
|
311
|
+
sessionKey: route.taskFlow.sessionKey,
|
|
312
|
+
skills: route.taskFlow.skills,
|
|
313
|
+
context: { webhookPayload: payload, webhookRoute: route.path },
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
defaultRuntime.log(success(`Agent executed successfully`));
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
defaultRuntime.log(danger(`Agent execution failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
defaultRuntime.log(info("Press Ctrl+C to stop"));
|
|
324
|
+
// Handle shutdown
|
|
325
|
+
process.on("SIGINT", () => {
|
|
326
|
+
defaultRuntime.log("");
|
|
327
|
+
defaultRuntime.log(info("Shutting down webhook server..."));
|
|
328
|
+
server.close();
|
|
329
|
+
process.exit(0);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
73
332
|
});
|
|
74
|
-
}
|
|
75
|
-
function parseGmailSetupOptions(raw) {
|
|
76
|
-
const accountRaw = raw.account;
|
|
77
|
-
const account = typeof accountRaw === "string" ? accountRaw.trim() : "";
|
|
78
|
-
if (!account)
|
|
79
|
-
throw new Error("--account is required");
|
|
80
|
-
return {
|
|
81
|
-
account,
|
|
82
|
-
project: stringOption(raw.project),
|
|
83
|
-
topic: stringOption(raw.topic),
|
|
84
|
-
subscription: stringOption(raw.subscription),
|
|
85
|
-
label: stringOption(raw.label),
|
|
86
|
-
hookUrl: stringOption(raw.hookUrl),
|
|
87
|
-
hookToken: stringOption(raw.hookToken),
|
|
88
|
-
pushToken: stringOption(raw.pushToken),
|
|
89
|
-
bind: stringOption(raw.bind),
|
|
90
|
-
port: numberOption(raw.port),
|
|
91
|
-
path: stringOption(raw.path),
|
|
92
|
-
includeBody: booleanOption(raw.includeBody),
|
|
93
|
-
maxBytes: numberOption(raw.maxBytes),
|
|
94
|
-
renewEveryMinutes: numberOption(raw.renewMinutes),
|
|
95
|
-
tailscale: stringOption(raw.tailscale),
|
|
96
|
-
tailscalePath: stringOption(raw.tailscalePath),
|
|
97
|
-
tailscaleTarget: stringOption(raw.tailscaleTarget),
|
|
98
|
-
pushEndpoint: stringOption(raw.pushEndpoint),
|
|
99
|
-
json: Boolean(raw.json),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function parseGmailRunOptions(raw) {
|
|
103
|
-
return {
|
|
104
|
-
account: stringOption(raw.account),
|
|
105
|
-
topic: stringOption(raw.topic),
|
|
106
|
-
subscription: stringOption(raw.subscription),
|
|
107
|
-
label: stringOption(raw.label),
|
|
108
|
-
hookUrl: stringOption(raw.hookUrl),
|
|
109
|
-
hookToken: stringOption(raw.hookToken),
|
|
110
|
-
pushToken: stringOption(raw.pushToken),
|
|
111
|
-
bind: stringOption(raw.bind),
|
|
112
|
-
port: numberOption(raw.port),
|
|
113
|
-
path: stringOption(raw.path),
|
|
114
|
-
includeBody: booleanOption(raw.includeBody),
|
|
115
|
-
maxBytes: numberOption(raw.maxBytes),
|
|
116
|
-
renewEveryMinutes: numberOption(raw.renewMinutes),
|
|
117
|
-
tailscale: stringOption(raw.tailscale),
|
|
118
|
-
tailscalePath: stringOption(raw.tailscalePath),
|
|
119
|
-
tailscaleTarget: stringOption(raw.tailscaleTarget),
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
function stringOption(value) {
|
|
123
|
-
if (typeof value !== "string")
|
|
124
|
-
return undefined;
|
|
125
|
-
const trimmed = value.trim();
|
|
126
|
-
return trimmed ? trimmed : undefined;
|
|
127
|
-
}
|
|
128
|
-
function numberOption(value) {
|
|
129
|
-
if (value === undefined || value === null)
|
|
130
|
-
return undefined;
|
|
131
|
-
const n = typeof value === "number" ? value : Number(value);
|
|
132
|
-
if (!Number.isFinite(n) || n <= 0)
|
|
133
|
-
return undefined;
|
|
134
|
-
return Math.floor(n);
|
|
135
|
-
}
|
|
136
|
-
function booleanOption(value) {
|
|
137
|
-
if (value === undefined || value === null)
|
|
138
|
-
return undefined;
|
|
139
|
-
return Boolean(value);
|
|
140
333
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-Friendly Error Messages
|
|
3
|
+
*
|
|
4
|
+
* Transforms technical errors into clear, actionable messages.
|
|
5
|
+
*/
|
|
6
|
+
export type ErrorCategory = "auth" | "network" | "config" | "model" | "rate_limit" | "permission" | "resource" | "validation" | "unknown";
|
|
7
|
+
export type UserFriendlyError = {
|
|
8
|
+
category: ErrorCategory;
|
|
9
|
+
message: string;
|
|
10
|
+
cause?: string;
|
|
11
|
+
solution: string[];
|
|
12
|
+
docsLink?: string;
|
|
13
|
+
retryable?: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Convert an error to a user-friendly error.
|
|
17
|
+
*/
|
|
18
|
+
export declare function toUserFriendlyError(error: unknown): UserFriendlyError;
|
|
19
|
+
/**
|
|
20
|
+
* Format a user-friendly error for display.
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatUserFriendlyError(err: UserFriendlyError): string;
|
|
23
|
+
//# sourceMappingURL=user-friendly-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-friendly-errors.d.ts","sourceRoot":"","sources":["../../src/errors/user-friendly-errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,SAAS,GACT,QAAQ,GACR,OAAO,GACP,YAAY,GACZ,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,SAAS,CAAC;AAEd,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA4IF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CA2BrE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CA0BtE"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-Friendly Error Messages
|
|
3
|
+
*
|
|
4
|
+
* Transforms technical errors into clear, actionable messages.
|
|
5
|
+
*/
|
|
6
|
+
const ERROR_PATTERNS = [
|
|
7
|
+
// Auth errors
|
|
8
|
+
{
|
|
9
|
+
pattern: /unauthorized|401|authentication failed/i,
|
|
10
|
+
category: "auth",
|
|
11
|
+
message: "Authentication failed",
|
|
12
|
+
solution: [
|
|
13
|
+
"Check your API key or token is correct",
|
|
14
|
+
"Ensure the token hasn't expired",
|
|
15
|
+
"Run 'poolbot secret get <name>' to verify",
|
|
16
|
+
],
|
|
17
|
+
docsLink: "/auth/troubleshooting",
|
|
18
|
+
retryable: false,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pattern: /forbidden|403|access denied/i,
|
|
22
|
+
category: "permission",
|
|
23
|
+
message: "Access denied",
|
|
24
|
+
solution: [
|
|
25
|
+
"Check you have the required permissions",
|
|
26
|
+
"Contact your administrator to grant access",
|
|
27
|
+
],
|
|
28
|
+
docsLink: "/auth/permissions",
|
|
29
|
+
retryable: false,
|
|
30
|
+
},
|
|
31
|
+
// Network errors
|
|
32
|
+
{
|
|
33
|
+
pattern: /network|connection.*refused|ECONNREFUSED/i,
|
|
34
|
+
category: "network",
|
|
35
|
+
message: "Network connection failed",
|
|
36
|
+
solution: [
|
|
37
|
+
"Check your internet connection",
|
|
38
|
+
"Verify the service is running",
|
|
39
|
+
"Try again in a few moments",
|
|
40
|
+
],
|
|
41
|
+
retryable: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
pattern: /timeout|ETIMEDOUT/i,
|
|
45
|
+
category: "network",
|
|
46
|
+
message: "Request timed out",
|
|
47
|
+
solution: [
|
|
48
|
+
"The service is taking too long to respond",
|
|
49
|
+
"Check if the service is overloaded",
|
|
50
|
+
"Try again with a smaller request",
|
|
51
|
+
],
|
|
52
|
+
retryable: true,
|
|
53
|
+
},
|
|
54
|
+
// Rate limit errors
|
|
55
|
+
{
|
|
56
|
+
pattern: /rate.*limit|429|too.*many.*request/i,
|
|
57
|
+
category: "rate_limit",
|
|
58
|
+
message: "Rate limit exceeded",
|
|
59
|
+
solution: [
|
|
60
|
+
"You've made too many requests",
|
|
61
|
+
"Wait before trying again",
|
|
62
|
+
"Consider upgrading your plan for higher limits",
|
|
63
|
+
],
|
|
64
|
+
docsLink: "/limits",
|
|
65
|
+
retryable: true,
|
|
66
|
+
},
|
|
67
|
+
// Model errors
|
|
68
|
+
{
|
|
69
|
+
pattern: /model.*not.*found|invalid.*model/i,
|
|
70
|
+
category: "model",
|
|
71
|
+
message: "Model not found",
|
|
72
|
+
solution: [
|
|
73
|
+
"Check the model name is correct",
|
|
74
|
+
"Run 'poolbot models list' to see available models",
|
|
75
|
+
"Ensure the model provider is configured",
|
|
76
|
+
],
|
|
77
|
+
docsLink: "/models",
|
|
78
|
+
retryable: false,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
pattern: /context.*exceeded|context.*window|token.*limit/i,
|
|
82
|
+
category: "model",
|
|
83
|
+
message: "Context window exceeded",
|
|
84
|
+
solution: [
|
|
85
|
+
"Your message is too long for this model",
|
|
86
|
+
"Try splitting your request into smaller parts",
|
|
87
|
+
"Use a model with a larger context window",
|
|
88
|
+
],
|
|
89
|
+
docsLink: "/models/context",
|
|
90
|
+
retryable: false,
|
|
91
|
+
},
|
|
92
|
+
// Config errors
|
|
93
|
+
{
|
|
94
|
+
pattern: /config.*not.*found|missing.*config/i,
|
|
95
|
+
category: "config",
|
|
96
|
+
message: "Configuration missing",
|
|
97
|
+
solution: [
|
|
98
|
+
"Run 'poolbot config show' to check your config",
|
|
99
|
+
"Set the required configuration values",
|
|
100
|
+
"Run 'poolbot onboard' for guided setup",
|
|
101
|
+
],
|
|
102
|
+
docsLink: "/config",
|
|
103
|
+
retryable: false,
|
|
104
|
+
},
|
|
105
|
+
// Resource errors
|
|
106
|
+
{
|
|
107
|
+
pattern: /not.*found|404|resource.*not.*exist/i,
|
|
108
|
+
category: "resource",
|
|
109
|
+
message: "Resource not found",
|
|
110
|
+
solution: [
|
|
111
|
+
"Check the resource ID is correct",
|
|
112
|
+
"Ensure the resource exists",
|
|
113
|
+
"List available resources to verify",
|
|
114
|
+
],
|
|
115
|
+
retryable: false,
|
|
116
|
+
},
|
|
117
|
+
// Validation errors
|
|
118
|
+
{
|
|
119
|
+
pattern: /invalid|validation.*failed|bad.*request|400/i,
|
|
120
|
+
category: "validation",
|
|
121
|
+
message: "Invalid input",
|
|
122
|
+
solution: [
|
|
123
|
+
"Check your input format",
|
|
124
|
+
"Review the required fields",
|
|
125
|
+
"See the documentation for examples",
|
|
126
|
+
],
|
|
127
|
+
retryable: false,
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
/**
|
|
131
|
+
* Convert an error to a user-friendly error.
|
|
132
|
+
*/
|
|
133
|
+
export function toUserFriendlyError(error) {
|
|
134
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
135
|
+
for (const pattern of ERROR_PATTERNS) {
|
|
136
|
+
if (pattern.pattern.test(errorMessage)) {
|
|
137
|
+
return {
|
|
138
|
+
category: pattern.category,
|
|
139
|
+
message: pattern.message,
|
|
140
|
+
cause: errorMessage,
|
|
141
|
+
solution: pattern.solution,
|
|
142
|
+
docsLink: pattern.docsLink,
|
|
143
|
+
retryable: pattern.retryable,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
category: "unknown",
|
|
149
|
+
message: "An unexpected error occurred",
|
|
150
|
+
cause: errorMessage,
|
|
151
|
+
solution: [
|
|
152
|
+
"Try again",
|
|
153
|
+
"Check the logs for more details",
|
|
154
|
+
"Contact support if the issue persists",
|
|
155
|
+
],
|
|
156
|
+
retryable: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Format a user-friendly error for display.
|
|
161
|
+
*/
|
|
162
|
+
export function formatUserFriendlyError(err) {
|
|
163
|
+
const lines = [];
|
|
164
|
+
lines.push(`❌ ${err.message}`);
|
|
165
|
+
if (err.cause && err.cause !== err.message) {
|
|
166
|
+
lines.push(` Cause: ${err.cause}`);
|
|
167
|
+
}
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("Solutions:");
|
|
170
|
+
for (const solution of err.solution) {
|
|
171
|
+
lines.push(` • ${solution}`);
|
|
172
|
+
}
|
|
173
|
+
if (err.docsLink) {
|
|
174
|
+
lines.push("");
|
|
175
|
+
lines.push(`📖 Documentation: ${err.docsLink}`);
|
|
176
|
+
}
|
|
177
|
+
if (err.retryable) {
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push("💡 This error is temporary. You can retry.");
|
|
180
|
+
}
|
|
181
|
+
return lines.join("\n");
|
|
182
|
+
}
|