@poolzin/pool-bot 2026.2.0 → 2026.2.1

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 (230) hide show
  1. package/dist/agents/bash-tools.exec.js +76 -25
  2. package/dist/agents/cli-runner/helpers.js +9 -11
  3. package/dist/agents/identity.js +47 -7
  4. package/dist/agents/memory-search.js +25 -8
  5. package/dist/agents/model-selection.js +21 -0
  6. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  7. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  8. package/dist/agents/pi-embedded-helpers.js +1 -1
  9. package/dist/agents/pi-embedded-runner/compact.js +1 -0
  10. package/dist/agents/pi-embedded-runner/model.js +61 -2
  11. package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
  12. package/dist/agents/pi-embedded-runner/run.js +199 -46
  13. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  14. package/dist/agents/pi-embedded-subscribe.js +118 -29
  15. package/dist/agents/pi-tools.js +10 -5
  16. package/dist/agents/poolbot-tools.js +15 -10
  17. package/dist/agents/sandbox-paths.js +31 -0
  18. package/dist/agents/session-tool-result-guard.js +94 -15
  19. package/dist/agents/shell-utils.js +51 -0
  20. package/dist/agents/skills/bundled-context.js +23 -0
  21. package/dist/agents/skills/bundled-dir.js +41 -7
  22. package/dist/agents/skills-install.js +60 -23
  23. package/dist/agents/subagent-announce.js +79 -34
  24. package/dist/agents/tool-policy.conformance.js +14 -0
  25. package/dist/agents/tool-policy.js +24 -0
  26. package/dist/agents/tools/cron-tool.js +166 -19
  27. package/dist/agents/tools/discord-actions-presence.js +78 -0
  28. package/dist/agents/tools/message-tool.js +56 -2
  29. package/dist/agents/tools/sessions-history-tool.js +69 -1
  30. package/dist/agents/tools/web-search.js +211 -42
  31. package/dist/agents/usage.js +23 -1
  32. package/dist/agents/workspace-run.js +67 -0
  33. package/dist/agents/workspace-templates.js +44 -0
  34. package/dist/auto-reply/command-auth.js +121 -6
  35. package/dist/auto-reply/envelope.js +50 -72
  36. package/dist/auto-reply/reply/commands-compact.js +1 -0
  37. package/dist/auto-reply/reply/commands-context-report.js +1 -0
  38. package/dist/auto-reply/reply/commands-context.js +1 -0
  39. package/dist/auto-reply/reply/commands-models.js +107 -60
  40. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  41. package/dist/auto-reply/reply/get-reply-run.js +2 -1
  42. package/dist/auto-reply/reply/inbound-context.js +5 -1
  43. package/dist/auto-reply/reply/model-selection.js +3 -3
  44. package/dist/auto-reply/thinking.js +88 -43
  45. package/dist/browser/bridge-server.js +13 -0
  46. package/dist/browser/cdp.helpers.js +38 -24
  47. package/dist/browser/client-fetch.js +50 -7
  48. package/dist/browser/config.js +1 -10
  49. package/dist/browser/extension-relay.js +101 -40
  50. package/dist/browser/pw-ai.js +1 -1
  51. package/dist/browser/pw-session.js +143 -8
  52. package/dist/browser/pw-tools-core.interactions.js +125 -27
  53. package/dist/browser/pw-tools-core.responses.js +1 -1
  54. package/dist/browser/pw-tools-core.state.js +1 -1
  55. package/dist/browser/routes/agent.act.js +86 -41
  56. package/dist/browser/routes/dispatcher.js +4 -4
  57. package/dist/browser/screenshot.js +1 -1
  58. package/dist/browser/server.js +13 -0
  59. package/dist/build-info.json +3 -3
  60. package/dist/channels/reply-prefix.js +8 -1
  61. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  62. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  63. package/dist/cli/cron-cli/shared.js +56 -41
  64. package/dist/cli/dns-cli.js +26 -14
  65. package/dist/cli/gateway-cli/register.js +37 -19
  66. package/dist/cli/memory-cli.js +5 -5
  67. package/dist/cli/parse-bytes.js +37 -0
  68. package/dist/cli/update-cli.js +173 -52
  69. package/dist/commands/agent.js +1 -0
  70. package/dist/commands/doctor-config-flow.js +61 -5
  71. package/dist/commands/doctor-state-migrations.js +1 -1
  72. package/dist/commands/health.js +1 -1
  73. package/dist/commands/model-allowlist.js +29 -0
  74. package/dist/commands/model-picker.js +2 -1
  75. package/dist/commands/models/list.status-command.js +43 -23
  76. package/dist/commands/models/shared.js +15 -0
  77. package/dist/commands/onboard-custom.js +384 -0
  78. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  79. package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
  80. package/dist/commands/onboard-skills.js +63 -38
  81. package/dist/commands/openai-model-default.js +41 -0
  82. package/dist/config/defaults.js +3 -2
  83. package/dist/config/paths.js +136 -35
  84. package/dist/config/plugin-auto-enable.js +21 -5
  85. package/dist/config/redact-snapshot.js +153 -0
  86. package/dist/config/schema.field-metadata.js +590 -0
  87. package/dist/config/schema.js +2 -2
  88. package/dist/config/sessions/store.js +291 -23
  89. package/dist/config/zod-schema.agent-defaults.js +3 -0
  90. package/dist/config/zod-schema.agent-runtime.js +13 -2
  91. package/dist/config/zod-schema.providers-core.js +142 -0
  92. package/dist/config/zod-schema.session.js +3 -0
  93. package/dist/cron/delivery.js +57 -0
  94. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  95. package/dist/cron/isolated-agent/helpers.js +22 -5
  96. package/dist/cron/isolated-agent/run.js +171 -63
  97. package/dist/cron/isolated-agent/session.js +2 -0
  98. package/dist/cron/normalize.js +356 -28
  99. package/dist/cron/parse.js +10 -5
  100. package/dist/cron/run-log.js +35 -10
  101. package/dist/cron/schedule.js +41 -6
  102. package/dist/cron/service/jobs.js +208 -35
  103. package/dist/cron/service/ops.js +72 -16
  104. package/dist/cron/service/state.js +2 -0
  105. package/dist/cron/service/store.js +386 -14
  106. package/dist/cron/service/timer.js +390 -147
  107. package/dist/cron/session-reaper.js +86 -0
  108. package/dist/cron/store.js +23 -8
  109. package/dist/cron/validate-timestamp.js +43 -0
  110. package/dist/discord/monitor/agent-components.js +438 -0
  111. package/dist/discord/monitor/allow-list.js +28 -5
  112. package/dist/discord/monitor/gateway-registry.js +29 -0
  113. package/dist/discord/monitor/native-command.js +44 -23
  114. package/dist/discord/monitor/sender-identity.js +45 -0
  115. package/dist/discord/pluralkit.js +27 -0
  116. package/dist/discord/send.outbound.js +92 -5
  117. package/dist/discord/send.shared.js +60 -23
  118. package/dist/discord/targets.js +84 -1
  119. package/dist/entry.js +15 -9
  120. package/dist/extensionAPI.js +8 -0
  121. package/dist/gateway/control-ui.js +8 -1
  122. package/dist/gateway/hooks-mapping.js +3 -0
  123. package/dist/gateway/hooks.js +65 -0
  124. package/dist/gateway/net.js +96 -31
  125. package/dist/gateway/node-command-policy.js +50 -15
  126. package/dist/gateway/origin-check.js +56 -0
  127. package/dist/gateway/protocol/client-info.js +9 -0
  128. package/dist/gateway/protocol/index.js +9 -2
  129. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  130. package/dist/gateway/protocol/schema/cron.js +22 -10
  131. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  132. package/dist/gateway/protocol/schema/sessions.js +12 -0
  133. package/dist/gateway/server/hooks.js +1 -1
  134. package/dist/gateway/server-broadcast.js +26 -9
  135. package/dist/gateway/server-chat.js +112 -23
  136. package/dist/gateway/server-discovery-runtime.js +10 -2
  137. package/dist/gateway/server-http.js +109 -11
  138. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  139. package/dist/gateway/server-methods/agents.js +321 -2
  140. package/dist/gateway/server-methods/usage.js +559 -16
  141. package/dist/gateway/server-runtime-state.js +22 -8
  142. package/dist/gateway/server-startup-memory.js +16 -0
  143. package/dist/gateway/server.impl.js +5 -1
  144. package/dist/gateway/session-utils.fs.js +23 -25
  145. package/dist/gateway/session-utils.js +20 -10
  146. package/dist/gateway/sessions-patch.js +7 -22
  147. package/dist/gateway/test-helpers.server.js +35 -2
  148. package/dist/imessage/constants.js +2 -0
  149. package/dist/imessage/monitor/deliver.js +4 -1
  150. package/dist/imessage/monitor/monitor-provider.js +51 -1
  151. package/dist/infra/bonjour-discovery.js +131 -70
  152. package/dist/infra/control-ui-assets.js +134 -12
  153. package/dist/infra/errors.js +12 -0
  154. package/dist/infra/exec-approvals.js +266 -57
  155. package/dist/infra/format-time/format-datetime.js +79 -0
  156. package/dist/infra/format-time/format-duration.js +81 -0
  157. package/dist/infra/format-time/format-relative.js +80 -0
  158. package/dist/infra/heartbeat-runner.js +140 -49
  159. package/dist/infra/home-dir.js +54 -0
  160. package/dist/infra/net/fetch-guard.js +122 -0
  161. package/dist/infra/net/ssrf.js +65 -29
  162. package/dist/infra/outbound/abort.js +14 -0
  163. package/dist/infra/outbound/message-action-runner.js +77 -13
  164. package/dist/infra/outbound/outbound-session.js +143 -37
  165. package/dist/infra/poolbot-root.js +43 -1
  166. package/dist/infra/session-cost-usage.js +631 -41
  167. package/dist/infra/state-migrations.js +317 -47
  168. package/dist/infra/update-global.js +35 -0
  169. package/dist/infra/update-runner.js +149 -43
  170. package/dist/infra/warning-filter.js +65 -0
  171. package/dist/infra/widearea-dns.js +30 -9
  172. package/dist/logging/redact-identifier.js +12 -0
  173. package/dist/media/fetch.js +81 -58
  174. package/dist/media-understanding/apply.js +403 -3
  175. package/dist/media-understanding/attachments.js +38 -27
  176. package/dist/media-understanding/defaults.js +16 -0
  177. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  178. package/dist/media-understanding/providers/google/audio.js +24 -17
  179. package/dist/media-understanding/providers/google/video.js +24 -17
  180. package/dist/media-understanding/providers/image.js +2 -2
  181. package/dist/media-understanding/providers/index.js +4 -1
  182. package/dist/media-understanding/providers/openai/audio.js +22 -14
  183. package/dist/media-understanding/providers/shared.js +16 -11
  184. package/dist/media-understanding/providers/zai/index.js +6 -0
  185. package/dist/media-understanding/runner.js +158 -90
  186. package/dist/memory/batch-voyage.js +277 -0
  187. package/dist/memory/embeddings-voyage.js +75 -0
  188. package/dist/memory/embeddings.js +28 -16
  189. package/dist/memory/internal.js +101 -18
  190. package/dist/memory/manager.js +154 -48
  191. package/dist/memory/search-manager.js +173 -0
  192. package/dist/memory/session-files.js +9 -3
  193. package/dist/node-host/runner.js +34 -24
  194. package/dist/node-host/with-timeout.js +27 -0
  195. package/dist/plugins/commands.js +5 -1
  196. package/dist/plugins/config-state.js +86 -7
  197. package/dist/plugins/source-display.js +51 -0
  198. package/dist/process/exec.js +20 -2
  199. package/dist/routing/resolve-route.js +12 -0
  200. package/dist/routing/session-key.js +15 -0
  201. package/dist/runtime.js +2 -0
  202. package/dist/security/audit-extra.async.js +601 -0
  203. package/dist/security/audit-extra.js +2 -830
  204. package/dist/security/audit-extra.sync.js +505 -0
  205. package/dist/security/channel-metadata.js +34 -0
  206. package/dist/security/external-content.js +88 -6
  207. package/dist/security/skill-scanner.js +330 -0
  208. package/dist/sessions/session-key-utils.js +7 -0
  209. package/dist/signal/monitor/event-handler.js +80 -1
  210. package/dist/slack/monitor/media.js +85 -15
  211. package/dist/tailscale/detect.js +1 -2
  212. package/dist/telegram/bot/helpers.js +109 -28
  213. package/dist/telegram/bot-handlers.js +144 -3
  214. package/dist/telegram/bot-message-context.js +37 -10
  215. package/dist/telegram/bot-message-dispatch.js +48 -15
  216. package/dist/telegram/bot-native-commands.js +86 -29
  217. package/dist/telegram/bot.js +30 -29
  218. package/dist/telegram/model-buttons.js +163 -0
  219. package/dist/telegram/monitor.js +110 -85
  220. package/dist/telegram/send.js +129 -47
  221. package/dist/terminal/restore.js +45 -0
  222. package/dist/test-helpers/state-dir-env.js +16 -0
  223. package/dist/tts/tts.js +12 -6
  224. package/dist/tui/tui-session-actions.js +166 -54
  225. package/dist/utils/fetch-timeout.js +20 -0
  226. package/dist/utils/normalize-secret-input.js +19 -0
  227. package/dist/utils/transcript-tools.js +58 -0
  228. package/dist/utils.js +45 -14
  229. package/dist/version.js +42 -5
  230. package/package.json +1 -1
