@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.
@@ -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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolzin/pool-bot",
3
- "version": "2026.3.6",
3
+ "version": "2026.3.7",
4
4
  "description": "🎱 Pool Bot - AI assistant with PLCODE integrations",
5
5
  "keywords": [],
6
6
  "license": "MIT",