@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.
Files changed (235) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/acp/client.js +207 -18
  3. package/dist/acp/secret-file.js +22 -0
  4. package/dist/agents/agent-scope.js +10 -0
  5. package/dist/agents/bash-process-registry.test-helpers.js +29 -0
  6. package/dist/agents/bash-tools.exec-approval-request.js +20 -0
  7. package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
  8. package/dist/agents/bash-tools.exec-host-node.js +235 -0
  9. package/dist/agents/bash-tools.exec-types.js +1 -0
  10. package/dist/agents/bash-tools.process.js +224 -218
  11. package/dist/agents/content-blocks.js +16 -0
  12. package/dist/agents/model-fallback.js +96 -101
  13. package/dist/agents/models-config.providers.js +299 -182
  14. package/dist/agents/pi-embedded-payloads.js +1 -0
  15. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
  16. package/dist/agents/skills.test-helpers.js +13 -0
  17. package/dist/agents/stable-stringify.js +12 -0
  18. package/dist/agents/subagent-registry.mocks.shared.js +12 -0
  19. package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
  20. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
  21. package/dist/agents/tool-policy-shared.js +108 -0
  22. package/dist/agents/tools/browser-tool.js +160 -54
  23. package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
  24. package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
  25. package/dist/agents/tools/image-tool.js +214 -99
  26. package/dist/agents/tools/sessions-history-tool.js +140 -108
  27. package/dist/agents/workspace.js +222 -46
  28. package/dist/auto-reply/commands-registry.js +15 -18
  29. package/dist/auto-reply/fallback-state.js +114 -0
  30. package/dist/auto-reply/model-runtime.js +68 -0
  31. package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
  32. package/dist/auto-reply/reply/agent-runner.js +165 -39
  33. package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
  34. package/dist/browser/config.js +26 -0
  35. package/dist/browser/navigation-guard.js +31 -0
  36. package/dist/browser/routes/agent.act.js +431 -424
  37. package/dist/browser/routes/agent.shared.js +47 -3
  38. package/dist/browser/routes/agent.snapshot.js +122 -116
  39. package/dist/browser/routes/agent.storage.js +303 -297
  40. package/dist/browser/routes/tabs.js +154 -100
  41. package/dist/browser/server-lifecycle.js +37 -0
  42. package/dist/build-info.json +3 -3
  43. package/dist/channels/allow-from.js +25 -0
  44. package/dist/channels/plugins/account-action-gate.js +13 -0
  45. package/dist/channels/plugins/message-actions.js +10 -0
  46. package/dist/channels/telegram/api.js +18 -0
  47. package/dist/cli/argv.js +84 -21
  48. package/dist/cli/banner.js +2 -1
  49. package/dist/cli/exec-approvals-cli.js +92 -124
  50. package/dist/cli/memory-cli.js +158 -61
  51. package/dist/cli/nodes-cli/register.push.js +63 -0
  52. package/dist/cli/nodes-media-utils.js +21 -0
  53. package/dist/cli/plugins-cli.js +245 -61
  54. package/dist/cli/program/build-program.js +3 -1
  55. package/dist/cli/program/command-registry.js +223 -136
  56. package/dist/cli/program/help.js +43 -12
  57. package/dist/cli/route.js +1 -1
  58. package/dist/cli/test-runtime-capture.js +24 -0
  59. package/dist/commands/agent.js +163 -87
  60. package/dist/commands/channels.mock-harness.js +23 -0
  61. package/dist/commands/daemon-install-runtime-warning.js +11 -0
  62. package/dist/commands/onboard-helpers.js +4 -4
  63. package/dist/commands/sessions.test-helpers.js +61 -0
  64. package/dist/compat/legacy-names.js +2 -2
  65. package/dist/config/commands.js +3 -0
  66. package/dist/config/config.js +1 -1
  67. package/dist/config/env-substitution.js +62 -34
  68. package/dist/config/env-vars.js +9 -0
  69. package/dist/config/io.js +571 -171
  70. package/dist/config/merge-patch.js +50 -4
  71. package/dist/config/redact-snapshot.js +404 -76
  72. package/dist/config/schema.js +58 -570
  73. package/dist/config/validation.js +140 -85
  74. package/dist/config/zod-schema.hooks.js +40 -11
  75. package/dist/config/zod-schema.installs.js +20 -0
  76. package/dist/config/zod-schema.js +8 -7
  77. package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
  78. package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
  79. package/dist/control-ui/index.html +1 -1
  80. package/dist/daemon/cmd-argv.js +21 -0
  81. package/dist/daemon/cmd-set.js +58 -0
  82. package/dist/daemon/service-types.js +1 -0
  83. package/dist/discord/monitor/exec-approvals.js +357 -162
  84. package/dist/gateway/auth.js +38 -3
  85. package/dist/gateway/call.js +149 -68
  86. package/dist/gateway/canvas-capability.js +75 -0
  87. package/dist/gateway/control-plane-audit.js +28 -0
  88. package/dist/gateway/control-plane-rate-limit.js +53 -0
  89. package/dist/gateway/events.js +1 -0
  90. package/dist/gateway/hooks.js +109 -54
  91. package/dist/gateway/http-common.js +22 -0
  92. package/dist/gateway/method-scopes.js +169 -0
  93. package/dist/gateway/net.js +23 -0
  94. package/dist/gateway/openresponses-http.js +120 -110
  95. package/dist/gateway/probe-auth.js +2 -0
  96. package/dist/gateway/protocol/index.js +3 -2
  97. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
  98. package/dist/gateway/protocol/schema/push.js +18 -0
  99. package/dist/gateway/protocol/schema.js +1 -0
  100. package/dist/gateway/server-http.js +236 -52
  101. package/dist/gateway/server-methods/agent.js +162 -24
  102. package/dist/gateway/server-methods/chat.js +461 -130
  103. package/dist/gateway/server-methods/config.js +193 -150
  104. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  105. package/dist/gateway/server-methods/nodes.js +251 -69
  106. package/dist/gateway/server-methods/push.js +53 -0
  107. package/dist/gateway/server-reload-handlers.js +2 -3
  108. package/dist/gateway/server-runtime-config.js +5 -0
  109. package/dist/gateway/server-runtime-state.js +2 -0
  110. package/dist/gateway/server-ws-runtime.js +1 -0
  111. package/dist/gateway/server.impl.js +296 -139
  112. package/dist/gateway/session-preview.test-helpers.js +11 -0
  113. package/dist/gateway/startup-auth.js +126 -0
  114. package/dist/gateway/test-helpers.agent-results.js +15 -0
  115. package/dist/gateway/test-helpers.mocks.js +37 -14
  116. package/dist/gateway/test-helpers.server.js +161 -77
  117. package/dist/hooks/bundled/session-memory/handler.js +165 -34
  118. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  119. package/dist/infra/archive-path.js +49 -0
  120. package/dist/infra/device-pairing.js +148 -167
  121. package/dist/infra/exec-approvals-allowlist.js +19 -70
  122. package/dist/infra/exec-approvals-analysis.js +44 -17
  123. package/dist/infra/exec-safe-bin-policy.js +269 -0
  124. package/dist/infra/fixed-window-rate-limit.js +33 -0
  125. package/dist/infra/git-root.js +61 -0
  126. package/dist/infra/heartbeat-active-hours.js +2 -2
  127. package/dist/infra/heartbeat-reason.js +40 -0
  128. package/dist/infra/heartbeat-runner.js +72 -32
  129. package/dist/infra/install-source-utils.js +91 -7
  130. package/dist/infra/node-pairing.js +50 -105
  131. package/dist/infra/npm-integrity.js +45 -0
  132. package/dist/infra/npm-pack-install.js +40 -0
  133. package/dist/infra/outbound/channel-adapters.js +20 -7
  134. package/dist/infra/outbound/message-action-runner.js +107 -327
  135. package/dist/infra/outbound/message.js +59 -36
  136. package/dist/infra/outbound/outbound-policy.js +52 -25
  137. package/dist/infra/outbound/outbound-send-service.js +58 -71
  138. package/dist/infra/pairing-files.js +10 -0
  139. package/dist/infra/plain-object.js +9 -0
  140. package/dist/infra/push-apns.js +365 -0
  141. package/dist/infra/restart-sentinel.js +16 -1
  142. package/dist/infra/restart.js +229 -26
  143. package/dist/infra/scp-host.js +54 -0
  144. package/dist/infra/update-startup.js +86 -9
  145. package/dist/media/inbound-path-policy.js +114 -0
  146. package/dist/media/input-files.js +16 -0
  147. package/dist/memory/test-manager.js +8 -0
  148. package/dist/plugin-sdk/temp-path.js +47 -0
  149. package/dist/plugins/discovery.js +217 -23
  150. package/dist/plugins/hook-runner-global.js +16 -0
  151. package/dist/plugins/loader.js +192 -26
  152. package/dist/plugins/logger.js +8 -0
  153. package/dist/plugins/manifest-registry.js +3 -0
  154. package/dist/plugins/path-safety.js +34 -0
  155. package/dist/plugins/registry.js +5 -2
  156. package/dist/plugins/runtime/index.js +271 -206
  157. package/dist/providers/github-copilot-models.js +4 -1
  158. package/dist/security/audit-channel.js +8 -19
  159. package/dist/security/audit-extra.async.js +354 -182
  160. package/dist/security/audit-extra.js +11 -1
  161. package/dist/security/audit-extra.sync.js +340 -33
  162. package/dist/security/audit-fs.js +31 -13
  163. package/dist/security/audit.js +145 -371
  164. package/dist/security/dm-policy-shared.js +24 -0
  165. package/dist/security/external-content.js +20 -8
  166. package/dist/security/fix.js +49 -85
  167. package/dist/security/scan-paths.js +20 -0
  168. package/dist/security/secret-equal.js +3 -7
  169. package/dist/security/windows-acl.js +30 -15
  170. package/dist/shared/node-list-parse.js +13 -0
  171. package/dist/shared/operator-scope-compat.js +37 -0
  172. package/dist/shared/text-chunking.js +29 -0
  173. package/dist/slack/blocks.test-helpers.js +31 -0
  174. package/dist/slack/monitor/mrkdwn.js +8 -0
  175. package/dist/telegram/bot-message-dispatch.js +366 -164
  176. package/dist/telegram/draft-stream.js +30 -7
  177. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  178. package/dist/terminal/prompt-select-styled.js +9 -0
  179. package/dist/test-utils/command-runner.js +6 -0
  180. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  181. package/dist/test-utils/model-auth-mock.js +12 -0
  182. package/dist/test-utils/provider-usage-fetch.js +14 -0
  183. package/dist/test-utils/temp-home.js +33 -0
  184. package/dist/tui/components/chat-log.js +9 -0
  185. package/dist/tui/tui-command-handlers.js +36 -27
  186. package/dist/tui/tui-event-handlers.js +122 -32
  187. package/dist/tui/tui.js +181 -45
  188. package/dist/utils/mask-api-key.js +10 -0
  189. package/dist/utils/run-with-concurrency.js +39 -0
  190. package/dist/web/media.js +4 -0
  191. package/docs/tools/slash-commands.md +5 -1
  192. package/extensions/bluebubbles/package.json +1 -1
  193. package/extensions/copilot-proxy/package.json +1 -1
  194. package/extensions/diagnostics-otel/package.json +1 -1
  195. package/extensions/discord/package.json +1 -1
  196. package/extensions/feishu/package.json +1 -1
  197. package/extensions/feishu/src/external-keys.ts +19 -0
  198. package/extensions/google-antigravity-auth/package.json +1 -1
  199. package/extensions/google-gemini-cli-auth/package.json +1 -1
  200. package/extensions/googlechat/package.json +1 -1
  201. package/extensions/imessage/package.json +1 -1
  202. package/extensions/irc/package.json +1 -1
  203. package/extensions/line/package.json +1 -1
  204. package/extensions/llm-task/package.json +1 -1
  205. package/extensions/lobster/package.json +1 -1
  206. package/extensions/lobster/src/windows-spawn.ts +193 -0
  207. package/extensions/matrix/CHANGELOG.md +5 -0
  208. package/extensions/matrix/package.json +1 -1
  209. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  210. package/extensions/mattermost/package.json +1 -1
  211. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  212. package/extensions/memory-core/package.json +1 -1
  213. package/extensions/memory-lancedb/package.json +1 -1
  214. package/extensions/minimax-portal-auth/package.json +1 -1
  215. package/extensions/msteams/CHANGELOG.md +5 -0
  216. package/extensions/msteams/package.json +1 -1
  217. package/extensions/nextcloud-talk/package.json +1 -1
  218. package/extensions/nostr/CHANGELOG.md +5 -0
  219. package/extensions/nostr/package.json +1 -1
  220. package/extensions/open-prose/package.json +1 -1
  221. package/extensions/openai-codex-auth/package.json +1 -1
  222. package/extensions/signal/package.json +1 -1
  223. package/extensions/slack/package.json +1 -1
  224. package/extensions/telegram/package.json +1 -1
  225. package/extensions/tlon/package.json +1 -1
  226. package/extensions/twitch/CHANGELOG.md +5 -0
  227. package/extensions/twitch/package.json +1 -1
  228. package/extensions/voice-call/CHANGELOG.md +5 -0
  229. package/extensions/voice-call/package.json +1 -1
  230. package/extensions/whatsapp/package.json +1 -1
  231. package/extensions/zalo/CHANGELOG.md +5 -0
  232. package/extensions/zalo/package.json +1 -1
  233. package/extensions/zalouser/CHANGELOG.md +5 -0
  234. package/extensions/zalouser/package.json +1 -1
  235. package/package.json +1 -1