@@ -18,12 +18,15 @@ function hashExecApprovalsRaw(raw) {
18
18
  .digest("hex");
19
19
  }
20
20
  function expandHome(value) {
21
- if (!value)
21
+ if (!value) {
22
22
  return value;
23
- if (value === "~")
23
+ }
24
+ if (value === "~") {
24
25
  return os.homedir();
25
- if (value.startsWith("~/"))
26
+ }
27
+ if (value.startsWith("~/")) {
26
28
  return path.join(os.homedir(), value.slice(2));
29
+ }
27
30
  return value;
28
31
  }
29
32
  export function resolveExecApprovalsPath() {
@@ -41,15 +44,18 @@ function mergeLegacyAgent(current, legacy) {
41
44
  const seen = new Set();
42
45
  const pushEntry = (entry) => {
43
46
  const key = normalizeAllowlistPattern(entry.pattern);
44
- if (!key || seen.has(key))
47
+ if (!key || seen.has(key)) {
45
48
  return;
49
+ }
46
50
  seen.add(key);
47
51
  allowlist.push(entry);
48
52
  };
49
- for (const entry of current.allowlist ?? [])
53
+ for (const entry of current.allowlist ?? []) {
50
54
  pushEntry(entry);
51
- for (const entry of legacy.allowlist ?? [])
55
+ }
56
+ for (const entry of legacy.allowlist ?? []) {
52
57
  pushEntry(entry);
58
+ }
53
59
  return {
54
60
  security: current.security ?? legacy.security,
55
61
  ask: current.ask ?? legacy.ask,
@@ -62,13 +68,49 @@ function ensureDir(filePath) {
62
68
  const dir = path.dirname(filePath);
63
69
  fs.mkdirSync(dir, { recursive: true });
64
70
  }
71
+ // Coerce legacy/corrupted allowlists into `ExecAllowlistEntry[]` before we spread
72
+ // entries to add ids (spreading strings creates {"0":"l","1":"s",...}).
73
+ function coerceAllowlistEntries(allowlist) {
74
+ if (!Array.isArray(allowlist) || allowlist.length === 0) {
75
+ return Array.isArray(allowlist) ? allowlist : undefined;
76
+ }
77
+ let changed = false;
78
+ const result = [];
79
+ for (const item of allowlist) {
80
+ if (typeof item === "string") {
81
+ const trimmed = item.trim();
82
+ if (trimmed) {
83
+ result.push({ pattern: trimmed });
84
+ changed = true;
85
+ }
86
+ else {
87
+ changed = true; // dropped empty string
88
+ }
89
+ }
90
+ else if (item && typeof item === "object" && !Array.isArray(item)) {
91
+ const pattern = item.pattern;
92
+ if (typeof pattern === "string" && pattern.trim().length > 0) {
93
+ result.push(item);
94
+ }
95
+ else {
96
+ changed = true; // dropped invalid entry
97
+ }
98
+ }
99
+ else {
100
+ changed = true; // dropped invalid entry
101
+ }
102
+ }
103
+ return changed ? (result.length > 0 ? result : undefined) : allowlist;
104
+ }
65
105
  function ensureAllowlistIds(allowlist) {
66
- if (!Array.isArray(allowlist) || allowlist.length === 0)
106
+ if (!Array.isArray(allowlist) || allowlist.length === 0) {
67
107
  return allowlist;
108
+ }
68
109
  let changed = false;
69
110
  const next = allowlist.map((entry) => {
70
- if (entry.id)
111
+ if (entry.id) {
71
112
  return entry;
113
+ }
72
114
  changed = true;
73
115
  return { ...entry, id: crypto.randomUUID() };
74
116
  });
@@ -85,7 +127,8 @@ export function normalizeExecApprovals(file) {
85
127
  delete agents.default;
86
128
  }
87
129
  for (const [key, agent] of Object.entries(agents)) {
88
- const allowlist = ensureAllowlistIds(agent.allowlist);
130
+ const coerced = coerceAllowlistEntries(agent.allowlist);
131
+ const allowlist = ensureAllowlistIds(coerced);
89
132
  if (allowlist !== agent.allowlist) {
90
133
  agents[key] = { ...agent, allowlist };
91
134
  }
@@ -184,13 +227,15 @@ export function ensureExecApprovals() {
184
227
  return updated;
185
228
  }
186
229
  function normalizeSecurity(value, fallback) {
187
- if (value === "allowlist" || value === "full" || value === "deny")
230
+ if (value === "allowlist" || value === "full" || value === "deny") {
188
231
  return value;
232
+ }
189
233
  return fallback;
190
234
  }
191
235
  function normalizeAsk(value, fallback) {
192
- if (value === "always" || value === "off" || value === "on-miss")
236
+ if (value === "always" || value === "off" || value === "on-miss") {
193
237
  return value;
238
+ }
194
239
  return fallback;
195
240
  }
196
241
  export function resolveExecApprovals(agentId, overrides) {
@@ -243,8 +288,9 @@ export function resolveExecApprovalsFromFile(params) {
243
288
  function isExecutableFile(filePath) {
244
289
  try {
245
290
  const stat = fs.statSync(filePath);
246
- if (!stat.isFile())
291
+ if (!stat.isFile()) {
247
292
  return false;
293
+ }
248
294
  if (process.platform !== "win32") {
249
295
  fs.accessSync(filePath, fs.constants.X_OK);
250
296
  }
@@ -256,13 +302,15 @@ function isExecutableFile(filePath) {
256
302
  }
257
303
  function parseFirstToken(command) {
258
304
  const trimmed = command.trim();
259
- if (!trimmed)
305
+ if (!trimmed) {
260
306
  return null;
307
+ }
261
308
  const first = trimmed[0];
262
309
  if (first === '"' || first === "'") {
263
310
  const end = trimmed.indexOf(first, 1);
264
- if (end > 1)
311
+ if (end > 1) {
265
312
  return trimmed.slice(1, end);
313
+ }
266
314
  return trimmed.slice(1);
267
315
  }
268
316
  const match = /^[^\s]+/.exec(trimmed);
@@ -295,24 +343,27 @@ function resolveExecutablePath(rawExecutable, cwd, env) {
295
343
  for (const entry of entries) {
296
344
  for (const ext of extensions) {
297
345
  const candidate = path.join(entry, expanded + ext);
298
- if (isExecutableFile(candidate))
346
+ if (isExecutableFile(candidate)) {
299
347
  return candidate;
348
+ }
300
349
  }
301
350
  }
302
351
  return undefined;
303
352
  }
304
353
  export function resolveCommandResolution(command, cwd, env) {
305
354
  const rawExecutable = parseFirstToken(command);
306
- if (!rawExecutable)
355
+ if (!rawExecutable) {
307
356
  return null;
357
+ }
308
358
  const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
309
359
  const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
310
360
  return { rawExecutable, resolvedPath, executableName };
311
361
  }
312
362
  export function resolveCommandResolutionFromArgv(argv, cwd, env) {
313
363
  const rawExecutable = argv[0]?.trim();
314
- if (!rawExecutable)
364
+ if (!rawExecutable) {
315
365
  return null;
366
+ }
316
367
  const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
317
368
  const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
318
369
  return { rawExecutable, resolvedPath, executableName };
@@ -361,8 +412,9 @@ function globToRegExp(pattern) {
361
412
  }
362
413
  function matchesPattern(pattern, target) {
363
414
  const trimmed = pattern.trim();
364
- if (!trimmed)
415
+ if (!trimmed) {
365
416
  return false;
417
+ }
366
418
  const expanded = trimmed.startsWith("~") ? expandHome(trimmed) : trimmed;
367
419
  const hasWildcard = /[*?]/.test(expanded);
368
420
  let normalizedPattern = expanded;
@@ -377,38 +429,64 @@ function matchesPattern(pattern, target) {
377
429
  return regex.test(normalizedTarget);
378
430
  }
379
431
  function resolveAllowlistCandidatePath(resolution, cwd) {
380
- if (!resolution)
432
+ if (!resolution) {
381
433
  return undefined;
382
- if (resolution.resolvedPath)
434
+ }
435
+ if (resolution.resolvedPath) {
383
436
  return resolution.resolvedPath;
437
+ }
384
438
  const raw = resolution.rawExecutable?.trim();
385
- if (!raw)
439
+ if (!raw) {
386
440
  return undefined;
441
+ }
387
442
  const expanded = raw.startsWith("~") ? expandHome(raw) : raw;
388
- if (!expanded.includes("/") && !expanded.includes("\\"))
443
+ if (!expanded.includes("/") && !expanded.includes("\\")) {
389
444
  return undefined;
390
- if (path.isAbsolute(expanded))
445
+ }
446
+ if (path.isAbsolute(expanded)) {
391
447
  return expanded;
448
+ }
392
449
  const base = cwd && cwd.trim() ? cwd.trim() : process.cwd();
393
450
  return path.resolve(base, expanded);
394
451
  }
395
452
  export function matchAllowlist(entries, resolution) {
396
- if (!entries.length || !resolution?.resolvedPath)
453
+ if (!entries.length || !resolution?.resolvedPath) {
397
454
  return null;
455
+ }
398
456
  const resolvedPath = resolution.resolvedPath;
399
457
  for (const entry of entries) {
400
458
  const pattern = entry.pattern?.trim();
401
- if (!pattern)
459
+ if (!pattern) {
402
460
  continue;
461
+ }
403
462
  const hasPath = pattern.includes("/") || pattern.includes("\\") || pattern.includes("~");
404
- if (!hasPath)
463
+ if (!hasPath) {
405
464
  continue;
406
- if (matchesPattern(pattern, resolvedPath))
465
+ }
466
+ if (matchesPattern(pattern, resolvedPath)) {
407
467
  return entry;
468
+ }
408
469
  }
409
470
  return null;
410
471
  }
411
472
  const DISALLOWED_PIPELINE_TOKENS = new Set([">", "<", "`", "\n", "\r", "(", ")"]);
473
+ const DOUBLE_QUOTE_ESCAPES = new Set(["\\", '"', "$", "`", "\n", "\r"]);
474
+ const WINDOWS_UNSUPPORTED_TOKENS = new Set([
475
+ "&",
476
+ "|",
477
+ "<",
478
+ ">",
479
+ "^",
480
+ "(",
481
+ ")",
482
+ "%",
483
+ "!",
484
+ "\n",
485
+ "\r",
486
+ ]);
487
+ function isDoubleQuoteEscape(next) {
488
+ return Boolean(next && DOUBLE_QUOTE_ESCAPES.has(next));
489
+ }
412
490
  /**
413
491
  * Iterates through a command string while respecting shell quoting rules.
414
492
  * The callback receives each character and the next character, and returns an action:
@@ -445,14 +523,31 @@ function iterateQuoteAware(command, onChar) {
445
523
  continue;
446
524
  }
447
525
  if (inSingle) {
448
- if (ch === "'")
526
+ if (ch === "'") {
449
527
  inSingle = false;
528
+ }
450
529
  buf += ch;
451
530
  continue;
452
531
  }
453
532
  if (inDouble) {
454
- if (ch === '"')
533
+ if (ch === "\\" && isDoubleQuoteEscape(next)) {
534
+ buf += ch;
535
+ buf += next;
536
+ i += 1;
537
+ continue;
538
+ }
539
+ if (ch === "$" && next === "(") {
540
+ return { ok: false, reason: "unsupported shell token: $()" };
541
+ }
542
+ if (ch === "`") {
543
+ return { ok: false, reason: "unsupported shell token: `" };
544
+ }
545
+ if (ch === "\n" || ch === "\r") {
546
+ return { ok: false, reason: "unsupported shell token: newline" };
547
+ }
548
+ if (ch === '"') {
455
549
  inDouble = false;
550
+ }
456
551
  buf += ch;
457
552
  continue;
458
553
  }
@@ -523,6 +618,75 @@ function splitShellPipeline(command) {
523
618
  }
524
619
  return { ok: true, segments: result.parts };
525
620
  }
621
+ function findWindowsUnsupportedToken(command) {
622
+ for (const ch of command) {
623
+ if (WINDOWS_UNSUPPORTED_TOKENS.has(ch)) {
624
+ if (ch === "\n" || ch === "\r") {
625
+ return "newline";
626
+ }
627
+ return ch;
628
+ }
629
+ }
630
+ return null;
631
+ }
632
+ function tokenizeWindowsSegment(segment) {
633
+ const tokens = [];
634
+ let buf = "";
635
+ let inDouble = false;
636
+ const pushToken = () => {
637
+ if (buf.length > 0) {
638
+ tokens.push(buf);
639
+ buf = "";
640
+ }
641
+ };
642
+ for (let i = 0; i < segment.length; i += 1) {
643
+ const ch = segment[i];
644
+ if (ch === '"') {
645
+ inDouble = !inDouble;
646
+ continue;
647
+ }
648
+ if (!inDouble && /\s/.test(ch)) {
649
+ pushToken();
650
+ continue;
651
+ }
652
+ buf += ch;
653
+ }
654
+ if (inDouble) {
655
+ return null;
656
+ }
657
+ pushToken();
658
+ return tokens.length > 0 ? tokens : null;
659
+ }
660
+ function analyzeWindowsShellCommand(params) {
661
+ const unsupported = findWindowsUnsupportedToken(params.command);
662
+ if (unsupported) {
663
+ return {
664
+ ok: false,
665
+ reason: `unsupported windows shell token: ${unsupported}`,
666
+ segments: [],
667
+ };
668
+ }
669
+ const argv = tokenizeWindowsSegment(params.command);
670
+ if (!argv || argv.length === 0) {
671
+ return { ok: false, reason: "unable to parse windows command", segments: [] };
672
+ }
673
+ return {
674
+ ok: true,
675
+ segments: [
676
+ {
677
+ raw: params.command,
678
+ argv,
679
+ resolution: resolveCommandResolutionFromArgv(argv, params.cwd, params.env),
680
+ },
681
+ ],
682
+ };
683
+ }
684
+ function isWindowsPlatform(platform) {
685
+ const normalized = String(platform ?? "")
686
+ .trim()
687
+ .toLowerCase();
688
+ return normalized.startsWith("win");
689
+ }
526
690
  function tokenizeShellSegment(segment) {
527
691
  const tokens = [];
528
692
  let buf = "";
@@ -556,6 +720,12 @@ function tokenizeShellSegment(segment) {
556
720
  continue;
557
721
  }
558
722
  if (inDouble) {
723
+ const next = segment[i + 1];
724
+ if (ch === "\\" && isDoubleQuoteEscape(next)) {
725
+ buf += next;
726
+ i += 1;
727
+ continue;
728
+ }
559
729
  if (ch === '"') {
560
730
  inDouble = false;
561
731
  }
@@ -600,6 +770,9 @@ function parseSegmentsFromParts(parts, cwd, env) {
600
770
  return segments;
601
771
  }
602
772
  export function analyzeShellCommand(params) {
773
+ if (isWindowsPlatform(params.platform)) {
774
+ return analyzeWindowsShellCommand(params);
775
+ }
603
776
  // First try splitting by chain operators (&&, ||, ;)
604
777
  const chainParts = splitCommandChain(params.command);
605
778
  if (chainParts) {
@@ -648,14 +821,18 @@ export function analyzeArgvCommand(params) {
648
821
  }
649
822
  function isPathLikeToken(value) {
650
823
  const trimmed = value.trim();
651
- if (!trimmed)
824
+ if (!trimmed) {
652
825
  return false;
653
- if (trimmed === "-")
826
+ }
827
+ if (trimmed === "-") {
654
828
  return false;
655
- if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~"))
829
+ }
830
+ if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) {
656
831
  return true;
657
- if (trimmed.startsWith("/"))
832
+ }
833
+ if (trimmed.startsWith("/")) {
658
834
  return true;
835
+ }
659
836
  return /^[A-Za-z]:[\\/]/.test(trimmed);
660
837
  }
661
838
  function defaultFileExists(filePath) {
@@ -667,40 +844,48 @@ function defaultFileExists(filePath) {
667
844
  }
668
845
  }
669
846
  export function normalizeSafeBins(entries) {
670
- if (!Array.isArray(entries))
847
+ if (!Array.isArray(entries)) {
671
848
  return new Set();
849
+ }
672
850
  const normalized = entries
673
851
  .map((entry) => entry.trim().toLowerCase())
674
852
  .filter((entry) => entry.length > 0);
675
853
  return new Set(normalized);
676
854
  }
677
855
  export function resolveSafeBins(entries) {
678
- if (entries === undefined)
856
+ if (entries === undefined) {
679
857
  return normalizeSafeBins(DEFAULT_SAFE_BINS);
858
+ }
680
859
  return normalizeSafeBins(entries ?? []);
681
860
  }
682
861
  export function isSafeBinUsage(params) {
683
- if (params.safeBins.size === 0)
862
+ if (params.safeBins.size === 0) {
684
863
  return false;
864
+ }
685
865
  const resolution = params.resolution;
686
866
  const execName = resolution?.executableName?.toLowerCase();
687
- if (!execName)
867
+ if (!execName) {
688
868
  return false;
869
+ }
689
870
  const matchesSafeBin = params.safeBins.has(execName) ||
690
871
  (process.platform === "win32" && params.safeBins.has(path.parse(execName).name));
691
- if (!matchesSafeBin)
872
+ if (!matchesSafeBin) {
692
873
  return false;
693
- if (!resolution?.resolvedPath)
874
+ }
875
+ if (!resolution?.resolvedPath) {
694
876
  return false;
877
+ }
695
878
  const cwd = params.cwd ?? process.cwd();
696
879
  const exists = params.fileExists ?? defaultFileExists;
697
880
  const argv = params.argv.slice(1);
698
881
  for (let i = 0; i < argv.length; i += 1) {
699
882
  const token = argv[i];
700
- if (!token)
883
+ if (!token) {
701
884
  continue;
702
- if (token === "-")
885
+ }
886
+ if (token === "-") {
703
887
  continue;
888
+ }
704
889
  if (token.startsWith("-")) {
705
890
  const eqIndex = token.indexOf("=");
706
891
  if (eqIndex > 0) {
@@ -711,10 +896,12 @@ export function isSafeBinUsage(params) {
711
896
  }
712
897
  continue;
713
898
  }
714
- if (isPathLikeToken(token))
899
+ if (isPathLikeToken(token)) {
715
900
  return false;
716
- if (exists(path.resolve(cwd, token)))
901
+ }
902
+ if (exists(path.resolve(cwd, token))) {
717
903
  return false;
904
+ }
718
905
  }
719
906
  return true;
720
907
  }
@@ -727,8 +914,9 @@ function evaluateSegments(segments, params) {
727
914
  ? { ...segment.resolution, resolvedPath: candidatePath }
728
915
  : segment.resolution;
729
916
  const match = matchAllowlist(params.allowlist, candidateResolution);
730
- if (match)
917
+ if (match) {
731
918
  matches.push(match);
919
+ }
732
920
  const safe = isSafeBinUsage({
733
921
  argv: segment.argv,
734
922
  resolution: segment.resolution,
@@ -798,6 +986,7 @@ function splitCommandChain(command) {
798
986
  };
799
987
  for (let i = 0; i < command.length; i += 1) {
800
988
  const ch = command[i];
989
+ const next = command[i + 1];
801
990
  if (escaped) {
802
991
  buf += ch;
803
992
  escaped = false;
@@ -809,14 +998,22 @@ function splitCommandChain(command) {
809
998
  continue;
810
999
  }
811
1000
  if (inSingle) {
812
- if (ch === "'")
1001
+ if (ch === "'") {
813
1002
  inSingle = false;
1003
+ }
814
1004
  buf += ch;
815
1005
  continue;
816
1006
  }
817
1007
  if (inDouble) {
818
- if (ch === '"')
1008
+ if (ch === "\\" && isDoubleQuoteEscape(next)) {
1009
+ buf += ch;
1010
+ buf += next;
1011
+ i += 1;
1012
+ continue;
1013
+ }
1014
+ if (ch === '"') {
819
1015
  inDouble = false;
1016
+ }
820
1017
  buf += ch;
821
1018
  continue;
822
1019
  }
@@ -831,44 +1028,50 @@ function splitCommandChain(command) {
831
1028
  continue;
832
1029
  }
833
1030
  if (ch === "&" && command[i + 1] === "&") {
834
- if (!pushPart())
1031
+ if (!pushPart()) {
835
1032
  invalidChain = true;
1033
+ }
836
1034
  i += 1;
837
1035
  foundChain = true;
838
1036
  continue;
839
1037
  }
840
1038
  if (ch === "|" && command[i + 1] === "|") {
841
- if (!pushPart())
1039
+ if (!pushPart()) {
842
1040
  invalidChain = true;
1041
+ }
843
1042
  i += 1;
844
1043
  foundChain = true;
845
1044
  continue;
846
1045
  }
847
1046
  if (ch === ";") {
848
- if (!pushPart())
1047
+ if (!pushPart()) {
849
1048
  invalidChain = true;
1049
+ }
850
1050
  foundChain = true;
851
1051
  continue;
852
1052
  }
853
1053
  buf += ch;
854
1054
  }
855
1055
  const pushedFinal = pushPart();
856
- if (!foundChain)
1056
+ if (!foundChain) {
857
1057
  return null;
858
- if (invalidChain || !pushedFinal)
1058
+ }
1059
+ if (invalidChain || !pushedFinal) {
859
1060
  return null;
1061
+ }
860
1062
  return parts.length > 0 ? parts : null;
861
1063
  }
862
1064
  /**
863
1065
  * Evaluates allowlist for shell commands (including &&, ||, ;) and returns analysis metadata.
864
1066
  */
865
1067
  export function evaluateShellAllowlist(params) {
866
- const chainParts = splitCommandChain(params.command);
1068
+ const chainParts = isWindowsPlatform(params.platform) ? null : splitCommandChain(params.command);
867
1069
  if (!chainParts) {
868
1070
  const analysis = analyzeShellCommand({
869
1071
  command: params.command,
870
1072
  cwd: params.cwd,
871
1073
  env: params.env,
1074
+ platform: params.platform,
872
1075
  });
873
1076
  if (!analysis.ok) {
874
1077
  return {
@@ -900,6 +1103,7 @@ export function evaluateShellAllowlist(params) {
900
1103
  command: part,
901
1104
  cwd: params.cwd,
902
1105
  env: params.env,
1106
+ platform: params.platform,
903
1107
  });
904
1108
  if (!analysis.ok) {
905
1109
  return {
@@ -965,10 +1169,12 @@ export function addAllowlistEntry(approvals, agentId, pattern) {
965
1169
  const existing = agents[target] ?? {};
966
1170
  const allowlist = Array.isArray(existing.allowlist) ? existing.allowlist : [];
967
1171
  const trimmed = pattern.trim();
968
- if (!trimmed)
1172
+ if (!trimmed) {
969
1173
  return;
970
- if (allowlist.some((entry) => entry.pattern === trimmed))
1174
+ }
1175
+ if (allowlist.some((entry) => entry.pattern === trimmed)) {
971
1176
  return;
1177
+ }
972
1178
  allowlist.push({ id: crypto.randomUUID(), pattern: trimmed, lastUsedAt: Date.now() });
973
1179
  agents[target] = { ...existing, allowlist };
974
1180
  approvals.agents = agents;
@@ -984,16 +1190,18 @@ export function maxAsk(a, b) {
984
1190
  }
985
1191
  export async function requestExecApprovalViaSocket(params) {
986
1192
  const { socketPath, token, request } = params;
987
- if (!socketPath || !token)
1193
+ if (!socketPath || !token) {
988
1194
  return null;
1195
+ }
989
1196
  const timeoutMs = params.timeoutMs ?? 15_000;
990
1197
  return await new Promise((resolve) => {
991
1198
  const client = new net.Socket();
992
1199
  let settled = false;
993
1200
  let buffer = "";
994
1201
  const finish = (value) => {
995
- if (settled)
1202
+ if (settled) {
996
1203
  return;
1204
+ }
997
1205
  settled = true;
998
1206
  try {
999
1207
  client.destroy();
@@ -1021,8 +1229,9 @@ export async function requestExecApprovalViaSocket(params) {
1021
1229
  const line = buffer.slice(0, idx).trim();
1022
1230
  buffer = buffer.slice(idx + 1);
1023
1231
  idx = buffer.indexOf("\n");
1024
- if (!line)
1232
+ if (!line) {
1025
1233
  continue;
1234
+ }
1026
1235
  try {
1027
1236
  const msg = JSON.parse(line);
1028
1237
  if (msg?.type === "decision" && msg.decision) {