@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,47 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
function sanitizePrefix(prefix) {
|
|
6
|
+
const normalized = prefix.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7
|
+
return normalized || "tmp";
|
|
8
|
+
}
|
|
9
|
+
function sanitizeExtension(extension) {
|
|
10
|
+
if (!extension) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
const normalized = extension.startsWith(".") ? extension : `.${extension}`;
|
|
14
|
+
const suffix = normalized.match(/[a-zA-Z0-9._-]+$/)?.[0] ?? "";
|
|
15
|
+
const token = suffix.replace(/^[._-]+/, "");
|
|
16
|
+
if (!token) {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
return `.${token}`;
|
|
20
|
+
}
|
|
21
|
+
function sanitizeFileName(fileName) {
|
|
22
|
+
const base = path.basename(fileName).replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
23
|
+
const normalized = base.replace(/^-+|-+$/g, "");
|
|
24
|
+
return normalized || "download.bin";
|
|
25
|
+
}
|
|
26
|
+
export function buildRandomTempFilePath(params) {
|
|
27
|
+
const prefix = sanitizePrefix(params.prefix);
|
|
28
|
+
const extension = sanitizeExtension(params.extension);
|
|
29
|
+
const nowCandidate = params.now;
|
|
30
|
+
const now = typeof nowCandidate === "number" && Number.isFinite(nowCandidate)
|
|
31
|
+
? Math.trunc(nowCandidate)
|
|
32
|
+
: Date.now();
|
|
33
|
+
const uuid = params.uuid?.trim() || crypto.randomUUID();
|
|
34
|
+
return path.join(params.tmpDir ?? os.tmpdir(), `${prefix}-${now}-${uuid}${extension}`);
|
|
35
|
+
}
|
|
36
|
+
export async function withTempDownloadPath(params, fn) {
|
|
37
|
+
const tempRoot = params.tmpDir ?? os.tmpdir();
|
|
38
|
+
const prefix = `${sanitizePrefix(params.prefix)}-`;
|
|
39
|
+
const dir = await mkdtemp(path.join(tempRoot, prefix));
|
|
40
|
+
const tmpPath = path.join(dir, sanitizeFileName(params.fileName ?? "download.bin"));
|
|
41
|
+
try {
|
|
42
|
+
return await fn(tmpPath);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,19 +1,145 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { isPathInsideWithRealpath } from "../security/scan-paths.js";
|
|
3
4
|
import { resolveConfigDir, resolveUserPath } from "../utils.js";
|
|
4
5
|
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
|
5
6
|
import { getPackageManifestMetadata, } from "./manifest.js";
|
|
7
|
+
import { formatPosixMode, isPathInside, safeRealpathSync, safeStatSync } from "./path-safety.js";
|
|
6
8
|
const EXTENSION_EXTS = new Set([".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"]);
|
|
9
|
+
function currentUid(overrideUid) {
|
|
10
|
+
if (overrideUid !== undefined) {
|
|
11
|
+
return overrideUid;
|
|
12
|
+
}
|
|
13
|
+
if (process.platform === "win32") {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (typeof process.getuid !== "function") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return process.getuid();
|
|
20
|
+
}
|
|
21
|
+
function checkSourceEscapesRoot(params) {
|
|
22
|
+
const sourceRealPath = safeRealpathSync(params.source);
|
|
23
|
+
const rootRealPath = safeRealpathSync(params.rootDir);
|
|
24
|
+
if (!sourceRealPath || !rootRealPath) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (isPathInside(rootRealPath, sourceRealPath)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
reason: "source_escapes_root",
|
|
32
|
+
sourcePath: params.source,
|
|
33
|
+
rootPath: params.rootDir,
|
|
34
|
+
targetPath: params.source,
|
|
35
|
+
sourceRealPath,
|
|
36
|
+
rootRealPath,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function checkPathStatAndPermissions(params) {
|
|
40
|
+
if (process.platform === "win32") {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const pathsToCheck = [params.rootDir, params.source];
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
for (const targetPath of pathsToCheck) {
|
|
46
|
+
const normalized = path.resolve(targetPath);
|
|
47
|
+
if (seen.has(normalized)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
seen.add(normalized);
|
|
51
|
+
const stat = safeStatSync(targetPath);
|
|
52
|
+
if (!stat) {
|
|
53
|
+
return {
|
|
54
|
+
reason: "path_stat_failed",
|
|
55
|
+
sourcePath: params.source,
|
|
56
|
+
rootPath: params.rootDir,
|
|
57
|
+
targetPath,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const modeBits = stat.mode & 0o777;
|
|
61
|
+
if ((modeBits & 0o002) !== 0) {
|
|
62
|
+
return {
|
|
63
|
+
reason: "path_world_writable",
|
|
64
|
+
sourcePath: params.source,
|
|
65
|
+
rootPath: params.rootDir,
|
|
66
|
+
targetPath,
|
|
67
|
+
modeBits,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (params.origin !== "bundled" &&
|
|
71
|
+
params.uid !== null &&
|
|
72
|
+
typeof stat.uid === "number" &&
|
|
73
|
+
stat.uid !== params.uid &&
|
|
74
|
+
stat.uid !== 0) {
|
|
75
|
+
return {
|
|
76
|
+
reason: "path_suspicious_ownership",
|
|
77
|
+
sourcePath: params.source,
|
|
78
|
+
rootPath: params.rootDir,
|
|
79
|
+
targetPath,
|
|
80
|
+
foundUid: stat.uid,
|
|
81
|
+
expectedUid: params.uid,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
function findCandidateBlockIssue(params) {
|
|
88
|
+
const escaped = checkSourceEscapesRoot({
|
|
89
|
+
source: params.source,
|
|
90
|
+
rootDir: params.rootDir,
|
|
91
|
+
});
|
|
92
|
+
if (escaped) {
|
|
93
|
+
return escaped;
|
|
94
|
+
}
|
|
95
|
+
return checkPathStatAndPermissions({
|
|
96
|
+
source: params.source,
|
|
97
|
+
rootDir: params.rootDir,
|
|
98
|
+
origin: params.origin,
|
|
99
|
+
uid: currentUid(params.ownershipUid),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function formatCandidateBlockMessage(issue) {
|
|
103
|
+
if (issue.reason === "source_escapes_root") {
|
|
104
|
+
return `blocked plugin candidate: source escapes plugin root (${issue.sourcePath} -> ${issue.sourceRealPath}; root=${issue.rootRealPath})`;
|
|
105
|
+
}
|
|
106
|
+
if (issue.reason === "path_stat_failed") {
|
|
107
|
+
return `blocked plugin candidate: cannot stat path (${issue.targetPath})`;
|
|
108
|
+
}
|
|
109
|
+
if (issue.reason === "path_world_writable") {
|
|
110
|
+
return `blocked plugin candidate: world-writable path (${issue.targetPath}, mode=${formatPosixMode(issue.modeBits ?? 0)})`;
|
|
111
|
+
}
|
|
112
|
+
return `blocked plugin candidate: suspicious ownership (${issue.targetPath}, uid=${issue.foundUid}, expected uid=${issue.expectedUid} or root)`;
|
|
113
|
+
}
|
|
114
|
+
function isUnsafePluginCandidate(params) {
|
|
115
|
+
const issue = findCandidateBlockIssue({
|
|
116
|
+
source: params.source,
|
|
117
|
+
rootDir: params.rootDir,
|
|
118
|
+
origin: params.origin,
|
|
119
|
+
ownershipUid: params.ownershipUid,
|
|
120
|
+
});
|
|
121
|
+
if (!issue) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
params.diagnostics.push({
|
|
125
|
+
level: "warn",
|
|
126
|
+
source: issue.targetPath,
|
|
127
|
+
message: formatCandidateBlockMessage(issue),
|
|
128
|
+
});
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
7
131
|
function isExtensionFile(filePath) {
|
|
8
132
|
const ext = path.extname(filePath);
|
|
9
|
-
if (!EXTENSION_EXTS.has(ext))
|
|
133
|
+
if (!EXTENSION_EXTS.has(ext)) {
|
|
10
134
|
return false;
|
|
135
|
+
}
|
|
11
136
|
return !filePath.endsWith(".d.ts");
|
|
12
137
|
}
|
|
13
138
|
function readPackageManifest(dir) {
|
|
14
139
|
const manifestPath = path.join(dir, "package.json");
|
|
15
|
-
if (!fs.existsSync(manifestPath))
|
|
140
|
+
if (!fs.existsSync(manifestPath)) {
|
|
16
141
|
return null;
|
|
142
|
+
}
|
|
17
143
|
try {
|
|
18
144
|
const raw = fs.readFileSync(manifestPath, "utf-8");
|
|
19
145
|
return JSON.parse(raw);
|
|
@@ -24,34 +150,48 @@ function readPackageManifest(dir) {
|
|
|
24
150
|
}
|
|
25
151
|
function resolvePackageExtensions(manifest) {
|
|
26
152
|
const raw = getPackageManifestMetadata(manifest)?.extensions;
|
|
27
|
-
if (!Array.isArray(raw))
|
|
153
|
+
if (!Array.isArray(raw)) {
|
|
28
154
|
return [];
|
|
155
|
+
}
|
|
29
156
|
return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
|
30
157
|
}
|
|
31
158
|
function deriveIdHint(params) {
|
|
32
159
|
const base = path.basename(params.filePath, path.extname(params.filePath));
|
|
33
160
|
const rawPackageName = params.packageName?.trim();
|
|
34
|
-
if (!rawPackageName)
|
|
161
|
+
if (!rawPackageName) {
|
|
35
162
|
return base;
|
|
163
|
+
}
|
|
36
164
|
// Prefer the unscoped name so config keys stay stable even when the npm
|
|
37
165
|
// package is scoped (example: @poolbot/voice-call -> voice-call).
|
|
38
166
|
const unscoped = rawPackageName.includes("/")
|
|
39
167
|
? (rawPackageName.split("/").pop() ?? rawPackageName)
|
|
40
168
|
: rawPackageName;
|
|
41
|
-
if (!params.hasMultipleExtensions)
|
|
169
|
+
if (!params.hasMultipleExtensions) {
|
|
42
170
|
return unscoped;
|
|
171
|
+
}
|
|
43
172
|
return `${unscoped}/${base}`;
|
|
44
173
|
}
|
|
45
174
|
function addCandidate(params) {
|
|
46
175
|
const resolved = path.resolve(params.source);
|
|
47
|
-
if (params.seen.has(resolved))
|
|
176
|
+
if (params.seen.has(resolved)) {
|
|
48
177
|
return;
|
|
178
|
+
}
|
|
179
|
+
const resolvedRoot = path.resolve(params.rootDir);
|
|
180
|
+
if (isUnsafePluginCandidate({
|
|
181
|
+
source: resolved,
|
|
182
|
+
rootDir: resolvedRoot,
|
|
183
|
+
origin: params.origin,
|
|
184
|
+
diagnostics: params.diagnostics,
|
|
185
|
+
ownershipUid: params.ownershipUid,
|
|
186
|
+
})) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
49
189
|
params.seen.add(resolved);
|
|
50
190
|
const manifest = params.manifest ?? null;
|
|
51
191
|
params.candidates.push({
|
|
52
192
|
idHint: params.idHint,
|
|
53
193
|
source: resolved,
|
|
54
|
-
rootDir:
|
|
194
|
+
rootDir: resolvedRoot,
|
|
55
195
|
origin: params.origin,
|
|
56
196
|
workspaceDir: params.workspaceDir,
|
|
57
197
|
packageName: manifest?.name?.trim() || undefined,
|
|
@@ -61,9 +201,24 @@ function addCandidate(params) {
|
|
|
61
201
|
packagePoolbot: getPackageManifestMetadata(manifest ?? undefined),
|
|
62
202
|
});
|
|
63
203
|
}
|
|
204
|
+
function resolvePackageEntrySource(params) {
|
|
205
|
+
const source = path.resolve(params.packageDir, params.entryPath);
|
|
206
|
+
if (!isPathInsideWithRealpath(params.packageDir, source, {
|
|
207
|
+
requireRealpath: true,
|
|
208
|
+
})) {
|
|
209
|
+
params.diagnostics.push({
|
|
210
|
+
level: "error",
|
|
211
|
+
message: `extension entry escapes package directory: ${params.entryPath}`,
|
|
212
|
+
source: params.sourceLabel,
|
|
213
|
+
});
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
return source;
|
|
217
|
+
}
|
|
64
218
|
function discoverInDirectory(params) {
|
|
65
|
-
if (!fs.existsSync(params.dir))
|
|
219
|
+
if (!fs.existsSync(params.dir)) {
|
|
66
220
|
return;
|
|
221
|
+
}
|
|
67
222
|
let entries = [];
|
|
68
223
|
try {
|
|
69
224
|
entries = fs.readdirSync(params.dir, { withFileTypes: true });
|
|
@@ -79,27 +234,40 @@ function discoverInDirectory(params) {
|
|
|
79
234
|
for (const entry of entries) {
|
|
80
235
|
const fullPath = path.join(params.dir, entry.name);
|
|
81
236
|
if (entry.isFile()) {
|
|
82
|
-
if (!isExtensionFile(fullPath))
|
|
237
|
+
if (!isExtensionFile(fullPath)) {
|
|
83
238
|
continue;
|
|
239
|
+
}
|
|
84
240
|
addCandidate({
|
|
85
241
|
candidates: params.candidates,
|
|
242
|
+
diagnostics: params.diagnostics,
|
|
86
243
|
seen: params.seen,
|
|
87
244
|
idHint: path.basename(entry.name, path.extname(entry.name)),
|
|
88
245
|
source: fullPath,
|
|
89
246
|
rootDir: path.dirname(fullPath),
|
|
90
247
|
origin: params.origin,
|
|
248
|
+
ownershipUid: params.ownershipUid,
|
|
91
249
|
workspaceDir: params.workspaceDir,
|
|
92
250
|
});
|
|
93
251
|
}
|
|
94
|
-
if (!entry.isDirectory())
|
|
252
|
+
if (!entry.isDirectory()) {
|
|
95
253
|
continue;
|
|
254
|
+
}
|
|
96
255
|
const manifest = readPackageManifest(fullPath);
|
|
97
256
|
const extensions = manifest ? resolvePackageExtensions(manifest) : [];
|
|
98
257
|
if (extensions.length > 0) {
|
|
99
258
|
for (const extPath of extensions) {
|
|
100
|
-
const resolved =
|
|
259
|
+
const resolved = resolvePackageEntrySource({
|
|
260
|
+
packageDir: fullPath,
|
|
261
|
+
entryPath: extPath,
|
|
262
|
+
sourceLabel: fullPath,
|
|
263
|
+
diagnostics: params.diagnostics,
|
|
264
|
+
});
|
|
265
|
+
if (!resolved) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
101
268
|
addCandidate({
|
|
102
269
|
candidates: params.candidates,
|
|
270
|
+
diagnostics: params.diagnostics,
|
|
103
271
|
seen: params.seen,
|
|
104
272
|
idHint: deriveIdHint({
|
|
105
273
|
filePath: resolved,
|
|
@@ -109,6 +277,7 @@ function discoverInDirectory(params) {
|
|
|
109
277
|
source: resolved,
|
|
110
278
|
rootDir: fullPath,
|
|
111
279
|
origin: params.origin,
|
|
280
|
+
ownershipUid: params.ownershipUid,
|
|
112
281
|
workspaceDir: params.workspaceDir,
|
|
113
282
|
manifest,
|
|
114
283
|
packageDir: fullPath,
|
|
@@ -123,11 +292,13 @@ function discoverInDirectory(params) {
|
|
|
123
292
|
if (indexFile && isExtensionFile(indexFile)) {
|
|
124
293
|
addCandidate({
|
|
125
294
|
candidates: params.candidates,
|
|
295
|
+
diagnostics: params.diagnostics,
|
|
126
296
|
seen: params.seen,
|
|
127
297
|
idHint: entry.name,
|
|
128
298
|
source: indexFile,
|
|
129
299
|
rootDir: fullPath,
|
|
130
300
|
origin: params.origin,
|
|
301
|
+
ownershipUid: params.ownershipUid,
|
|
131
302
|
workspaceDir: params.workspaceDir,
|
|
132
303
|
manifest,
|
|
133
304
|
packageDir: fullPath,
|
|
@@ -157,11 +328,13 @@ function discoverFromPath(params) {
|
|
|
157
328
|
}
|
|
158
329
|
addCandidate({
|
|
159
330
|
candidates: params.candidates,
|
|
331
|
+
diagnostics: params.diagnostics,
|
|
160
332
|
seen: params.seen,
|
|
161
333
|
idHint: path.basename(resolved, path.extname(resolved)),
|
|
162
334
|
source: resolved,
|
|
163
335
|
rootDir: path.dirname(resolved),
|
|
164
336
|
origin: params.origin,
|
|
337
|
+
ownershipUid: params.ownershipUid,
|
|
165
338
|
workspaceDir: params.workspaceDir,
|
|
166
339
|
});
|
|
167
340
|
return;
|
|
@@ -171,9 +344,18 @@ function discoverFromPath(params) {
|
|
|
171
344
|
const extensions = manifest ? resolvePackageExtensions(manifest) : [];
|
|
172
345
|
if (extensions.length > 0) {
|
|
173
346
|
for (const extPath of extensions) {
|
|
174
|
-
const source =
|
|
347
|
+
const source = resolvePackageEntrySource({
|
|
348
|
+
packageDir: resolved,
|
|
349
|
+
entryPath: extPath,
|
|
350
|
+
sourceLabel: resolved,
|
|
351
|
+
diagnostics: params.diagnostics,
|
|
352
|
+
});
|
|
353
|
+
if (!source) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
175
356
|
addCandidate({
|
|
176
357
|
candidates: params.candidates,
|
|
358
|
+
diagnostics: params.diagnostics,
|
|
177
359
|
seen: params.seen,
|
|
178
360
|
idHint: deriveIdHint({
|
|
179
361
|
filePath: source,
|
|
@@ -183,6 +365,7 @@ function discoverFromPath(params) {
|
|
|
183
365
|
source,
|
|
184
366
|
rootDir: resolved,
|
|
185
367
|
origin: params.origin,
|
|
368
|
+
ownershipUid: params.ownershipUid,
|
|
186
369
|
workspaceDir: params.workspaceDir,
|
|
187
370
|
manifest,
|
|
188
371
|
packageDir: resolved,
|
|
@@ -197,11 +380,13 @@ function discoverFromPath(params) {
|
|
|
197
380
|
if (indexFile && isExtensionFile(indexFile)) {
|
|
198
381
|
addCandidate({
|
|
199
382
|
candidates: params.candidates,
|
|
383
|
+
diagnostics: params.diagnostics,
|
|
200
384
|
seen: params.seen,
|
|
201
385
|
idHint: path.basename(resolved),
|
|
202
386
|
source: indexFile,
|
|
203
387
|
rootDir: resolved,
|
|
204
388
|
origin: params.origin,
|
|
389
|
+
ownershipUid: params.ownershipUid,
|
|
205
390
|
workspaceDir: params.workspaceDir,
|
|
206
391
|
manifest,
|
|
207
392
|
packageDir: resolved,
|
|
@@ -211,6 +396,7 @@ function discoverFromPath(params) {
|
|
|
211
396
|
discoverInDirectory({
|
|
212
397
|
dir: resolved,
|
|
213
398
|
origin: params.origin,
|
|
399
|
+
ownershipUid: params.ownershipUid,
|
|
214
400
|
workspaceDir: params.workspaceDir,
|
|
215
401
|
candidates: params.candidates,
|
|
216
402
|
diagnostics: params.diagnostics,
|
|
@@ -226,14 +412,17 @@ export function discoverPoolBotPlugins(params) {
|
|
|
226
412
|
const workspaceDir = params.workspaceDir?.trim();
|
|
227
413
|
const extra = params.extraPaths ?? [];
|
|
228
414
|
for (const extraPath of extra) {
|
|
229
|
-
if (typeof extraPath !== "string")
|
|
415
|
+
if (typeof extraPath !== "string") {
|
|
230
416
|
continue;
|
|
417
|
+
}
|
|
231
418
|
const trimmed = extraPath.trim();
|
|
232
|
-
if (!trimmed)
|
|
419
|
+
if (!trimmed) {
|
|
233
420
|
continue;
|
|
421
|
+
}
|
|
234
422
|
discoverFromPath({
|
|
235
423
|
rawPath: trimmed,
|
|
236
424
|
origin: "config",
|
|
425
|
+
ownershipUid: params.ownershipUid,
|
|
237
426
|
workspaceDir: workspaceDir?.trim() || undefined,
|
|
238
427
|
candidates,
|
|
239
428
|
diagnostics,
|
|
@@ -242,20 +431,24 @@ export function discoverPoolBotPlugins(params) {
|
|
|
242
431
|
}
|
|
243
432
|
if (workspaceDir) {
|
|
244
433
|
const workspaceRoot = resolveUserPath(workspaceDir);
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
434
|
+
const workspaceExtDirs = [path.join(workspaceRoot, ".poolbot", "extensions")];
|
|
435
|
+
for (const dir of workspaceExtDirs) {
|
|
436
|
+
discoverInDirectory({
|
|
437
|
+
dir,
|
|
438
|
+
origin: "workspace",
|
|
439
|
+
ownershipUid: params.ownershipUid,
|
|
440
|
+
workspaceDir: workspaceRoot,
|
|
441
|
+
candidates,
|
|
442
|
+
diagnostics,
|
|
443
|
+
seen,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
254
446
|
}
|
|
255
447
|
const globalDir = path.join(resolveConfigDir(), "extensions");
|
|
256
448
|
discoverInDirectory({
|
|
257
449
|
dir: globalDir,
|
|
258
450
|
origin: "global",
|
|
451
|
+
ownershipUid: params.ownershipUid,
|
|
259
452
|
candidates,
|
|
260
453
|
diagnostics,
|
|
261
454
|
seen,
|
|
@@ -265,6 +458,7 @@ export function discoverPoolBotPlugins(params) {
|
|
|
265
458
|
discoverInDirectory({
|
|
266
459
|
dir: bundledDir,
|
|
267
460
|
origin: "bundled",
|
|
461
|
+
ownershipUid: params.ownershipUid,
|
|
268
462
|
candidates,
|
|
269
463
|
diagnostics,
|
|
270
464
|
seen,
|
|
@@ -48,6 +48,22 @@ export function getGlobalPluginRegistry() {
|
|
|
48
48
|
export function hasGlobalHooks(hookName) {
|
|
49
49
|
return globalHookRunner?.hasHooks(hookName) ?? false;
|
|
50
50
|
}
|
|
51
|
+
export async function runGlobalGatewayStopSafely(params) {
|
|
52
|
+
const hookRunner = getGlobalHookRunner();
|
|
53
|
+
if (!hookRunner?.hasHooks("gateway_stop")) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
await hookRunner.runGatewayStop(params.event, params.ctx);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (params.onError) {
|
|
61
|
+
params.onError(err);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
log.warn(`gateway_stop hook failed: ${String(err)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
51
67
|
/**
|
|
52
68
|
* Reset the global hook runner (for testing).
|
|
53
69
|
*/
|