@poolzin/pool-bot 2026.3.6 → 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/dist/agents/pi-tools.js +32 -2
- 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/discord/monitor/message-handler.preflight.js +11 -2
- package/dist/gateway/http-common.js +6 -1
- 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/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/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/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { getCapabilityManager } from "./capability-manager.js";
|
|
2
|
+
import { CapabilityError } from "./capability-guards.js";
|
|
3
|
+
import { logVerbose } from "../globals.js";
|
|
4
|
+
/**
|
|
5
|
+
* Creates a middleware that checks tool invocation capabilities.
|
|
6
|
+
*/
|
|
7
|
+
export function createCapabilityMiddleware() {
|
|
8
|
+
return async (ctx, toolId, _args, next) => {
|
|
9
|
+
const manager = getCapabilityManager();
|
|
10
|
+
// Check for tool:all first (grants any tool)
|
|
11
|
+
const allCheck = manager.check(ctx.agentId, { type: "tool:all" });
|
|
12
|
+
if (allCheck.granted) {
|
|
13
|
+
logVerbose(`[capability] ${ctx.agentId} granted tool:all for ${toolId}`);
|
|
14
|
+
return await next();
|
|
15
|
+
}
|
|
16
|
+
// Check for specific tool invocation
|
|
17
|
+
const toolCheck = manager.check(ctx.agentId, {
|
|
18
|
+
type: "tool:invoke",
|
|
19
|
+
toolId,
|
|
20
|
+
});
|
|
21
|
+
if (!toolCheck.granted) {
|
|
22
|
+
logVerbose(`[capability] ${ctx.agentId} denied access to tool ${toolId}: ${toolCheck.reason}`);
|
|
23
|
+
throw new CapabilityError(`Tool '${toolId}' access denied: ${toolCheck.reason}`, ctx.agentId, { type: "tool:invoke", toolId });
|
|
24
|
+
}
|
|
25
|
+
logVerbose(`[capability] ${ctx.agentId} granted access to ${toolId}`);
|
|
26
|
+
return await next();
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates a middleware that checks file access capabilities.
|
|
31
|
+
*/
|
|
32
|
+
export function createFileAccessMiddleware() {
|
|
33
|
+
return async (ctx, toolId, args, next) => {
|
|
34
|
+
// Tools that read files
|
|
35
|
+
if (toolId === "file_read" || toolId === "read_file") {
|
|
36
|
+
const path = args.path || args.file_path || args.filePath;
|
|
37
|
+
if (typeof path === "string") {
|
|
38
|
+
const manager = getCapabilityManager();
|
|
39
|
+
const check = manager.check(ctx.agentId, {
|
|
40
|
+
type: "file:read",
|
|
41
|
+
pattern: path,
|
|
42
|
+
});
|
|
43
|
+
if (!check.granted) {
|
|
44
|
+
throw new CapabilityError(`File read denied for '${path}': ${check.reason}`, ctx.agentId, { type: "file:read", pattern: path });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Tools that write files
|
|
49
|
+
if (toolId === "file_write" || toolId === "write_file") {
|
|
50
|
+
const path = args.path || args.file_path || args.filePath;
|
|
51
|
+
if (typeof path === "string") {
|
|
52
|
+
const manager = getCapabilityManager();
|
|
53
|
+
const check = manager.check(ctx.agentId, {
|
|
54
|
+
type: "file:write",
|
|
55
|
+
pattern: path,
|
|
56
|
+
});
|
|
57
|
+
if (!check.granted) {
|
|
58
|
+
throw new CapabilityError(`File write denied for '${path}': ${check.reason}`, ctx.agentId, { type: "file:write", pattern: path });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return await next();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates a middleware that checks shell execution capabilities.
|
|
67
|
+
*/
|
|
68
|
+
export function createShellExecutionMiddleware() {
|
|
69
|
+
return async (ctx, toolId, args, next) => {
|
|
70
|
+
if (toolId === "shell" || toolId === "bash" || toolId === "exec") {
|
|
71
|
+
const command = args.command || args.cmd || args.shell;
|
|
72
|
+
if (typeof command === "string") {
|
|
73
|
+
const manager = getCapabilityManager();
|
|
74
|
+
const check = manager.check(ctx.agentId, {
|
|
75
|
+
type: "shell:exec",
|
|
76
|
+
pattern: command,
|
|
77
|
+
});
|
|
78
|
+
if (!check.granted) {
|
|
79
|
+
throw new CapabilityError(`Shell execution denied for '${command}': ${check.reason}`, ctx.agentId, { type: "shell:exec", pattern: command });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return await next();
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Composes multiple middlewares into a single middleware.
|
|
88
|
+
*/
|
|
89
|
+
export function composeMiddlewares(...middlewares) {
|
|
90
|
+
return async (ctx, toolId, args, finalNext) => {
|
|
91
|
+
let index = 0;
|
|
92
|
+
const dispatch = async () => {
|
|
93
|
+
if (index >= middlewares.length) {
|
|
94
|
+
return await finalNext();
|
|
95
|
+
}
|
|
96
|
+
const middleware = middlewares[index++];
|
|
97
|
+
return await middleware(ctx, toolId, args, dispatch);
|
|
98
|
+
};
|
|
99
|
+
return await dispatch();
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Default security middleware stack. */
|
|
103
|
+
export function createDefaultSecurityMiddleware() {
|
|
104
|
+
return composeMiddlewares(createCapabilityMiddleware(), createFileAccessMiddleware(), createShellExecutionMiddleware());
|
|
105
|
+
}
|
|
@@ -228,6 +228,7 @@ export function createSlackMonitorContext(params) {
|
|
|
228
228
|
slashCommand: params.slashCommand,
|
|
229
229
|
textLimit: params.textLimit,
|
|
230
230
|
ackReactionScope: params.ackReactionScope,
|
|
231
|
+
typingReaction: params.typingReaction,
|
|
231
232
|
mediaMaxBytes: params.mediaMaxBytes,
|
|
232
233
|
removeAckAfterReply: params.removeAckAfterReply,
|
|
233
234
|
logger,
|
|
@@ -8,7 +8,7 @@ import { createReplyPrefixOptions } from "../../../channels/reply-prefix.js";
|
|
|
8
8
|
import { createTypingCallbacks } from "../../../channels/typing.js";
|
|
9
9
|
import { resolveStorePath, updateLastRoute } from "../../../config/sessions.js";
|
|
10
10
|
import { danger, logVerbose, shouldLogVerbose } from "../../../globals.js";
|
|
11
|
-
import { removeSlackReaction } from "../../actions.js";
|
|
11
|
+
import { reactSlackMessage, removeSlackReaction } from "../../actions.js";
|
|
12
12
|
import { createSlackDraftStream } from "../../draft-stream.js";
|
|
13
13
|
import { applyAppendOnlyStreamUpdate, buildStatusFinalPreviewText, resolveSlackStreamingConfig, } from "../../stream-mode.js";
|
|
14
14
|
import { appendSlackStream, startSlackStream, stopSlackStream } from "../../streaming.js";
|
|
@@ -78,6 +78,7 @@ export async function dispatchPreparedSlackMessage(prepared) {
|
|
|
78
78
|
hasRepliedRef,
|
|
79
79
|
});
|
|
80
80
|
const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
|
|
81
|
+
const typingReaction = ctx.typingReaction;
|
|
81
82
|
const typingCallbacks = createTypingCallbacks({
|
|
82
83
|
start: async () => {
|
|
83
84
|
didSetStatus = true;
|
|
@@ -86,6 +87,12 @@ export async function dispatchPreparedSlackMessage(prepared) {
|
|
|
86
87
|
threadTs: statusThreadTs,
|
|
87
88
|
status: "is typing...",
|
|
88
89
|
});
|
|
90
|
+
if (typingReaction && message.ts) {
|
|
91
|
+
await reactSlackMessage(message.channel, message.ts, typingReaction, {
|
|
92
|
+
token: ctx.botToken,
|
|
93
|
+
client: ctx.app.client,
|
|
94
|
+
}).catch(() => { });
|
|
95
|
+
}
|
|
89
96
|
},
|
|
90
97
|
stop: async () => {
|
|
91
98
|
if (!didSetStatus) {
|
|
@@ -97,6 +104,12 @@ export async function dispatchPreparedSlackMessage(prepared) {
|
|
|
97
104
|
threadTs: statusThreadTs,
|
|
98
105
|
status: "",
|
|
99
106
|
});
|
|
107
|
+
if (typingReaction && message.ts) {
|
|
108
|
+
await removeSlackReaction(message.channel, message.ts, typingReaction, {
|
|
109
|
+
token: ctx.botToken,
|
|
110
|
+
client: ctx.app.client,
|
|
111
|
+
}).catch(() => { });
|
|
112
|
+
}
|
|
100
113
|
},
|
|
101
114
|
onStartError: (err) => {
|
|
102
115
|
logTypingFailure({
|
|
@@ -90,6 +90,7 @@ export async function monitorSlackProvider(opts = {}) {
|
|
|
90
90
|
const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions";
|
|
91
91
|
const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
|
|
92
92
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
|
93
|
+
const typingReaction = slackCfg.typingReaction ?? "";
|
|
93
94
|
const receiver = slackMode === "http"
|
|
94
95
|
? new HTTPReceiver({
|
|
95
96
|
signingSecret: signingSecret ?? "",
|
|
@@ -160,6 +161,7 @@ export async function monitorSlackProvider(opts = {}) {
|
|
|
160
161
|
slashCommand,
|
|
161
162
|
textLimit,
|
|
162
163
|
ackReactionScope,
|
|
164
|
+
typingReaction,
|
|
163
165
|
mediaMaxBytes,
|
|
164
166
|
removeAckAfterReply,
|
|
165
167
|
});
|