@@ -3,37 +3,43 @@ import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { createJiti } from "jiti";
5
5
  import { createSubsystemLogger } from "../logging/subsystem.js";
6
+ import { isPathInsideWithRealpath } from "../security/scan-paths.js";
6
7
  import { resolveUserPath } from "../utils.js";
8
+ import { clearPluginCommands } from "./commands.js";
9
+ import { applyTestPluginDefaults, normalizePluginsConfig, resolveEnableState, resolveMemorySlotDecision, } from "./config-state.js";
7
10
  import { discoverPoolBotPlugins } from "./discovery.js";
8
- import { loadPluginManifestRegistry } from "./manifest-registry.js";
9
- import { normalizePluginsConfig, resolveEnableState, resolveMemorySlotDecision, } from "./config-state.js";
10
11
  import { initializeGlobalHookRunner } from "./hook-runner-global.js";
11
- import { clearPluginCommands } from "./commands.js";
12
+ import { loadPluginManifestRegistry } from "./manifest-registry.js";
13
+ import { isPathInside, safeStatSync } from "./path-safety.js";
12
14
  import { createPluginRegistry } from "./registry.js";
13
- import { createPluginRuntime } from "./runtime/index.js";
14
15
  import { setActivePluginRegistry } from "./runtime.js";
