@poolzin/pool-bot 2026.2.23 → 2026.2.25
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 +29 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/onboard-helpers.js +4 -4
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/compat/legacy-names.js +2 -2
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
- package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
- package/dist/control-ui/index.html +1 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function makeAttemptResult(overrides = {}) {
|
|
2
|
+
return {
|
|
3
|
+
aborted: false,
|
|
4
|
+
timedOut: false,
|
|
5
|
+
timedOutDuringCompaction: false,
|
|
6
|
+
promptError: null,
|
|
7
|
+
sessionIdUsed: "test-session",
|
|
8
|
+
assistantTexts: ["Hello!"],
|
|
9
|
+
toolMetas: [],
|
|
10
|
+
lastAssistant: undefined,
|
|
11
|
+
messagesSnapshot: [],
|
|
12
|
+
didSendViaMessagingTool: false,
|
|
13
|
+
messagingToolSentTexts: [],
|
|
14
|
+
messagingToolSentMediaUrls: [],
|
|
15
|
+
messagingToolSentTargets: [],
|
|
16
|
+
cloudCodeAssistFormatError: false,
|
|
17
|
+
...overrides,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function mockOverflowRetrySuccess(params) {
|
|
21
|
+
const overflowError = new Error(params.overflowMessage ?? "request_too_large: Request size exceeds model context window");
|
|
22
|
+
params.runEmbeddedAttempt.mockResolvedValueOnce(makeAttemptResult({ promptError: overflowError }));
|
|
23
|
+
params.runEmbeddedAttempt.mockResolvedValueOnce(makeAttemptResult({ promptError: null }));
|
|
24
|
+
params.compactDirect.mockResolvedValueOnce({
|
|
25
|
+
ok: true,
|
|
26
|
+
compacted: true,
|
|
27
|
+
result: {
|
|
28
|
+
summary: "Compacted session",
|
|
29
|
+
firstKeptEntryId: "entry-5",
|
|
30
|
+
tokensBefore: 150000,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
return overflowError;
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function writeSkill(params) {
|
|
4
|
+
const { dir, name, description, body } = params;
|
|
5
|
+
await fs.mkdir(dir, { recursive: true });
|
|
6
|
+
await fs.writeFile(path.join(dir, "SKILL.md"), `---
|
|
7
|
+
name: ${name}
|
|
8
|
+
description: ${description}
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
${body ?? `# ${name}\n`}
|
|
12
|
+
`, "utf-8");
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function stableStringify(value) {
|
|
2
|
+
if (value === null || typeof value !== "object") {
|
|
3
|
+
return JSON.stringify(value) ?? "null";
|
|
4
|
+
}
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
7
|
+
}
|
|
8
|
+
const record = value;
|
|
9
|
+
const keys = Object.keys(record).toSorted();
|
|
10
|
+
const entries = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
|
|
11
|
+
return `{${entries.join(",")}}`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
const noop = () => { };
|
|
3
|
+
vi.mock("../gateway/call.js", () => ({
|
|
4
|
+
callGateway: vi.fn(async () => ({
|
|
5
|
+
status: "ok",
|
|
6
|
+
startedAt: 111,
|
|
7
|
+
endedAt: 222,
|
|
8
|
+
})),
|
|
9
|
+
}));
|
|
10
|
+
vi.mock("../infra/agent-events.js", () => ({
|
|
11
|
+
onAgentEvent: vi.fn(() => noop),
|
|
12
|
+
}));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const ZERO_USAGE = {
|
|
2
|
+
input: 0,
|
|
3
|
+
output: 0,
|
|
4
|
+
cacheRead: 0,
|
|
5
|
+
cacheWrite: 0,
|
|
6
|
+
totalTokens: 0,
|
|
7
|
+
cost: {
|
|
8
|
+
input: 0,
|
|
9
|
+
output: 0,
|
|
10
|
+
cacheRead: 0,
|
|
11
|
+
cacheWrite: 0,
|
|
12
|
+
total: 0,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export function makeAssistantMessageFixture(overrides = {}) {
|
|
16
|
+
const errorText = typeof overrides.errorMessage === "string" ? overrides.errorMessage : "error";
|
|
17
|
+
return {
|
|
18
|
+
role: "assistant",
|
|
19
|
+
api: "openai-responses",
|
|
20
|
+
provider: "openai",
|
|
21
|
+
model: "test-model",
|
|
22
|
+
usage: ZERO_USAGE,
|
|
23
|
+
timestamp: 0,
|
|
24
|
+
stopReason: "error",
|
|
25
|
+
errorMessage: errorText,
|
|
26
|
+
content: [{ type: "text", text: errorText }],
|
|
27
|
+
...overrides,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function createPiToolsSandboxContext(params) {
|
|
2
|
+
const workspaceDir = params.workspaceDir;
|
|
3
|
+
return {
|
|
4
|
+
enabled: true,
|
|
5
|
+
sessionKey: params.sessionKey ?? "sandbox:test",
|
|
6
|
+
workspaceDir,
|
|
7
|
+
agentWorkspaceDir: params.agentWorkspaceDir ?? workspaceDir,
|
|
8
|
+
workspaceAccess: params.workspaceAccess ?? "rw",
|
|
9
|
+
containerName: params.containerName ?? "poolbot-sbx-test",
|
|
10
|
+
containerWorkdir: params.containerWorkdir ?? "/workspace",
|
|
11
|
+
fsBridge: params.fsBridge,
|
|
12
|
+
docker: {
|
|
13
|
+
image: "poolbot-sandbox:bookworm-slim",
|
|
14
|
+
containerPrefix: "poolbot-sbx-",
|
|
15
|
+
workdir: "/workspace",
|
|
16
|
+
readOnlyRoot: true,
|
|
17
|
+
tmpfs: [],
|
|
18
|
+
network: "none",
|
|
19
|
+
user: "1000:1000",
|
|
20
|
+
capDrop: ["ALL"],
|
|
21
|
+
env: { LANG: "C.UTF-8" },
|
|
22
|
+
...params.dockerOverrides,
|
|
23
|
+
},
|
|
24
|
+
tools: params.tools ?? { allow: [], deny: [] },
|
|
25
|
+
browserAllowHostControl: params.browserAllowHostControl ?? false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const TOOL_NAME_ALIASES = {
|
|
2
|
+
bash: "exec",
|
|
3
|
+
"apply-patch": "apply_patch",
|
|
4
|
+
};
|
|
5
|
+
export const TOOL_GROUPS = {
|
|
6
|
+
// NOTE: Keep canonical (lowercase) tool names here.
|
|
7
|
+
"group:memory": ["memory_search", "memory_get"],
|
|
8
|
+
"group:web": ["web_search", "web_fetch"],
|
|
9
|
+
// Basic workspace/file tools
|
|
10
|
+
"group:fs": ["read", "write", "edit", "apply_patch"],
|
|
11
|
+
// Host/runtime execution tools
|
|
12
|
+
"group:runtime": ["exec", "process"],
|
|
13
|
+
// Session management tools
|
|
14
|
+
"group:sessions": [
|
|
15
|
+
"sessions_list",
|
|
16
|
+
"sessions_history",
|
|
17
|
+
"sessions_send",
|
|
18
|
+
"sessions_spawn",
|
|
19
|
+
"subagents",
|
|
20
|
+
"session_status",
|
|
21
|
+
],
|
|
22
|
+
// UI helpers
|
|
23
|
+
"group:ui": ["browser", "canvas"],
|
|
24
|
+
// Automation + infra
|
|
25
|
+
"group:automation": ["cron", "gateway"],
|
|
26
|
+
// Messaging surface
|
|
27
|
+
"group:messaging": ["message"],
|
|
28
|
+
// Nodes + device tools
|
|
29
|
+
"group:nodes": ["nodes"],
|
|
30
|
+
// All Pool Bot native tools (excludes provider plugins).
|
|
31
|
+
"group:poolbot": [
|
|
32
|
+
"browser",
|
|
33
|
+
"canvas",
|
|
34
|
+
"nodes",
|
|
35
|
+
"cron",
|
|
36
|
+
"message",
|
|
37
|
+
"gateway",
|
|
38
|
+
"agents_list",
|
|
39
|
+
"sessions_list",
|
|
40
|
+
"sessions_history",
|
|
41
|
+
"sessions_send",
|
|
42
|
+
"sessions_spawn",
|
|
43
|
+
"subagents",
|
|
44
|
+
"session_status",
|
|
45
|
+
"memory_search",
|
|
46
|
+
"memory_get",
|
|
47
|
+
"web_search",
|
|
48
|
+
"web_fetch",
|
|
49
|
+
"image",
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
const TOOL_PROFILES = {
|
|
53
|
+
minimal: {
|
|
54
|
+
allow: ["session_status"],
|
|
55
|
+
},
|
|
56
|
+
coding: {
|
|
57
|
+
allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image"],
|
|
58
|
+
},
|
|
59
|
+
messaging: {
|
|
60
|
+
allow: [
|
|
61
|
+
"group:messaging",
|
|
62
|
+
"sessions_list",
|
|
63
|
+
"sessions_history",
|
|
64
|
+
"sessions_send",
|
|
65
|
+
"session_status",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
full: {},
|
|
69
|
+
};
|
|
70
|
+
export function normalizeToolName(name) {
|
|
71
|
+
const normalized = name.trim().toLowerCase();
|
|
72
|
+
return TOOL_NAME_ALIASES[normalized] ?? normalized;
|
|
73
|
+
}
|
|
74
|
+
export function normalizeToolList(list) {
|
|
75
|
+
if (!list) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
return list.map(normalizeToolName).filter(Boolean);
|
|
79
|
+
}
|
|
80
|
+
export function expandToolGroups(list) {
|
|
81
|
+
const normalized = normalizeToolList(list);
|
|
82
|
+
const expanded = [];
|
|
83
|
+
for (const value of normalized) {
|
|
84
|
+
const group = TOOL_GROUPS[value];
|
|
85
|
+
if (group) {
|
|
86
|
+
expanded.push(...group);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
expanded.push(value);
|
|
90
|
+
}
|
|
91
|
+
return Array.from(new Set(expanded));
|
|
92
|
+
}
|
|
93
|
+
export function resolveToolProfilePolicy(profile) {
|
|
94
|
+
if (!profile) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const resolved = TOOL_PROFILES[profile];
|
|
98
|
+
if (!resolved) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
if (!resolved.allow && !resolved.deny) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
allow: resolved.allow ? [...resolved.allow] : undefined,
|
|
106
|
+
deny: resolved.deny ? [...resolved.deny] : undefined,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -1,14 +1,35 @@
|
|
|
1
|
-
import { browserCloseTab, browserFocusTab, browserOpenTab, browserProfiles, browserSnapshot, browserStart, browserStatus, browserStop, browserTabs, } from "../../browser/client.js";
|
|
2
|
-
import { browserAct, browserArmDialog, browserArmFileChooser, browserConsoleMessages, browserNavigate, browserPdfSave, browserScreenshotAction, } from "../../browser/client-actions.js";
|
|
3
1
|
import crypto from "node:crypto";
|
|
2
|
+
import { browserAct, browserArmDialog, browserArmFileChooser, browserConsoleMessages, browserNavigate, browserPdfSave, browserScreenshotAction, } from "../../browser/client-actions.js";
|
|
3
|
+
import { browserCloseTab, browserFocusTab, browserOpenTab, browserProfiles, browserSnapshot, browserStart, browserStatus, browserStop, browserTabs, } from "../../browser/client.js";
|
|
4
4
|
import { resolveBrowserConfig } from "../../browser/config.js";
|
|
5
5
|
import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "../../browser/constants.js";
|
|
6
|
+
import { DEFAULT_UPLOAD_DIR, resolvePathsWithinRoot } from "../../browser/paths.js";
|
|
7
|
+
import { applyBrowserProxyPaths, persistBrowserProxyFiles } from "../../browser/proxy-files.js";
|
|
6
8
|
import { loadConfig } from "../../config/config.js";
|
|
7
|
-
import {
|
|
8
|
-
import { listNodes, resolveNodeIdFromList } from "./nodes-utils.js";
|
|
9
|
+
import { wrapExternalContent } from "../../security/external-content.js";
|
|
9
10
|
import { BrowserToolSchema } from "./browser-tool.schema.js";
|
|
10
11
|
import { imageResultFromFile, jsonResult, readStringParam } from "./common.js";
|
|
11
12
|
import { callGatewayTool } from "./gateway.js";
|
|
13
|
+
import { listNodes, resolveNodeIdFromList } from "./nodes-utils.js";
|
|
14
|
+
function wrapBrowserExternalJson(params) {
|
|
15
|
+
const extractedText = JSON.stringify(params.payload, null, 2);
|
|
16
|
+
const wrappedText = wrapExternalContent(extractedText, {
|
|
17
|
+
source: "browser",
|
|
18
|
+
includeWarning: params.includeWarning ?? true,
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
wrappedText,
|
|
22
|
+
safeDetails: {
|
|
23
|
+
ok: true,
|
|
24
|
+
externalContent: {
|
|
25
|
+
untrusted: true,
|
|
26
|
+
source: "browser",
|
|
27
|
+
kind: params.kind,
|
|
28
|
+
wrapped: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
12
33
|
const DEFAULT_BROWSER_PROXY_TIMEOUT_MS = 20_000;
|
|
13
34
|
function isBrowserNode(node) {
|
|
14
35
|
const caps = Array.isArray(node.caps) ? node.caps : [];
|
|
@@ -28,8 +49,9 @@ async function resolveBrowserNodeTarget(params) {
|
|
|
28
49
|
if (params.sandboxBridgeUrl?.trim() && params.target !== "node" && !params.requestedNode) {
|
|
29
50
|
return null;
|
|
30
51
|
}
|
|
31
|
-
if (params.target && params.target !== "node")
|
|
52
|
+
if (params.target && params.target !== "node") {
|
|
32
53
|
return null;
|
|
54
|
+
}
|
|
33
55
|
if (mode === "manual" && params.target !== "node" && !params.requestedNode) {
|
|
34
56
|
return null;
|
|
35
57
|
}
|
|
@@ -54,8 +76,9 @@ async function resolveBrowserNodeTarget(params) {
|
|
|
54
76
|
}
|
|
55
77
|
throw new Error(`Multiple browser-capable nodes connected (${browserNodes.length}). Set gateway.nodes.browser.node or pass node=<id>.`);
|
|
56
78
|
}
|
|
57
|
-
if (mode === "manual")
|
|
79
|
+
if (mode === "manual") {
|
|
58
80
|
return null;
|
|
81
|
+
}
|
|
59
82
|
if (browserNodes.length === 1) {
|
|
60
83
|
const node = browserNodes[0];
|
|
61
84
|
return { nodeId: node.nodeId, label: node.displayName ?? node.remoteIp ?? node.nodeId };
|
|
@@ -66,7 +89,7 @@ async function callBrowserProxy(params) {
|
|
|
66
89
|
const gatewayTimeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
|
|
67
90
|
? Math.max(1, Math.floor(params.timeoutMs))
|
|
68
91
|
: DEFAULT_BROWSER_PROXY_TIMEOUT_MS;
|
|
69
|
-
const payload =
|
|
92
|
+
const payload = await callGatewayTool("node.invoke", { timeoutMs: gatewayTimeoutMs }, {
|
|
70
93
|
nodeId: params.nodeId,
|
|
71
94
|
command: "browser.proxy",
|
|
72
95
|
params: {
|
|
@@ -78,44 +101,21 @@ async function callBrowserProxy(params) {
|
|
|
78
101
|
profile: params.profile,
|
|
79
102
|
},
|
|
80
103
|
idempotencyKey: crypto.randomUUID(),
|
|
81
|
-
})
|
|
104
|
+
});
|
|
82
105
|
const parsed = payload?.payload ??
|
|
83
106
|
(typeof payload?.payloadJSON === "string" && payload.payloadJSON
|
|
84
107
|
? JSON.parse(payload.payloadJSON)
|
|
85
108
|
: null);
|
|
86
|
-
if (!parsed || typeof parsed !== "object") {
|
|
109
|
+
if (!parsed || typeof parsed !== "object" || !("result" in parsed)) {
|
|
87
110
|
throw new Error("browser proxy failed");
|
|
88
111
|
}
|
|
89
112
|
return parsed;
|
|
90
113
|
}
|
|
91
114
|
async function persistProxyFiles(files) {
|
|
92
|
-
|
|
93
|
-
return new Map();
|
|
94
|
-
const mapping = new Map();
|
|
95
|
-
for (const file of files) {
|
|
96
|
-
const buffer = Buffer.from(file.base64, "base64");
|
|
97
|
-
const saved = await saveMediaBuffer(buffer, file.mimeType, "browser", buffer.byteLength);
|
|
98
|
-
mapping.set(file.path, saved.path);
|
|
99
|
-
}
|
|
100
|
-
return mapping;
|
|
115
|
+
return await persistBrowserProxyFiles(files);
|
|
101
116
|
}
|
|
102
117
|
function applyProxyPaths(result, mapping) {
|
|
103
|
-
|
|
104
|
-
return;
|
|
105
|
-
const obj = result;
|
|
106
|
-
if (typeof obj.path === "string" && mapping.has(obj.path)) {
|
|
107
|
-
obj.path = mapping.get(obj.path);
|
|
108
|
-
}
|
|
109
|
-
if (typeof obj.imagePath === "string" && mapping.has(obj.imagePath)) {
|
|
110
|
-
obj.imagePath = mapping.get(obj.imagePath);
|
|
111
|
-
}
|
|
112
|
-
const download = obj.download;
|
|
113
|
-
if (download && typeof download === "object") {
|
|
114
|
-
const d = download;
|
|
115
|
-
if (typeof d.path === "string" && mapping.has(d.path)) {
|
|
116
|
-
d.path = mapping.get(d.path);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
118
|
+
applyBrowserProxyPaths(result, mapping);
|
|
119
119
|
}
|
|
120
120
|
function resolveBrowserBaseUrl(params) {
|
|
121
121
|
const cfg = loadConfig();
|
|
@@ -143,11 +143,11 @@ export function createBrowserTool(opts) {
|
|
|
143
143
|
label: "Browser",
|
|
144
144
|
name: "browser",
|
|
145
145
|
description: [
|
|
146
|
-
"Control the browser via
|
|
146
|
+
"Control the browser via Pool Bot's browser control server (status/start/stop/profiles/tabs/open/snapshot/screenshot/actions).",
|
|
147
147
|
'Profiles: use profile="chrome" for Chrome extension relay takeover (your existing Chrome tabs). Use profile="clawd" for the isolated clawd-managed browser.',
|
|
148
|
-
'If the user mentions the Chrome extension / Browser Relay / toolbar button /
|
|
148
|
+
'If the user mentions the Chrome extension / Browser Relay / toolbar button / "attach tab", ALWAYS use profile="chrome" (do not ask which profile).',
|
|
149
149
|
'When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target="node".',
|
|
150
|
-
"Chrome extension relay needs an attached tab: user must click the
|
|
150
|
+
"Chrome extension relay needs an attached tab: user must click the Pool Bot Browser Relay toolbar icon on the tab (badge ON). If no tab is connected, ask them to attach it.",
|
|
151
151
|
"When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc).",
|
|
152
152
|
'For stable, self-resolving refs across calls, use snapshot with refs="aria" (Playwright aria-ref ids). Default refs="role" are role+name-based.',
|
|
153
153
|
"Use snapshot+act for UI automation. Avoid act:wait by default; use only in exceptional cases when no reliable UI state exists.",
|
|
@@ -254,9 +254,28 @@ export function createBrowserTool(opts) {
|
|
|
254
254
|
profile,
|
|
255
255
|
});
|
|
256
256
|
const tabs = result.tabs ?? [];
|
|
257
|
-
|
|
257
|
+
const wrapped = wrapBrowserExternalJson({
|
|
258
|
+
kind: "tabs",
|
|
259
|
+
payload: { tabs },
|
|
260
|
+
includeWarning: false,
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
content: [{ type: "text", text: wrapped.wrappedText }],
|
|
264
|
+
details: { ...wrapped.safeDetails, tabCount: tabs.length },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
{
|
|
268
|
+
const tabs = await browserTabs(baseUrl, { profile });
|
|
269
|
+
const wrapped = wrapBrowserExternalJson({
|
|
270
|
+
kind: "tabs",
|
|
271
|
+
payload: { tabs },
|
|
272
|
+
includeWarning: false,
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
content: [{ type: "text", text: wrapped.wrappedText }],
|
|
276
|
+
details: { ...wrapped.safeDetails, tabCount: tabs.length },
|
|
277
|
+
};
|
|
258
278
|
}
|
|
259
|
-
return jsonResult({ tabs: await browserTabs(baseUrl, { profile }) });
|
|
260
279
|
case "open": {
|
|
261
280
|
const targetUrl = readStringParam(params, "targetUrl", {
|
|
262
281
|
required: true,
|
|
@@ -305,10 +324,12 @@ export function createBrowserTool(opts) {
|
|
|
305
324
|
});
|
|
306
325
|
return jsonResult(result);
|
|
307
326
|
}
|
|
308
|
-
if (targetId)
|
|
327
|
+
if (targetId) {
|
|
309
328
|
await browserCloseTab(baseUrl, targetId, { profile });
|
|
310
|
-
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
311
331
|
await browserAct(baseUrl, { kind: "close" }, { profile });
|
|
332
|
+
}
|
|
312
333
|
return jsonResult({ ok: true });
|
|
313
334
|
}
|
|
314
335
|
case "snapshot": {
|
|
@@ -383,20 +404,68 @@ export function createBrowserTool(opts) {
|
|
|
383
404
|
profile,
|
|
384
405
|
});
|
|
385
406
|
if (snapshot.format === "ai") {
|
|
407
|
+
const extractedText = snapshot.snapshot ?? "";
|
|
408
|
+
const wrappedSnapshot = wrapExternalContent(extractedText, {
|
|
409
|
+
source: "browser",
|
|
410
|
+
includeWarning: true,
|
|
411
|
+
});
|
|
412
|
+
const safeDetails = {
|
|
413
|
+
ok: true,
|
|
414
|
+
format: snapshot.format,
|
|
415
|
+
targetId: snapshot.targetId,
|
|
416
|
+
url: snapshot.url,
|
|
417
|
+
truncated: snapshot.truncated,
|
|
418
|
+
stats: snapshot.stats,
|
|
419
|
+
refs: snapshot.refs ? Object.keys(snapshot.refs).length : undefined,
|
|
420
|
+
labels: snapshot.labels,
|
|
421
|
+
labelsCount: snapshot.labelsCount,
|
|
422
|
+
labelsSkipped: snapshot.labelsSkipped,
|
|
423
|
+
imagePath: snapshot.imagePath,
|
|
424
|
+
imageType: snapshot.imageType,
|
|
425
|
+
externalContent: {
|
|
426
|
+
untrusted: true,
|
|
427
|
+
source: "browser",
|
|
428
|
+
kind: "snapshot",
|
|
429
|
+
format: "ai",
|
|
430
|
+
wrapped: true,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
386
433
|
if (labels && snapshot.imagePath) {
|
|
387
434
|
return await imageResultFromFile({
|
|
388
435
|
label: "browser:snapshot",
|
|
389
436
|
path: snapshot.imagePath,
|
|
390
|
-
extraText:
|
|
391
|
-
details:
|
|
437
|
+
extraText: wrappedSnapshot,
|
|
438
|
+
details: safeDetails,
|
|
392
439
|
});
|
|
393
440
|
}
|
|
394
441
|
return {
|
|
395
|
-
content: [{ type: "text", text:
|
|
396
|
-
details:
|
|
442
|
+
content: [{ type: "text", text: wrappedSnapshot }],
|
|
443
|
+
details: safeDetails,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
{
|
|
447
|
+
const wrapped = wrapBrowserExternalJson({
|
|
448
|
+
kind: "snapshot",
|
|
449
|
+
payload: snapshot,
|
|
450
|
+
});
|
|
451
|
+
return {
|
|
452
|
+
content: [{ type: "text", text: wrapped.wrappedText }],
|
|
453
|
+
details: {
|
|
454
|
+
...wrapped.safeDetails,
|
|
455
|
+
format: "aria",
|
|
456
|
+
targetId: snapshot.targetId,
|
|
457
|
+
url: snapshot.url,
|
|
458
|
+
nodeCount: snapshot.nodes.length,
|
|
459
|
+
externalContent: {
|
|
460
|
+
untrusted: true,
|
|
461
|
+
source: "browser",
|
|
462
|
+
kind: "snapshot",
|
|
463
|
+
format: "aria",
|
|
464
|
+
wrapped: true,
|
|
465
|
+
},
|
|
466
|
+
},
|
|
397
467
|
};
|
|
398
468
|
}
|
|
399
|
-
return jsonResult(snapshot);
|
|
400
469
|
}
|
|
401
470
|
case "screenshot": {
|
|
402
471
|
const targetId = readStringParam(params, "targetId");
|
|
@@ -458,7 +527,7 @@ export function createBrowserTool(opts) {
|
|
|
458
527
|
const level = typeof params.level === "string" ? params.level.trim() : undefined;
|
|
459
528
|
const targetId = typeof params.targetId === "string" ? params.targetId.trim() : undefined;
|
|
460
529
|
if (proxyRequest) {
|
|
461
|
-
const result = await proxyRequest({
|
|
530
|
+
const result = (await proxyRequest({
|
|
462
531
|
method: "GET",
|
|
463
532
|
path: "/console",
|
|
464
533
|
profile,
|
|
@@ -466,10 +535,37 @@ export function createBrowserTool(opts) {
|
|
|
466
535
|
level,
|
|
467
536
|
targetId,
|
|
468
537
|
},
|
|
538
|
+
}));
|
|
539
|
+
const wrapped = wrapBrowserExternalJson({
|
|
540
|
+
kind: "console",
|
|
541
|
+
payload: result,
|
|
542
|
+
includeWarning: false,
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
content: [{ type: "text", text: wrapped.wrappedText }],
|
|
546
|
+
details: {
|
|
547
|
+
...wrapped.safeDetails,
|
|
548
|
+
targetId: typeof result.targetId === "string" ? result.targetId : undefined,
|
|
549
|
+
messageCount: Array.isArray(result.messages) ? result.messages.length : undefined,
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
{
|
|
554
|
+
const result = await browserConsoleMessages(baseUrl, { level, targetId, profile });
|
|
555
|
+
const wrapped = wrapBrowserExternalJson({
|
|
556
|
+
kind: "console",
|
|
557
|
+
payload: result,
|
|
558
|
+
includeWarning: false,
|
|
469
559
|
});
|
|
470
|
-
return
|
|
560
|
+
return {
|
|
561
|
+
content: [{ type: "text", text: wrapped.wrappedText }],
|
|
562
|
+
details: {
|
|
563
|
+
...wrapped.safeDetails,
|
|
564
|
+
targetId: result.targetId,
|
|
565
|
+
messageCount: result.messages.length,
|
|
566
|
+
},
|
|
567
|
+
};
|
|
471
568
|
}
|
|
472
|
-
return jsonResult(await browserConsoleMessages(baseUrl, { level, targetId, profile }));
|
|
473
569
|
}
|
|
474
570
|
case "pdf": {
|
|
475
571
|
const targetId = typeof params.targetId === "string" ? params.targetId.trim() : undefined;
|
|
@@ -488,8 +584,18 @@ export function createBrowserTool(opts) {
|
|
|
488
584
|
}
|
|
489
585
|
case "upload": {
|
|
490
586
|
const paths = Array.isArray(params.paths) ? params.paths.map((p) => String(p)) : [];
|
|
491
|
-
if (paths.length === 0)
|
|
587
|
+
if (paths.length === 0) {
|
|
492
588
|
throw new Error("paths required");
|
|
589
|
+
}
|
|
590
|
+
const uploadPathsResult = resolvePathsWithinRoot({
|
|
591
|
+
rootDir: DEFAULT_UPLOAD_DIR,
|
|
592
|
+
requestedPaths: paths,
|
|
593
|
+
scopeLabel: `uploads directory (${DEFAULT_UPLOAD_DIR})`,
|
|
594
|
+
});
|
|
595
|
+
if (!uploadPathsResult.ok) {
|
|
596
|
+
throw new Error(uploadPathsResult.error);
|
|
597
|
+
}
|
|
598
|
+
const normalizedPaths = uploadPathsResult.paths;
|
|
493
599
|
const ref = readStringParam(params, "ref");
|
|
494
600
|
const inputRef = readStringParam(params, "inputRef");
|
|
495
601
|
const element = readStringParam(params, "element");
|
|
@@ -503,7 +609,7 @@ export function createBrowserTool(opts) {
|
|
|
503
609
|
path: "/hooks/file-chooser",
|
|
504
610
|
profile,
|
|
505
611
|
body: {
|
|
506
|
-
paths,
|
|
612
|
+
paths: normalizedPaths,
|
|
507
613
|
ref,
|
|
508
614
|
inputRef,
|
|
509
615
|
element,
|
|
@@ -514,7 +620,7 @@ export function createBrowserTool(opts) {
|
|
|
514
620
|
return jsonResult(result);
|
|
515
621
|
}
|
|
516
622
|
return jsonResult(await browserArmFileChooser(baseUrl, {
|
|
517
|
-
paths,
|
|
623
|
+
paths: normalizedPaths,
|
|
518
624
|
ref,
|
|
519
625
|
inputRef,
|
|
520
626
|
element,
|
|
@@ -581,9 +687,9 @@ export function createBrowserTool(opts) {
|
|
|
581
687
|
})).tabs ?? [])
|
|
582
688
|
: await browserTabs(baseUrl, { profile }).catch(() => []);
|
|
583
689
|
if (!tabs.length) {
|
|
584
|
-
throw new Error("No Chrome tabs are attached via the
|
|
690
|
+
throw new Error("No Chrome tabs are attached via the Pool Bot Browser Relay extension. Click the toolbar icon on the tab you want to control (badge ON), then retry.", { cause: err });
|
|
585
691
|
}
|
|
586
|
-
throw new Error(`Chrome tab not found (stale targetId?). Run action=tabs profile="chrome" and use one of the returned targetIds
|
|
692
|
+
throw new Error(`Chrome tab not found (stale targetId?). Run action=tabs profile="chrome" and use one of the returned targetIds.`, { cause: err });
|
|
587
693
|
}
|
|
588
694
|
throw err;
|
|
589
695
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
export const callGatewayMock = vi.fn();
|
|
3
|
+
vi.mock("../../gateway/call.js", () => ({
|
|
4
|
+
callGateway: (opts) => callGatewayMock(opts),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock("../agent-scope.js", () => ({
|
|
7
|
+
resolveSessionAgentId: () => "agent-123",
|
|
8
|
+
}));
|
|
9
|
+
export function resetCronToolGatewayMock() {
|
|
10
|
+
callGatewayMock.mockReset();
|
|
11
|
+
callGatewayMock.mockResolvedValue({ ok: true });
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PermissionFlagsBits } from "discord-api-types/v10";
|
|
2
|
+
import { readNumberParam, readStringParam } from "./common.js";
|
|
3
|
+
const moderationPermissions = {
|
|
4
|
+
timeout: PermissionFlagsBits.ModerateMembers,
|
|
5
|
+
kick: PermissionFlagsBits.KickMembers,
|
|
6
|
+
ban: PermissionFlagsBits.BanMembers,
|
|
7
|
+
};
|
|
8
|
+
export function isDiscordModerationAction(action) {
|
|
9
|
+
return action === "timeout" || action === "kick" || action === "ban";
|
|
10
|
+
}
|
|
11
|
+
export function requiredGuildPermissionForModerationAction(action) {
|
|
12
|
+
return moderationPermissions[action];
|
|
13
|
+
}
|
|
14
|
+
export function readDiscordModerationCommand(action, params) {
|
|
15
|
+
if (!isDiscordModerationAction(action)) {
|
|
16
|
+
throw new Error(`Unsupported Discord moderation action: ${action}`);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
action,
|
|
20
|
+
guildId: readStringParam(params, "guildId", { required: true }),
|
|
21
|
+
userId: readStringParam(params, "userId", { required: true }),
|
|
22
|
+
durationMinutes: readNumberParam(params, "durationMinutes", { integer: true }),
|
|
23
|
+
until: readStringParam(params, "until"),
|
|
24
|
+
reason: readStringParam(params, "reason"),
|
|
25
|
+
deleteMessageDays: readNumberParam(params, "deleteMessageDays", { integer: true }),
|
|
26
|
+
};
|
|
27
|
+
}
|