@poolzin/pool-bot 2026.3.4 → 2026.3.7
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/CHANGELOG.md +10 -0
- package/assets/pool-bot-icon-dark.png +0 -0
- package/assets/pool-bot-logo-1.png +0 -0
- package/assets/pool-bot-mascot.png +0 -0
- package/dist/agents/pi-embedded-runner/tool-result-truncation.js +62 -7
- package/dist/agents/pi-tools.js +32 -2
- package/dist/agents/poolbot-tools.js +12 -0
- package/dist/agents/session-write-lock.js +93 -8
- package/dist/agents/tools/pdf-native-providers.js +102 -0
- package/dist/agents/tools/pdf-tool.helpers.js +86 -0
- package/dist/agents/tools/pdf-tool.js +508 -0
- package/dist/auto-reply/reply/get-reply.js +6 -0
- package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/banner.js +20 -1
- package/dist/cli/security-cli.js +211 -2
- package/dist/cli/tagline.js +7 -0
- package/dist/config/types.cli.js +1 -0
- package/dist/config/types.security.js +33 -0
- package/dist/config/zod-schema.js +15 -0
- package/dist/config/zod-schema.providers-core.js +1 -0
- package/dist/config/zod-schema.security.js +113 -0
- package/dist/cron/normalize.js +3 -0
- package/dist/cron/service/jobs.js +48 -0
- package/dist/discord/monitor/message-handler.preflight.js +11 -2
- package/dist/gateway/http-common.js +6 -1
- package/dist/gateway/protocol/schema/cron.js +3 -0
- package/dist/gateway/server-channels.js +99 -14
- package/dist/gateway/server-cron.js +89 -0
- package/dist/gateway/server-health-probes.js +55 -0
- package/dist/gateway/server-http.js +5 -0
- package/dist/hooks/bundled/session-memory/handler.js +8 -2
- package/dist/hooks/fire-and-forget.js +6 -0
- package/dist/hooks/internal-hooks.js +64 -19
- package/dist/hooks/message-hook-mappers.js +179 -0
- package/dist/infra/abort-signal.js +12 -0
- package/dist/infra/boundary-file-read.js +118 -0
- package/dist/infra/boundary-path.js +594 -0
- package/dist/infra/file-identity.js +12 -0
- package/dist/infra/fs-safe.js +377 -12
- package/dist/infra/hardlink-guards.js +30 -0
- package/dist/infra/json-utf8-bytes.js +8 -0
- package/dist/infra/net/fetch-guard.js +63 -13
- package/dist/infra/net/proxy-env.js +17 -0
- package/dist/infra/net/ssrf.js +74 -272
- package/dist/infra/path-alias-guards.js +21 -0
- package/dist/infra/path-guards.js +13 -1
- package/dist/infra/ports-probe.js +19 -0
- package/dist/infra/prototype-keys.js +4 -0
- package/dist/infra/restart-stale-pids.js +254 -0
- package/dist/infra/safe-open-sync.js +71 -0
- package/dist/infra/secure-random.js +7 -0
- package/dist/media/ffmpeg-limits.js +4 -0
- package/dist/media/input-files.js +6 -2
- package/dist/media/temp-files.js +12 -0
- package/dist/memory/embedding-chunk-limits.js +5 -2
- package/dist/memory/embeddings-ollama.js +91 -138
- package/dist/memory/embeddings-remote-fetch.js +11 -10
- package/dist/memory/embeddings.js +25 -9
- package/dist/memory/manager-embedding-ops.js +1 -1
- package/dist/memory/post-json.js +23 -0
- package/dist/memory/qmd-manager.js +272 -77
- package/dist/memory/remote-http.js +33 -0
- package/dist/plugin-sdk/windows-spawn.js +214 -0
- package/dist/security/capability-guards.js +89 -0
- package/dist/security/capability-manager.js +76 -0
- package/dist/security/capability.js +147 -0
- package/dist/security/index.js +7 -0
- package/dist/security/middleware.js +105 -0
- package/dist/shared/net/ip-test-fixtures.js +1 -0
- package/dist/shared/net/ip.js +303 -0
- package/dist/shared/net/ipv4.js +8 -11
- package/dist/shared/pid-alive.js +59 -2
- package/dist/slack/monitor/context.js +1 -0
- package/dist/slack/monitor/message-handler/dispatch.js +14 -1
- package/dist/slack/monitor/provider.js +2 -0
- package/dist/test-helpers/ssrf.js +13 -0
- package/dist/tui/tui.js +9 -4
- package/dist/utils/fetch-timeout.js +12 -1
- package/docs/adr/003-feature-gap-analysis.md +112 -0
- package/package.json +10 -4
package/dist/cli/security-cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { loadConfig } from "../config/config.js";
|
|
1
|
+
import { loadConfig, writeConfigFile } from "../config/config.js";
|
|
2
2
|
import { defaultRuntime } from "../runtime.js";
|
|
3
3
|
import { runSecurityAudit } from "../security/audit.js";
|
|
4
4
|
import { fixSecurityFootguns } from "../security/fix.js";
|
|
5
|
+
import { CAPABILITY_TYPES } from "../security/capability.js";
|
|
5
6
|
import { formatDocsLink } from "../terminal/links.js";
|
|
6
7
|
import { isRich, theme } from "../terminal/theme.js";
|
|
7
8
|
import { shortenHomeInString, shortenHomePath } from "../utils.js";
|
|
@@ -17,10 +18,24 @@ function formatSummary(summary) {
|
|
|
17
18
|
parts.push(rich ? theme.muted(`${i} info`) : `${i} info`);
|
|
18
19
|
return parts.join(" · ");
|
|
19
20
|
}
|
|
21
|
+
function formatCapability(cap) {
|
|
22
|
+
let result = cap.type;
|
|
23
|
+
if ("pattern" in cap && cap.pattern)
|
|
24
|
+
result += ` pattern="${cap.pattern}"`;
|
|
25
|
+
if ("toolId" in cap && cap.toolId)
|
|
26
|
+
result += ` tool="${cap.toolId}"`;
|
|
27
|
+
if ("limit" in cap && cap.limit)
|
|
28
|
+
result += ` limit=${cap.limit}`;
|
|
29
|
+
if ("scope" in cap && cap.scope)
|
|
30
|
+
result += ` scope="${cap.scope}"`;
|
|
31
|
+
if ("port" in cap && cap.port)
|
|
32
|
+
result += ` port=${cap.port}`;
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
20
35
|
export function registerSecurityCli(program) {
|
|
21
36
|
const security = program
|
|
22
37
|
.command("security")
|
|
23
|
-
.description("Security tools (audit)")
|
|
38
|
+
.description("Security tools (audit, capabilities)")
|
|
24
39
|
.addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.molt.bot/cli/security")}\n`);
|
|
25
40
|
security
|
|
26
41
|
.command("audit")
|
|
@@ -120,4 +135,198 @@ export function registerSecurityCli(program) {
|
|
|
120
135
|
render("info");
|
|
121
136
|
defaultRuntime.log(lines.join("\n"));
|
|
122
137
|
});
|
|
138
|
+
// Capability management commands
|
|
139
|
+
const capabilities = security
|
|
140
|
+
.command("capabilities")
|
|
141
|
+
.description("Manage agent capabilities (permission system)");
|
|
142
|
+
capabilities
|
|
143
|
+
.command("grant")
|
|
144
|
+
.description("Grant a capability to an agent or pattern")
|
|
145
|
+
.argument("<type>", `Capability type (${CAPABILITY_TYPES.join(", ")})`)
|
|
146
|
+
.option("-a, --agent <pattern>", "Agent ID pattern (e.g., 'agent-*', 'coder')", "*")
|
|
147
|
+
.option("-p, --pattern <glob>", "File path pattern for file:* capabilities")
|
|
148
|
+
.option("-t, --tool <name>", "Tool name for tool:invoke capability")
|
|
149
|
+
.option("-h, --host <hostname>", "Host pattern for net:connect capability")
|
|
150
|
+
.option("-m, --max-tokens <n>", "Max tokens for llm:maxTokens capability")
|
|
151
|
+
.option("--json", "Print JSON output")
|
|
152
|
+
.action(async (type, opts) => {
|
|
153
|
+
const rich = isRich();
|
|
154
|
+
const cfg = loadConfig();
|
|
155
|
+
// Validate capability type
|
|
156
|
+
if (!CAPABILITY_TYPES.includes(type)) {
|
|
157
|
+
defaultRuntime.log(rich
|
|
158
|
+
? theme.error(`Invalid capability type: ${type}`)
|
|
159
|
+
: `Invalid capability type: ${type}`);
|
|
160
|
+
defaultRuntime.log(`Valid types: ${CAPABILITY_TYPES.join(", ")}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
// Build capability object
|
|
164
|
+
const capability = { type: type };
|
|
165
|
+
if (opts.pattern && (type.startsWith("file:") || type === "shell:exec")) {
|
|
166
|
+
capability.pattern = opts.pattern;
|
|
167
|
+
}
|
|
168
|
+
if (opts.tool && type === "tool:invoke") {
|
|
169
|
+
capability.toolId = opts.tool;
|
|
170
|
+
}
|
|
171
|
+
if (opts.host && type === "net:connect") {
|
|
172
|
+
capability.pattern = opts.host;
|
|
173
|
+
}
|
|
174
|
+
if (opts.maxTokens && type === "llm:maxTokens") {
|
|
175
|
+
capability.limit = parseInt(opts.maxTokens, 10);
|
|
176
|
+
}
|
|
177
|
+
// Initialize security config if needed
|
|
178
|
+
if (!cfg.security) {
|
|
179
|
+
cfg.security = { enabled: true };
|
|
180
|
+
}
|
|
181
|
+
if (!cfg.security.agents) {
|
|
182
|
+
cfg.security.agents = [];
|
|
183
|
+
}
|
|
184
|
+
// Find or create agent entry
|
|
185
|
+
const agentId = opts.agent || "*";
|
|
186
|
+
let agentEntry = cfg.security.agents.find((a) => a.agentId === agentId);
|
|
187
|
+
if (!agentEntry) {
|
|
188
|
+
agentEntry = { agentId, capabilities: [] };
|
|
189
|
+
cfg.security.agents.push(agentEntry);
|
|
190
|
+
}
|
|
191
|
+
// Add capability
|
|
192
|
+
agentEntry.capabilities.push(capability);
|
|
193
|
+
// Write config
|
|
194
|
+
await writeConfigFile(cfg);
|
|
195
|
+
if (opts.json) {
|
|
196
|
+
defaultRuntime.log(JSON.stringify({ success: true, capability, agentId }, null, 2));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
defaultRuntime.log(rich
|
|
200
|
+
? theme.success(`Granted ${type} to agent "${agentId}"`)
|
|
201
|
+
: `Granted ${type} to agent "${agentId}"`);
|
|
202
|
+
defaultRuntime.log(formatCapability(capability));
|
|
203
|
+
});
|
|
204
|
+
capabilities
|
|
205
|
+
.command("revoke")
|
|
206
|
+
.description("Revoke capabilities from an agent")
|
|
207
|
+
.argument("[type]", `Capability type to revoke (omit to revoke all)`)
|
|
208
|
+
.option("-a, --agent <pattern>", "Agent ID pattern", "*")
|
|
209
|
+
.option("--json", "Print JSON output")
|
|
210
|
+
.action(async (type, opts) => {
|
|
211
|
+
const rich = isRich();
|
|
212
|
+
const cfg = loadConfig();
|
|
213
|
+
if (!cfg.security?.agents || cfg.security.agents.length === 0) {
|
|
214
|
+
if (opts.json) {
|
|
215
|
+
defaultRuntime.log(JSON.stringify({ success: true, removed: 0 }));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
defaultRuntime.log(rich ? theme.muted("No capabilities configured") : "No capabilities configured");
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const agentId = opts.agent || "*";
|
|
223
|
+
const agentEntry = cfg.security.agents.find((a) => a.agentId === agentId);
|
|
224
|
+
if (!agentEntry) {
|
|
225
|
+
if (opts.json) {
|
|
226
|
+
defaultRuntime.log(JSON.stringify({ success: true, removed: 0 }));
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
defaultRuntime.log(rich
|
|
230
|
+
? theme.muted(`No capabilities found for agent "${agentId}"`)
|
|
231
|
+
: `No capabilities found for agent "${agentId}"`);
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const beforeCount = agentEntry.capabilities.length;
|
|
236
|
+
if (type) {
|
|
237
|
+
agentEntry.capabilities = agentEntry.capabilities.filter((c) => c.type !== type);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
agentEntry.capabilities = [];
|
|
241
|
+
}
|
|
242
|
+
const removed = beforeCount - agentEntry.capabilities.length;
|
|
243
|
+
// Remove empty agent entries
|
|
244
|
+
cfg.security.agents = cfg.security.agents.filter((a) => a.capabilities.length > 0);
|
|
245
|
+
await writeConfigFile(cfg);
|
|
246
|
+
if (opts.json) {
|
|
247
|
+
defaultRuntime.log(JSON.stringify({ success: true, removed }));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (removed === 0) {
|
|
251
|
+
defaultRuntime.log(rich ? theme.muted("No matching capabilities found") : "No matching capabilities found");
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
defaultRuntime.log(rich
|
|
255
|
+
? theme.success(`Revoked ${removed} capability(s) from agent "${agentId}"`)
|
|
256
|
+
: `Revoked ${removed} capability(s) from agent "${agentId}"`);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
capabilities
|
|
260
|
+
.command("list")
|
|
261
|
+
.description("List agent capabilities")
|
|
262
|
+
.option("-a, --agent <pattern>", "Filter by agent ID pattern")
|
|
263
|
+
.option("--json", "Print JSON output")
|
|
264
|
+
.action(async (opts) => {
|
|
265
|
+
const rich = isRich();
|
|
266
|
+
const cfg = loadConfig();
|
|
267
|
+
const agents = cfg.security?.agents || [];
|
|
268
|
+
const filtered = opts.agent
|
|
269
|
+
? agents.filter((a) => a.agentId === opts.agent || (opts.agent && a.agentId.includes(opts.agent)))
|
|
270
|
+
: agents;
|
|
271
|
+
if (opts.json) {
|
|
272
|
+
defaultRuntime.log(JSON.stringify({ enabled: cfg.security?.enabled ?? false, agents: filtered }, null, 2));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (filtered.length === 0) {
|
|
276
|
+
defaultRuntime.log(rich ? theme.muted("No capabilities configured") : "No capabilities configured");
|
|
277
|
+
defaultRuntime.log(`\nEnable capability system: ${formatCliCommand("poolbot config set security.enabled true")}`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const lines = [];
|
|
281
|
+
lines.push(rich ? theme.heading("Agent Capabilities") : "Agent Capabilities");
|
|
282
|
+
lines.push(rich
|
|
283
|
+
? theme.muted(`Security enabled: ${cfg.security?.enabled ?? false}`)
|
|
284
|
+
: `Security enabled: ${cfg.security?.enabled ?? false}`);
|
|
285
|
+
lines.push("");
|
|
286
|
+
for (const agent of filtered) {
|
|
287
|
+
lines.push(rich ? theme.heading(agent.agentId) : agent.agentId);
|
|
288
|
+
for (const cap of agent.capabilities) {
|
|
289
|
+
lines.push(` ${formatCapability(cap)}`);
|
|
290
|
+
}
|
|
291
|
+
lines.push("");
|
|
292
|
+
}
|
|
293
|
+
defaultRuntime.log(lines.join("\n"));
|
|
294
|
+
});
|
|
295
|
+
capabilities
|
|
296
|
+
.command("enable")
|
|
297
|
+
.description("Enable the capability security system")
|
|
298
|
+
.option("--json", "Print JSON output")
|
|
299
|
+
.action(async (opts) => {
|
|
300
|
+
const cfg = loadConfig();
|
|
301
|
+
cfg.security = cfg.security || {};
|
|
302
|
+
cfg.security.enabled = true;
|
|
303
|
+
await writeConfigFile(cfg);
|
|
304
|
+
if (opts.json) {
|
|
305
|
+
defaultRuntime.log(JSON.stringify({ success: true, enabled: true }));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
defaultRuntime.log(isRich()
|
|
309
|
+
? theme.success("Capability security system enabled")
|
|
310
|
+
: "Capability security system enabled");
|
|
311
|
+
defaultRuntime.log(`Run ${formatCliCommand("poolbot security capabilities list")} to view current grants`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
capabilities
|
|
315
|
+
.command("disable")
|
|
316
|
+
.description("Disable the capability security system (permissive mode)")
|
|
317
|
+
.option("--json", "Print JSON output")
|
|
318
|
+
.action(async (opts) => {
|
|
319
|
+
const cfg = loadConfig();
|
|
320
|
+
cfg.security = cfg.security || {};
|
|
321
|
+
cfg.security.enabled = false;
|
|
322
|
+
await writeConfigFile(cfg);
|
|
323
|
+
if (opts.json) {
|
|
324
|
+
defaultRuntime.log(JSON.stringify({ success: true, enabled: false }));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
defaultRuntime.log(isRich()
|
|
328
|
+
? theme.warn("Capability security system disabled (permissive mode)")
|
|
329
|
+
: "Capability security system disabled (permissive mode)");
|
|
330
|
+
}
|
|
331
|
+
});
|
|
123
332
|
}
|
package/dist/cli/tagline.js
CHANGED
|
@@ -191,6 +191,13 @@ export function activeTaglines(options = {}) {
|
|
|
191
191
|
return filtered.length > 0 ? filtered : TAGLINES;
|
|
192
192
|
}
|
|
193
193
|
export function pickTagline(options = {}) {
|
|
194
|
+
const mode = options.mode;
|
|
195
|
+
if (mode === "off") {
|
|
196
|
+
return "";
|
|
197
|
+
}
|
|
198
|
+
if (mode === "default") {
|
|
199
|
+
return DEFAULT_TAGLINE;
|
|
200
|
+
}
|
|
194
201
|
const env = options.env ?? process.env;
|
|
195
202
|
const override = env?.POOLBOT_TAGLINE_INDEX ?? env?.CLAWDBOT_TAGLINE_INDEX;
|
|
196
203
|
if (override !== undefined) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Default permissive capabilities (when security is disabled). */
|
|
2
|
+
export const PERMISSIVE_CAPABILITIES = [
|
|
3
|
+
{ type: "file:read", pattern: "*" },
|
|
4
|
+
{ type: "file:write", pattern: "*" },
|
|
5
|
+
{ type: "net:connect", pattern: "*" },
|
|
6
|
+
{ type: "tool:all" },
|
|
7
|
+
{ type: "llm:query", pattern: "*" },
|
|
8
|
+
{ type: "llm:maxTokens", limit: Number.MAX_SAFE_INTEGER },
|
|
9
|
+
{ type: "agent:spawn" },
|
|
10
|
+
{ type: "agent:message", pattern: "*" },
|
|
11
|
+
{ type: "agent:kill", pattern: "*" },
|
|
12
|
+
{ type: "memory:read", scope: "*" },
|
|
13
|
+
{ type: "memory:write", scope: "*" },
|
|
14
|
+
{ type: "shell:exec", pattern: "*" },
|
|
15
|
+
{ type: "env:read", pattern: "*" },
|
|
16
|
+
{ type: "gateway:admin" },
|
|
17
|
+
{ type: "gateway:channels:read" },
|
|
18
|
+
{ type: "gateway:channels:write", pattern: "*" },
|
|
19
|
+
{ type: "econ:spend", limit: Number.MAX_SAFE_INTEGER },
|
|
20
|
+
{ type: "econ:earn" },
|
|
21
|
+
{ type: "econ:transfer", pattern: "*" },
|
|
22
|
+
];
|
|
23
|
+
/** Restricted capabilities for untrusted agents. */
|
|
24
|
+
export const RESTRICTED_CAPABILITIES = [
|
|
25
|
+
{ type: "file:read", pattern: "/data/*" },
|
|
26
|
+
{ type: "net:connect", pattern: "*.openai.com:443" },
|
|
27
|
+
{ type: "tool:invoke", toolId: "web_search" },
|
|
28
|
+
{ type: "tool:invoke", toolId: "file_read" },
|
|
29
|
+
{ type: "llm:query", pattern: "gpt-4*" },
|
|
30
|
+
{ type: "llm:maxTokens", limit: 10000 },
|
|
31
|
+
{ type: "memory:read", scope: "session/*" },
|
|
32
|
+
{ type: "memory:write", scope: "session/*" },
|
|
33
|
+
];
|
|
@@ -6,6 +6,7 @@ import { HexColorSchema, ModelsConfigSchema } from "./zod-schema.core.js";
|
|
|
6
6
|
import { HookMappingSchema, HooksGmailSchema, InternalHooksSchema } from "./zod-schema.hooks.js";
|
|
7
7
|
import { InstallRecordShape } from "./zod-schema.installs.js";
|
|
8
8
|
import { ChannelsSchema } from "./zod-schema.providers.js";
|
|
9
|
+
import { SecuritySchema } from "./zod-schema.security.js";
|
|
9
10
|
import { sensitive } from "./zod-schema.sensitive.js";
|
|
10
11
|
import { CommandsSchema, MessagesSchema, SessionSchema, SessionSendPolicySchema, } from "./zod-schema.session.js";
|
|
11
12
|
const BrowserSnapshotDefaultsSchema = z
|
|
@@ -641,6 +642,20 @@ export const PoolBotSchema = z
|
|
|
641
642
|
})
|
|
642
643
|
.strict()
|
|
643
644
|
.optional(),
|
|
645
|
+
cli: z
|
|
646
|
+
.object({
|
|
647
|
+
banner: z
|
|
648
|
+
.object({
|
|
649
|
+
taglineMode: z
|
|
650
|
+
.union([z.literal("random"), z.literal("default"), z.literal("off")])
|
|
651
|
+
.optional(),
|
|
652
|
+
})
|
|
653
|
+
.strict()
|
|
654
|
+
.optional(),
|
|
655
|
+
})
|
|
656
|
+
.strict()
|
|
657
|
+
.optional(),
|
|
658
|
+
security: SecuritySchema,
|
|
644
659
|
})
|
|
645
660
|
.strict()
|
|
646
661
|
.superRefine((cfg, ctx) => {
|
|
@@ -583,6 +583,7 @@ export const SlackAccountSchema = z
|
|
|
583
583
|
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
584
584
|
responsePrefix: z.string().optional(),
|
|
585
585
|
ackReaction: z.string().optional(),
|
|
586
|
+
typingReaction: z.string().optional(),
|
|
586
587
|
})
|
|
587
588
|
.strict()
|
|
588
589
|
.superRefine((value, ctx) => {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for security configuration.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/** Capability type schema - matches the TypeScript Capability type. */
|
|
6
|
+
const CapabilitySchema = z.union([
|
|
7
|
+
// File capabilities
|
|
8
|
+
z.object({
|
|
9
|
+
type: z.literal("file:read"),
|
|
10
|
+
pattern: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
z.object({
|
|
13
|
+
type: z.literal("file:write"),
|
|
14
|
+
pattern: z.string(),
|
|
15
|
+
}),
|
|
16
|
+
// Network capabilities
|
|
17
|
+
z.object({
|
|
18
|
+
type: z.literal("net:connect"),
|
|
19
|
+
pattern: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
// Tool capabilities
|
|
22
|
+
z.object({
|
|
23
|
+
type: z.literal("tool:invoke"),
|
|
24
|
+
toolId: z.string(),
|
|
25
|
+
}),
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.literal("tool:all"),
|
|
28
|
+
}),
|
|
29
|
+
// LLM capabilities
|
|
30
|
+
z.object({
|
|
31
|
+
type: z.literal("llm:query"),
|
|
32
|
+
pattern: z.string(),
|
|
33
|
+
}),
|
|
34
|
+
z.object({
|
|
35
|
+
type: z.literal("llm:maxTokens"),
|
|
36
|
+
limit: z.number().int().positive(),
|
|
37
|
+
}),
|
|
38
|
+
// Agent capabilities
|
|
39
|
+
z.object({
|
|
40
|
+
type: z.literal("agent:spawn"),
|
|
41
|
+
}),
|
|
42
|
+
z.object({
|
|
43
|
+
type: z.literal("agent:message"),
|
|
44
|
+
pattern: z.string(),
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
type: z.literal("agent:kill"),
|
|
48
|
+
pattern: z.string(),
|
|
49
|
+
}),
|
|
50
|
+
// Memory capabilities
|
|
51
|
+
z.object({
|
|
52
|
+
type: z.literal("memory:read"),
|
|
53
|
+
scope: z.string(),
|
|
54
|
+
}),
|
|
55
|
+
z.object({
|
|
56
|
+
type: z.literal("memory:write"),
|
|
57
|
+
scope: z.string(),
|
|
58
|
+
}),
|
|
59
|
+
// Shell capabilities
|
|
60
|
+
z.object({
|
|
61
|
+
type: z.literal("shell:exec"),
|
|
62
|
+
pattern: z.string(),
|
|
63
|
+
}),
|
|
64
|
+
// Environment capabilities
|
|
65
|
+
z.object({
|
|
66
|
+
type: z.literal("env:read"),
|
|
67
|
+
pattern: z.string(),
|
|
68
|
+
}),
|
|
69
|
+
// Gateway capabilities
|
|
70
|
+
z.object({
|
|
71
|
+
type: z.literal("gateway:admin"),
|
|
72
|
+
}),
|
|
73
|
+
z.object({
|
|
74
|
+
type: z.literal("gateway:channels:read"),
|
|
75
|
+
}),
|
|
76
|
+
z.object({
|
|
77
|
+
type: z.literal("gateway:channels:write"),
|
|
78
|
+
pattern: z.string(),
|
|
79
|
+
}),
|
|
80
|
+
// Economic capabilities
|
|
81
|
+
z.object({
|
|
82
|
+
type: z.literal("econ:spend"),
|
|
83
|
+
limit: z.number().int().nonnegative(),
|
|
84
|
+
}),
|
|
85
|
+
z.object({
|
|
86
|
+
type: z.literal("econ:earn"),
|
|
87
|
+
}),
|
|
88
|
+
z.object({
|
|
89
|
+
type: z.literal("econ:transfer"),
|
|
90
|
+
pattern: z.string(),
|
|
91
|
+
}),
|
|
92
|
+
]);
|
|
93
|
+
/** Agent capability configuration schema. */
|
|
94
|
+
const AgentCapabilityConfigSchema = z
|
|
95
|
+
.object({
|
|
96
|
+
agentId: z.string(),
|
|
97
|
+
capabilities: z.array(CapabilitySchema),
|
|
98
|
+
})
|
|
99
|
+
.strict();
|
|
100
|
+
/** Security configuration schema. */
|
|
101
|
+
export const SecuritySchema = z
|
|
102
|
+
.object({
|
|
103
|
+
/** Enable capability enforcement. Default: false (permissive mode). */
|
|
104
|
+
enabled: z.boolean().optional(),
|
|
105
|
+
/** Default capabilities for agents without explicit config. */
|
|
106
|
+
defaultCapabilities: z.array(CapabilitySchema).optional(),
|
|
107
|
+
/** Per-agent capability configurations. */
|
|
108
|
+
agents: z.array(AgentCapabilityConfigSchema).optional(),
|
|
109
|
+
/** Capabilities for the system/root agent. */
|
|
110
|
+
systemCapabilities: z.array(CapabilitySchema).optional(),
|
|
111
|
+
})
|
|
112
|
+
.strict()
|
|
113
|
+
.optional();
|
package/dist/cron/normalize.js
CHANGED
|
@@ -351,6 +351,9 @@ export function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
|
|
|
351
351
|
if (isRecord(base.delivery)) {
|
|
352
352
|
next.delivery = coerceDelivery(base.delivery);
|
|
353
353
|
}
|
|
354
|
+
if (isRecord(base.onFailure)) {
|
|
355
|
+
next.onFailure = coerceDelivery(base.onFailure);
|
|
356
|
+
}
|
|
354
357
|
if ("isolation" in next) {
|
|
355
358
|
delete next.isolation;
|
|
356
359
|
}
|
|
@@ -68,6 +68,22 @@ function assertDeliverySupport(job) {
|
|
|
68
68
|
throw new Error('cron channel delivery config is only supported for sessionTarget="isolated"');
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function assertFailureAlertSupport(job) {
|
|
72
|
+
if (!job.onFailure) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (job.onFailure.mode === "webhook") {
|
|
76
|
+
const target = normalizeHttpWebhookUrl(job.onFailure.to);
|
|
77
|
+
if (!target) {
|
|
78
|
+
throw new Error("cron onFailure webhook requires onFailure.to to be a valid http(s) URL");
|
|
79
|
+
}
|
|
80
|
+
job.onFailure.to = target;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (job.sessionTarget !== "isolated") {
|
|
84
|
+
throw new Error('cron onFailure announce config is only supported for sessionTarget="isolated"');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
71
87
|
export function findJobOrThrow(state, id) {
|
|
72
88
|
const job = state.store?.jobs.find((j) => j.id === id);
|
|
73
89
|
if (!job) {
|
|
@@ -285,12 +301,14 @@ export function createJob(state, input) {
|
|
|
285
301
|
wakeMode: input.wakeMode,
|
|
286
302
|
payload: input.payload,
|
|
287
303
|
delivery: input.delivery,
|
|
304
|
+
onFailure: input.onFailure,
|
|
288
305
|
state: {
|
|
289
306
|
...input.state,
|
|
290
307
|
},
|
|
291
308
|
};
|
|
292
309
|
assertSupportedJobSpec(job);
|
|
293
310
|
assertDeliverySupport(job);
|
|
311
|
+
assertFailureAlertSupport(job);
|
|
294
312
|
job.state.nextRunAtMs = computeJobNextRunAtMs(job, now);
|
|
295
313
|
return job;
|
|
296
314
|
}
|
|
@@ -352,6 +370,12 @@ export function applyJobPatch(job, patch) {
|
|
|
352
370
|
if (job.sessionTarget === "main" && job.delivery?.mode !== "webhook") {
|
|
353
371
|
job.delivery = undefined;
|
|
354
372
|
}
|
|
373
|
+
if (patch.onFailure) {
|
|
374
|
+
job.onFailure = mergeCronFailureAlert(job.onFailure, patch.onFailure);
|
|
375
|
+
}
|
|
376
|
+
if (job.sessionTarget === "main" && job.onFailure?.mode !== "webhook") {
|
|
377
|
+
job.onFailure = undefined;
|
|
378
|
+
}
|
|
355
379
|
if (patch.state) {
|
|
356
380
|
job.state = { ...job.state, ...patch.state };
|
|
357
381
|
}
|
|
@@ -363,6 +387,7 @@ export function applyJobPatch(job, patch) {
|
|
|
363
387
|
}
|
|
364
388
|
assertSupportedJobSpec(job);
|
|
365
389
|
assertDeliverySupport(job);
|
|
390
|
+
assertFailureAlertSupport(job);
|
|
366
391
|
}
|
|
367
392
|
function mergeCronPayload(existing, patch) {
|
|
368
393
|
if (patch.kind !== existing.kind) {
|
|
@@ -488,6 +513,29 @@ function mergeCronDelivery(existing, patch) {
|
|
|
488
513
|
}
|
|
489
514
|
return next;
|
|
490
515
|
}
|
|
516
|
+
function mergeCronFailureAlert(existing, patch) {
|
|
517
|
+
const next = {
|
|
518
|
+
mode: existing?.mode ?? "none",
|
|
519
|
+
channel: existing?.channel,
|
|
520
|
+
to: existing?.to,
|
|
521
|
+
bestEffort: existing?.bestEffort,
|
|
522
|
+
};
|
|
523
|
+
if (typeof patch.mode === "string") {
|
|
524
|
+
next.mode = patch.mode === "deliver" ? "announce" : patch.mode;
|
|
525
|
+
}
|
|
526
|
+
if ("channel" in patch) {
|
|
527
|
+
const channel = typeof patch.channel === "string" ? patch.channel.trim() : "";
|
|
528
|
+
next.channel = channel ? channel : undefined;
|
|
529
|
+
}
|
|
530
|
+
if ("to" in patch) {
|
|
531
|
+
const to = typeof patch.to === "string" ? patch.to.trim() : "";
|
|
532
|
+
next.to = to ? to : undefined;
|
|
533
|
+
}
|
|
534
|
+
if (typeof patch.bestEffort === "boolean") {
|
|
535
|
+
next.bestEffort = patch.bestEffort;
|
|
536
|
+
}
|
|
537
|
+
return next;
|
|
538
|
+
}
|
|
491
539
|
export function isJobDue(job, nowMs, opts) {
|
|
492
540
|
if (!job.state) {
|
|
493
541
|
job.state = {};
|
|
@@ -66,7 +66,8 @@ export async function preflightDiscordMessage(params) {
|
|
|
66
66
|
logVerbose(`discord: drop message ${message.id} (missing channel id)`);
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
|
-
const
|
|
69
|
+
const allowBotsSetting = params.discordConfig?.allowBots;
|
|
70
|
+
const allowBotsMode = allowBotsSetting === "mentions" ? "mentions" : allowBotsSetting === true ? "all" : "off";
|
|
70
71
|
if (params.botUserId && author.id === params.botUserId) {
|
|
71
72
|
// Always ignore own messages to prevent self-reply loops
|
|
72
73
|
return null;
|
|
@@ -92,7 +93,7 @@ export async function preflightDiscordMessage(params) {
|
|
|
92
93
|
pluralkitInfo,
|
|
93
94
|
});
|
|
94
95
|
if (author.bot) {
|
|
95
|
-
if (
|
|
96
|
+
if (allowBotsMode === "off" && !sender.isPluralKit) {
|
|
96
97
|
logVerbose("discord: drop bot message (allowBots=false)");
|
|
97
98
|
return null;
|
|
98
99
|
}
|
|
@@ -523,6 +524,14 @@ export async function preflightDiscordMessage(params) {
|
|
|
523
524
|
});
|
|
524
525
|
return null;
|
|
525
526
|
}
|
|
527
|
+
if (author.bot && !sender.isPluralKit && allowBotsMode === "mentions") {
|
|
528
|
+
const botMentioned = isDirectMessage || wasMentioned || implicitMention;
|
|
529
|
+
if (!botMentioned) {
|
|
530
|
+
logDebug(`[discord-preflight] drop: bot message missing mention (allowBots=mentions)`);
|
|
531
|
+
logVerbose("discord: drop bot message (allowBots=mentions, missing mention)");
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
526
535
|
if (!messageText) {
|
|
527
536
|
logDebug(`[discord-preflight] drop: empty content`);
|
|
528
537
|
logVerbose(`discord: drop message ${message.id} (empty content)`);
|
|
@@ -5,9 +5,14 @@ import { readJsonBody } from "./hooks.js";
|
|
|
5
5
|
* Content-Security-Policy are intentionally omitted here because some handlers
|
|
6
6
|
* (canvas host, A2UI) serve content that may be loaded inside frames.
|
|
7
7
|
*/
|
|
8
|
-
export function setDefaultSecurityHeaders(res) {
|
|
8
|
+
export function setDefaultSecurityHeaders(res, opts) {
|
|
9
9
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
10
10
|
res.setHeader("Referrer-Policy", "no-referrer");
|
|
11
|
+
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
|
|
12
|
+
const strictTransportSecurity = opts?.strictTransportSecurity;
|
|
13
|
+
if (typeof strictTransportSecurity === "string" && strictTransportSecurity.length > 0) {
|
|
14
|
+
res.setHeader("Strict-Transport-Security", strictTransportSecurity);
|
|
15
|
+
}
|
|
11
16
|
}
|
|
12
17
|
export function sendJson(res, status, body) {
|
|
13
18
|
res.statusCode = status;
|
|
@@ -99,6 +99,7 @@ export const CronJobSchema = Type.Object({
|
|
|
99
99
|
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
|
|
100
100
|
payload: CronPayloadSchema,
|
|
101
101
|
delivery: Type.Optional(CronDeliverySchema),
|
|
102
|
+
onFailure: Type.Optional(CronDeliverySchema),
|
|
102
103
|
state: CronJobStateSchema,
|
|
103
104
|
}, { additionalProperties: false });
|
|
104
105
|
export const CronListParamsSchema = Type.Object({
|
|
@@ -117,6 +118,7 @@ export const CronAddParamsSchema = Type.Object({
|
|
|
117
118
|
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
|
|
118
119
|
payload: CronPayloadSchema,
|
|
119
120
|
delivery: Type.Optional(CronDeliverySchema),
|
|
121
|
+
onFailure: Type.Optional(CronDeliverySchema),
|
|
120
122
|
}, { additionalProperties: false });
|
|
121
123
|
export const CronJobPatchSchema = Type.Object({
|
|
122
124
|
name: Type.Optional(NonEmptyString),
|
|
@@ -130,6 +132,7 @@ export const CronJobPatchSchema = Type.Object({
|
|
|
130
132
|
wakeMode: Type.Optional(Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")])),
|
|
131
133
|
payload: Type.Optional(CronPayloadPatchSchema),
|
|
132
134
|
delivery: Type.Optional(CronDeliveryPatchSchema),
|
|
135
|
+
onFailure: Type.Optional(CronDeliveryPatchSchema),
|
|
133
136
|
state: Type.Optional(Type.Partial(CronJobStateSchema)),
|
|
134
137
|
}, { additionalProperties: false });
|
|
135
138
|
export const CronUpdateParamsSchema = Type.Union([
|