16
+ import { createPluginRuntime } from "./runtime/index.js";
15
17
  import { validateJsonSchemaValue } from "./schema-validator.js";
16
18
  const registryCache = new Map();
17
19
  const defaultLogger = () => createSubsystemLogger("plugins");
18
- const resolvePluginSdkAlias = () => {
20
+ const resolvePluginSdkAliasFile = (params) => {
19
21
  try {
20
22
  const modulePath = fileURLToPath(import.meta.url);
21
- const isDistRuntime = modulePath.split(path.sep).includes("dist");
22
- const preferDist = process.env.VITEST || process.env.NODE_ENV === "test" || isDistRuntime;
23
+ const isProduction = process.env.NODE_ENV === "production";
24
+ const isTest = process.env.VITEST || process.env.NODE_ENV === "test";
23
25
  let cursor = path.dirname(modulePath);
24
26
  for (let i = 0; i < 6; i += 1) {
25
- const srcCandidate = path.join(cursor, "src", "plugin-sdk", "index.ts");
26
- const distCandidate = path.join(cursor, "dist", "plugin-sdk", "index.js");
27
- const orderedCandidates = preferDist
28
- ? [distCandidate, srcCandidate]
27
+ const srcCandidate = path.join(cursor, "src", "plugin-sdk", params.srcFile);
28
+ const distCandidate = path.join(cursor, "dist", "plugin-sdk", params.distFile);
29
+ const orderedCandidates = isProduction
30
+ ? isTest
31
+ ? [distCandidate, srcCandidate]
32
+ : [distCandidate]
29
33
  : [srcCandidate, distCandidate];
30
34
  for (const candidate of orderedCandidates) {
31
- if (fs.existsSync(candidate))
35
+ if (fs.existsSync(candidate)) {
32
36
  return candidate;
37
+ }
33
38
  }
34
39
  const parent = path.dirname(cursor);
35
- if (parent === cursor)
40
+ if (parent === cursor) {
36
41
  break;
42
+ }
37
43
  cursor = parent;
38
44
  }
39
45
  }
@@ -42,6 +48,10 @@ const resolvePluginSdkAlias = () => {
42
48
  }
43
49
  return null;
44
50
  };
51
+ const resolvePluginSdkAlias = () => resolvePluginSdkAliasFile({ srcFile: "index.ts", distFile: "index.js" });
52
+ const resolvePluginSdkAccountIdAlias = () => {
53
+ return resolvePluginSdkAliasFile({ srcFile: "account-id.ts", distFile: "account-id.js" });
54
+ };
45
55
  function buildCacheKey(params) {
46
56
  const workspaceKey = params.workspaceDir ? resolveUserPath(params.workspaceDir) : "";
47
57
  return `${workspaceKey}::${JSON.stringify(params.plugins)}`;
@@ -109,8 +119,118 @@ function createPluginRecord(params) {
109
119
  function pushDiagnostics(diagnostics, append) {
110
120
  diagnostics.push(...append);
111
121
  }
122
+ function createPathMatcher() {
123
+ return { exact: new Set(), dirs: [] };
124
+ }
125
+ function addPathToMatcher(matcher, rawPath) {
126
+ const trimmed = rawPath.trim();
127
+ if (!trimmed) {
128
+ return;
129
+ }
130
+ const resolved = resolveUserPath(trimmed);
131
+ if (!resolved) {
132
+ return;
133
+ }
134
+ if (matcher.exact.has(resolved) || matcher.dirs.includes(resolved)) {
135
+ return;
136
+ }
137
+ const stat = safeStatSync(resolved);
138
+ if (stat?.isDirectory()) {
139
+ matcher.dirs.push(resolved);
140
+ return;
141
+ }
142
+ matcher.exact.add(resolved);
143
+ }
144
+ function matchesPathMatcher(matcher, sourcePath) {
145
+ if (matcher.exact.has(sourcePath)) {
146
+ return true;
147
+ }
148
+ return matcher.dirs.some((dirPath) => isPathInside(dirPath, sourcePath));
149
+ }
150
+ function buildProvenanceIndex(params) {
151
+ const loadPathMatcher = createPathMatcher();
152
+ for (const loadPath of params.normalizedLoadPaths) {
153
+ addPathToMatcher(loadPathMatcher, loadPath);
154
+ }
155
+ const installRules = new Map();
156
+ const installs = params.config.plugins?.installs ?? {};
157
+ for (const [pluginId, install] of Object.entries(installs)) {
158
+ const rule = {
159
+ trackedWithoutPaths: false,
160
+ matcher: createPathMatcher(),
161
+ };
162
+ const trackedPaths = [install.installPath, install.sourcePath]
163
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
164
+ .filter(Boolean);
165
+ if (trackedPaths.length === 0) {
166
+ rule.trackedWithoutPaths = true;
167
+ }
168
+ else {
169
+ for (const trackedPath of trackedPaths) {
170
+ addPathToMatcher(rule.matcher, trackedPath);
171
+ }
172
+ }
173
+ installRules.set(pluginId, rule);
174
+ }
175
+ return { loadPathMatcher, installRules };
176
+ }
177
+ function isTrackedByProvenance(params) {
178
+ const sourcePath = resolveUserPath(params.source);
179
+ const installRule = params.index.installRules.get(params.pluginId);
180
+ if (installRule) {
181
+ if (installRule.trackedWithoutPaths) {
182
+ return true;
183
+ }
184
+ if (matchesPathMatcher(installRule.matcher, sourcePath)) {
185
+ return true;
186
+ }
187
+ }
188
+ return matchesPathMatcher(params.index.loadPathMatcher, sourcePath);
189
+ }
190
+ function warnWhenAllowlistIsOpen(params) {
191
+ if (!params.pluginsEnabled) {
192
+ return;
193
+ }
194
+ if (params.allow.length > 0) {
195
+ return;
196
+ }
197
+ const nonBundled = params.discoverablePlugins.filter((entry) => entry.origin !== "bundled");
198
+ if (nonBundled.length === 0) {
199
+ return;
200
+ }
201
+ const preview = nonBundled
202
+ .slice(0, 6)
203
+ .map((entry) => `${entry.id} (${entry.source})`)
204
+ .join(", ");
205
+ const extra = nonBundled.length > 6 ? ` (+${nonBundled.length - 6} more)` : "";
206
+ params.logger.warn(`[plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: ${preview}${extra}. Set plugins.allow to explicit trusted ids.`);
207
+ }
208
+ function warnAboutUntrackedLoadedPlugins(params) {
209
+ for (const plugin of params.registry.plugins) {
210
+ if (plugin.status !== "loaded" || plugin.origin === "bundled") {
211
+ continue;
212
+ }
213
+ if (isTrackedByProvenance({
214
+ pluginId: plugin.id,
215
+ source: plugin.source,
216
+ index: params.provenance,
217
+ })) {
218
+ continue;
219
+ }
220
+ const message = "loaded without install/load-path provenance; treat as untracked local code and pin trust via plugins.allow or install records";
221
+ params.registry.diagnostics.push({
222
+ level: "warn",
223
+ pluginId: plugin.id,
224
+ source: plugin.source,
225
+ message,
226
+ });
227
+ params.logger.warn(`[plugins] ${plugin.id}: ${message} (${plugin.source})`);
228
+ }
229
+ }
112
230
  export function loadPoolBotPlugins(options = {}) {
113
- const cfg = options.config ?? {};
231
+ // Test env: default-disable plugins unless explicitly configured.
232
+ // This keeps unit/gateway suites fast and avoids loading heavyweight plugin deps by accident.
233
+ const cfg = applyTestPluginDefaults(options.config ?? {}, process.env);
114
234
  const logger = options.logger ?? defaultLogger();
115
235
  const validateOnly = options.mode === "validate";
116
236
  const normalized = normalizePluginsConfig(cfg.plugins);
@@ -146,18 +266,44 @@ export function loadPoolBotPlugins(options = {}) {
146
266
  diagnostics: discovery.diagnostics,
147
267
  });
148
268
  pushDiagnostics(registry.diagnostics, manifestRegistry.diagnostics);
149
- const pluginSdkAlias = resolvePluginSdkAlias();
150
- const jiti = createJiti(import.meta.url, {
151
- interopDefault: true,
152
- extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
153
- ...(pluginSdkAlias
154
- ? {
155
- alias: {
156
- "poolbot/plugin-sdk": pluginSdkAlias,
157
- },
158
- }
159
- : {}),
269
+ warnWhenAllowlistIsOpen({
270
+ logger,
271
+ pluginsEnabled: normalized.enabled,
272
+ allow: normalized.allow,
273
+ discoverablePlugins: manifestRegistry.plugins.map((plugin) => ({
274
+ id: plugin.id,
275
+ source: plugin.source,
276
+ origin: plugin.origin,
277
+ })),
160
278
  });
279
+ const provenance = buildProvenanceIndex({
280
+ config: cfg,
281
+ normalizedLoadPaths: normalized.loadPaths,
282
+ });
283
+ // Lazy: avoid creating the Jiti loader when all plugins are disabled (common in unit tests).
284
+ let jitiLoader = null;
285
+ const getJiti = () => {
286
+ if (jitiLoader) {
287
+ return jitiLoader;
288
+ }
289
+ const pluginSdkAlias = resolvePluginSdkAlias();
290
+ const pluginSdkAccountIdAlias = resolvePluginSdkAccountIdAlias();
291
+ jitiLoader = createJiti(import.meta.url, {
292
+ interopDefault: true,
293
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
294
+ ...(pluginSdkAlias || pluginSdkAccountIdAlias
295
+ ? {
296
+ alias: {
297
+ ...(pluginSdkAlias ? { "poolbot/plugin-sdk": pluginSdkAlias } : {}),
298
+ ...(pluginSdkAccountIdAlias
299
+ ? { "poolbot/plugin-sdk/account-id": pluginSdkAccountIdAlias }
300
+ : {}),
301
+ },
302
+ }
303
+ : {}),
304
+ });
305
+ return jitiLoader;
306
+ };
161
307
  const manifestByRoot = new Map(manifestRegistry.plugins.map((record) => [record.rootDir, record]));
162
308
  const seenIds = new Map();
163
309
  const memorySlot = normalized.slots.memory;
@@ -223,9 +369,24 @@ export function loadPoolBotPlugins(options = {}) {
223
369
  });
224
370
  continue;
225
371
  }
372
+ if (!isPathInsideWithRealpath(candidate.rootDir, candidate.source, {
373
+ requireRealpath: true,
374
+ })) {
375
+ record.status = "error";
376
+ record.error = "plugin entry path escapes plugin root";
377
+ registry.plugins.push(record);
378
+ seenIds.set(pluginId, candidate.origin);
379
+ registry.diagnostics.push({
380
+ level: "error",
381
+ pluginId: record.id,
382
+ source: record.source,
383
+ message: record.error,
384
+ });
385
+ continue;
386
+ }
226
387
  let mod = null;
227
388
  try {
228
- mod = jiti(candidate.source);
389
+ mod = getJiti()(candidate.source);
229
390
  }
230
391
  catch (err) {
231
392
  logger.error(`[plugins] ${record.id} failed to load from ${record.source}: ${String(err)}`);
@@ -361,6 +522,11 @@ export function loadPoolBotPlugins(options = {}) {
361
522
  message: `memory slot plugin not found or not marked as memory: ${memorySlot}`,
362
523
  });
363
524
  }
525
+ warnAboutUntrackedLoadedPlugins({
526
+ registry,
527
+ provenance,
528
+ logger,
529
+ });
364
530
  if (cacheEnabled) {
365
531
  registryCache.set(cacheKey, registry);
366
532
  }
@@ -0,0 +1,8 @@
1
+ export function createPluginLoaderLogger(logger) {
2
+ return {
3
+ info: (msg) => logger.info(msg),
4
+ warn: (msg) => logger.warn(msg),
5
+ error: (msg) => logger.error(msg),
6
+ debug: (msg) => logger.debug?.(msg),
7
+ };
8
+ }
@@ -5,6 +5,9 @@ import { discoverPoolBotPlugins } from "./discovery.js";
5
5
  import { loadPluginManifest } from "./manifest.js";
6
6
  const registryCache = new Map();
7
7
  const DEFAULT_MANIFEST_CACHE_MS = 200;
8
+ export function clearPluginManifestRegistryCache() {
9
+ registryCache.clear();
10
+ }
8
11
  function resolveManifestCacheMs(env) {
9
12
  const raw = env.POOLBOT_PLUGIN_MANIFEST_CACHE_MS?.trim() || env.CLAWDBOT_PLUGIN_MANIFEST_CACHE_MS?.trim();
10
13
  if (raw === "" || raw === "0")
@@ -0,0 +1,34 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export function isPathInside(baseDir, targetPath) {
4
+ const rel = path.relative(baseDir, targetPath);
5
+ if (!rel) {
6
+ return true;
7
+ }
8
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
9
+ }
10
+ export function safeRealpathSync(targetPath, cache) {
11
+ const cached = cache?.get(targetPath);
12
+ if (cached) {
13
+ return cached;
14
+ }
15
+ try {
16
+ const resolved = fs.realpathSync(targetPath);
17
+ cache?.set(targetPath, resolved);
18
+ return resolved;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ export function safeStatSync(targetPath) {
25
+ try {
26
+ return fs.statSync(targetPath);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ export function formatPosixMode(mode) {
33
+ return (mode & 0o777).toString(8).padStart(3, "0");
34
+ }
@@ -3,8 +3,8 @@ import { resolveUserPath } from "../utils.js";
3
3
  import { registerPluginCommand } from "./commands.js";
4
4
  import path from "node:path";
5
5
  import { normalizePluginHttpPath } from "./http-path.js";
6
- export function createPluginRegistry(registryParams) {
7
- const registry = {
6
+ export function createEmptyPluginRegistry() {
7
+ return {
8
8
  plugins: [],
9
9
  tools: [],
10
10
  hooks: [],
@@ -19,6 +19,9 @@ export function createPluginRegistry(registryParams) {
19
19
  commands: [],
20
20
  diagnostics: [],
21
21
  };
22
+ }
23
+ export function createPluginRegistry(registryParams) {
24
+ const registry = createEmptyPluginRegistry();
22
25
  const coreGatewayMethods = new Set(Object.keys(registryParams.coreGatewayHandlers ?? {}));
23
26
  const pushDiagnostic = (diag) => {
24
27
  registry.diagnostics.push(diag);