@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
@@ -15,6 +15,46 @@ import { listNodes, resolveNodeIdFromList } from "./tools/nodes-utils.js";
15
15
  import { getShellConfig, sanitizeBinaryOutput } from "./shell-utils.js";
16
16
  import { buildCursorPositionResponse, stripDsrRequests } from "./pty-dsr.js";
17
17
  import { parseAgentSessionKey, resolveAgentIdFromSessionKey } from "../routing/session-key.js";
18
+ // Security: Blocklist of environment variables that could alter execution flow
19
+ // or inject code when running on non-sandboxed hosts (Gateway/Node).
20
+ const DANGEROUS_HOST_ENV_VARS = new Set([
21
+ "LD_PRELOAD",
22
+ "LD_LIBRARY_PATH",
23
+ "LD_AUDIT",
24
+ "DYLD_INSERT_LIBRARIES",
25
+ "DYLD_LIBRARY_PATH",
26
+ "NODE_OPTIONS",
27
+ "NODE_PATH",
28
+ "PYTHONPATH",
29
+ "PYTHONHOME",
30
+ "RUBYLIB",
31
+ "PERL5LIB",
32
+ "BASH_ENV",
33
+ "ENV",
34
+ "GCONV_PATH",
35
+ "IFS",
36
+ "SSLKEYLOGFILE",
37
+ ]);
38
+ const DANGEROUS_HOST_ENV_PREFIXES = ["DYLD_", "LD_"];
39
+ // Centralized sanitization helper.
40
+ // Throws an error if dangerous variables or PATH modifications are detected on the host.
41
+ function validateHostEnv(env) {
42
+ for (const key of Object.keys(env)) {
43
+ const upperKey = key.toUpperCase();
44
+ // 1. Block known dangerous variables (Fail Closed)
45
+ if (DANGEROUS_HOST_ENV_PREFIXES.some((prefix) => upperKey.startsWith(prefix))) {
46
+ throw new Error(`Security Violation: Environment variable '${key}' is forbidden during host execution.`);
47
+ }
48
+ if (DANGEROUS_HOST_ENV_VARS.has(upperKey)) {
49
+ throw new Error(`Security Violation: Environment variable '${key}' is forbidden during host execution.`);
50
+ }
51
+ // 2. Strictly block PATH modification on host
52
+ // Allowing custom PATH on the gateway/node can lead to binary hijacking.
53
+ if (upperKey === "PATH") {
54
+ throw new Error("Security Violation: Custom 'PATH' variable is forbidden during host execution.");
55
+ }
56
+ }
57
+ }
18
58
  const DEFAULT_MAX_OUTPUT = clampNumber(readEnvInt("PI_BASH_MAX_OUTPUT_CHARS"), 200_000, 1_000, 200_000);
19
59
  const DEFAULT_PENDING_MAX_OUTPUT = clampNumber(readEnvInt("CLAWDBOT_BASH_PENDING_MAX_OUTPUT_CHARS"), 200_000, 1_000, 200_000);
20
60
  const DEFAULT_PATH = process.env.PATH ?? "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
@@ -643,6 +683,11 @@ export function createExecTool(defaults) {
643
683
  workdir = resolveWorkdir(rawWorkdir, warnings);
644
684
  }
645
685
  const baseEnv = coerceEnv(process.env);
686
+ // Logic: Sandbox gets raw env. Host (gateway/node) must pass validation.
687
+ // We validate BEFORE merging to prevent any dangerous vars from entering the stream.
688
+ if (host !== "sandbox" && params.env) {
689
+ validateHostEnv(params.env);
690
+ }
646
691
  const mergedEnv = params.env ? { ...baseEnv, ...params.env } : baseEnv;
647
692
  const env = sandbox
648
693
  ? buildSandboxEnv({
@@ -684,7 +729,7 @@ export function createExecTool(defaults) {
684
729
  }
685
730
  catch (err) {
686
731
  if (!nodeQuery && String(err).includes("node required")) {
687
- throw new Error("exec host=node requires a node id when multiple nodes are available (set tools.exec.node or exec.node).");
732
+ throw new Error("exec host=node requires a node id when multiple nodes are available (set tools.exec.node or exec.node).", { cause: err });
688
733
  }
689
734
  throw err;
690
735
  }
@@ -706,12 +751,13 @@ export function createExecTool(defaults) {
706
751
  safeBins: new Set(),
707
752
  cwd: workdir,
708
753
  env,
754
+ platform: nodeInfo?.platform,
709
755
  });
710
756
  let analysisOk = baseAllowlistEval.analysisOk;
711
757
  let allowlistSatisfied = false;
712
758
  if (hostAsk === "on-miss" && hostSecurity === "allowlist" && analysisOk) {
713
759
  try {
714
- const approvalsSnapshot = (await callGatewayTool("exec.approvals.node.get", { timeoutMs: 10_000 }, { nodeId }));
760
+ const approvalsSnapshot = await callGatewayTool("exec.approvals.node.get", { timeoutMs: 10_000 }, { nodeId });
715
761
  const approvalsFile = approvalsSnapshot && typeof approvalsSnapshot === "object"
716
762
  ? approvalsSnapshot.file
717
763
  : undefined;
@@ -728,6 +774,7 @@ export function createExecTool(defaults) {
728
774
  safeBins: new Set(),
729
775
  cwd: workdir,
730
776
  env,
777
+ platform: nodeInfo?.platform,
731
778
  });
732
779
  allowlistSatisfied = allowlistEval.allowlistSatisfied;
733
780
  analysisOk = allowlistEval.analysisOk;
@@ -772,7 +819,7 @@ export function createExecTool(defaults) {
772
819
  void (async () => {
773
820
  let decision = null;
774
821
  try {
775
- const decisionResult = (await callGatewayTool("exec.approval.request", { timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS }, {
822
+ const decisionResult = await callGatewayTool("exec.approval.request", { timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS }, {
776
823
  id: approvalId,
777
824
  command: commandText,
778
825
  cwd: workdir,
@@ -783,11 +830,11 @@ export function createExecTool(defaults) {
783
830
  resolvedPath: undefined,
784
831
  sessionKey: defaults?.sessionKey,
785
832
  timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
786
- }));
787
- decision =
788
- decisionResult && typeof decisionResult === "object"
789
- ? (decisionResult.decision ?? null)
790
- : null;
833
+ });
834
+ const decisionValue = decisionResult && typeof decisionResult === "object"
835
+ ? decisionResult.decision
836
+ : undefined;
837
+ decision = typeof decisionValue === "string" ? decisionValue : null;
791
838
  }
792
839
  catch {
793
840
  emitExecSystemEvent(`Exec denied (node=${nodeId} id=${approvalId}, approval-request-failed): ${commandText}`, { sessionKey: notifySessionKey, contextKey });
@@ -861,20 +908,26 @@ export function createExecTool(defaults) {
861
908
  };
862
909
  }
863
910
  const startedAt = Date.now();
864
- const raw = (await callGatewayTool("node.invoke", { timeoutMs: invokeTimeoutMs }, buildInvokeParams(false, null)));
865
- const payload = raw?.payload ?? {};
911
+ const raw = await callGatewayTool("node.invoke", { timeoutMs: invokeTimeoutMs }, buildInvokeParams(false, null));
912
+ const payload = raw && typeof raw === "object" ? raw.payload : undefined;
913
+ const payloadObj = payload && typeof payload === "object" ? payload : {};
914
+ const stdout = typeof payloadObj.stdout === "string" ? payloadObj.stdout : "";
915
+ const stderr = typeof payloadObj.stderr === "string" ? payloadObj.stderr : "";
916
+ const errorText = typeof payloadObj.error === "string" ? payloadObj.error : "";
917
+ const success = typeof payloadObj.success === "boolean" ? payloadObj.success : false;
918
+ const exitCode = typeof payloadObj.exitCode === "number" ? payloadObj.exitCode : null;
866
919
  return {
867
920
  content: [
868
921
  {
869
922
  type: "text",
870
- text: payload.stdout || payload.stderr || payload.error || "",
923
+ text: stdout || stderr || errorText || "",
871
924
  },
872
925
  ],
873
926
  details: {
874
- status: payload.success ? "completed" : "failed",
875
- exitCode: payload.exitCode ?? null,
927
+ status: success ? "completed" : "failed",
928
+ exitCode,
876
929
  durationMs: Date.now() - startedAt,
877
- aggregated: [payload.stdout, payload.stderr, payload.error].filter(Boolean).join("\n"),
930
+ aggregated: [stdout, stderr, errorText].filter(Boolean).join("\n"),
878
931
  cwd: workdir,
879
932
  },
880
933
  };
@@ -893,6 +946,7 @@ export function createExecTool(defaults) {
893
946
  safeBins,
894
947
  cwd: workdir,
895
948
  env,
949
+ platform: process.platform,
896
950
  });
897
951
  const allowlistMatches = allowlistEval.allowlistMatches;
898
952
  const analysisOk = allowlistEval.analysisOk;
@@ -916,7 +970,7 @@ export function createExecTool(defaults) {
916
970
  void (async () => {
917
971
  let decision = null;
918
972
  try {
919
- const decisionResult = (await callGatewayTool("exec.approval.request", { timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS }, {
973
+ const decisionResult = await callGatewayTool("exec.approval.request", { timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS }, {
920
974
  id: approvalId,
921
975
  command: commandText,
922
976
  cwd: workdir,
@@ -927,11 +981,11 @@ export function createExecTool(defaults) {
927
981
  resolvedPath,
928
982
  sessionKey: defaults?.sessionKey,
929
983
  timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
930
- }));
931
- decision =
932
- decisionResult && typeof decisionResult === "object"
933
- ? (decisionResult.decision ?? null)
934
- : null;
984
+ });
985
+ const decisionValue = decisionResult && typeof decisionResult === "object"
986
+ ? decisionResult.decision
987
+ : undefined;
988
+ decision = typeof decisionValue === "string" ? decisionValue : null;
935
989
  }
936
990
  catch {
937
991
  emitExecSystemEvent(`Exec denied (gateway id=${approvalId}, approval-request-failed): ${commandText}`, { sessionKey: notifySessionKey, contextKey });
@@ -1033,8 +1087,7 @@ export function createExecTool(defaults) {
1033
1087
  content: [
1034
1088
  {
1035
1089
  type: "text",
1036
- text: `${warningText}` +
1037
- `Approval required (id ${approvalSlug}). ` +
1090
+ text: `${warningText}Approval required (id ${approvalSlug}). ` +
1038
1091
  "Approve to run; updates will arrive after completion.",
1039
1092
  },
1040
1093
  ],
@@ -1099,9 +1152,7 @@ export function createExecTool(defaults) {
1099
1152
  content: [
1100
1153
  {
1101
1154
  type: "text",
1102
- text: `${getWarningText()}` +
1103
- `Command still running (session ${run.session.id}, pid ${run.session.pid ?? "n/a"}). ` +
1104
- "Use process (list/poll/log/write/kill/clear/remove) for follow-up.",
1155
+ text: `${getWarningText()}Command still running (session ${run.session.id}, pid ${run.session.pid ?? "n/a"}). Use process (list/poll/log/write/kill/clear/remove) for follow-up.`,
1105
1156
  },
1106
1157
  ],
1107
1158
  details: {
@@ -3,14 +3,13 @@ import fs from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { runExec } from "../../process/exec.js";
6
- import { buildSystemPromptParams } from "../system-prompt-params.js";
6
+ import { buildTtsSystemPromptHint } from "../../tts/tts.js";
7
+ import { escapeRegExp, isRecord } from "../../utils.js";
7
8
  import { resolveDefaultModelForAgent } from "../model-selection.js";
9
+ import { detectRuntimeShell } from "../shell-utils.js";
10
+ import { buildSystemPromptParams } from "../system-prompt-params.js";
8
11
  import { buildAgentSystemPrompt } from "../system-prompt.js";
9
- import { buildTtsSystemPromptHint } from "../../tts/tts.js";
10
12
  const CLI_RUN_QUEUE = new Map();
11
- function escapeRegex(value) {
12
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
- }
14
13
  export async function cleanupResumeProcesses(backend, sessionId) {
15
14
  if (process.platform === "win32")
16
15
  return;
@@ -25,7 +24,7 @@ export async function cleanupResumeProcesses(backend, sessionId) {
25
24
  const resumeTokens = resumeArgs.map((arg) => arg.replaceAll("{sessionId}", sessionId));
26
25
  const pattern = [commandToken, ...resumeTokens]
27
26
  .filter(Boolean)
28
- .map((token) => escapeRegex(token))
27
+ .map((token) => escapeRegExp(token))
29
28
  .join(".*");
30
29
  if (!pattern)
31
30
  return;
@@ -69,8 +68,8 @@ function buildSessionMatchers(backend) {
69
68
  }
70
69
  function tokenToRegex(token) {
71
70
  if (!token.includes("{sessionId}"))
72
- return escapeRegex(token);
73
- const parts = token.split("{sessionId}").map((part) => escapeRegex(part));
71
+ return escapeRegExp(token);
72
+ const parts = token.split("{sessionId}").map((part) => escapeRegExp(part));
74
73
  return parts.join("\\S+");
75
74
  }
76
75
  /**
@@ -158,6 +157,7 @@ export function buildSystemPrompt(params) {
158
157
  node: process.version,
159
158
  model: params.modelDisplay,
160
159
  defaultModel: defaultModelLabel,
160
+ shell: detectRuntimeShell(),
161
161
  },
162
162
  });
163
163
  const ttsHint = params.config ? buildTtsSystemPromptHint(params.config) : undefined;
@@ -177,6 +177,7 @@ export function buildSystemPrompt(params) {
177
177
  userTimeFormat,
178
178
  contextFiles: params.contextFiles,
179
179
  ttsHint,
180
+ memoryCitationsMode: params.config?.memory?.citations,
180
181
  });
181
182
  }
182
183
  export function normalizeCliModel(modelId, backend) {
@@ -203,9 +204,6 @@ function toUsage(raw) {
203
204
  return undefined;
204
205
  return { input, output, cacheRead, cacheWrite, total };
205
206
  }
206
- function isRecord(value) {
207
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
208
- }
209
207
  function collectText(value) {
210
208
  if (!value)
211
209
  return "";
@@ -5,15 +5,17 @@ export function resolveAgentIdentity(cfg, agentId) {
5
5
  }
6
6
  export function resolveAckReaction(cfg, agentId) {
7
7
  const configured = cfg.messages?.ackReaction;
8
- if (configured !== undefined)
8
+ if (configured !== undefined) {
9
9
  return configured.trim();
10
+ }
10
11
  const emoji = resolveAgentIdentity(cfg, agentId)?.emoji?.trim();
11
12
  return emoji || DEFAULT_ACK_REACTION;
12
13
  }
13
14
  export function resolveIdentityNamePrefix(cfg, agentId) {
14
15
  const name = resolveAgentIdentity(cfg, agentId)?.name?.trim();
15
- if (!name)
16
+ if (!name) {
16
17
  return undefined;
18
+ }
17
19
  return `[${name}]`;
18
20
  }
19
21
  /** Returns just the identity name (without brackets) for template context. */
@@ -22,14 +24,48 @@ export function resolveIdentityName(cfg, agentId) {
22
24
  }
23
25
  export function resolveMessagePrefix(cfg, agentId, opts) {
24
26
  const configured = opts?.configured ?? cfg.messages?.messagePrefix;
25
- if (configured !== undefined)
27
+ if (configured !== undefined) {
26
28
  return configured;
29
+ }
27
30
  const hasAllowFrom = opts?.hasAllowFrom === true;
28
- if (hasAllowFrom)
31
+ if (hasAllowFrom) {
29
32
  return "";
33
+ }
30
34
  return resolveIdentityNamePrefix(cfg, agentId) ?? opts?.fallback ?? "[poolbot]";
31
35
  }
32
- export function resolveResponsePrefix(cfg, agentId) {
36
+ /** Helper to extract a channel config value by dynamic key. */
37
+ function getChannelConfig(cfg, channel) {
38
+ const channels = cfg.channels;
39
+ const value = channels?.[channel];
40
+ return typeof value === "object" && value !== null
41
+ ? value
42
+ : undefined;
43
+ }
44
+ export function resolveResponsePrefix(cfg, agentId, opts) {
45
+ // L1: Channel account level
46
+ if (opts?.channel && opts?.accountId) {
47
+ const channelCfg = getChannelConfig(cfg, opts.channel);
48
+ const accounts = channelCfg?.accounts;
49
+ const accountPrefix = accounts?.[opts.accountId]?.responsePrefix;
50
+ if (accountPrefix !== undefined) {
51
+ if (accountPrefix === "auto") {
52
+ return resolveIdentityNamePrefix(cfg, agentId);
53
+ }
54
+ return accountPrefix;
55
+ }
56
+ }
57
+ // L2: Channel level
58
+ if (opts?.channel) {
59
+ const channelCfg = getChannelConfig(cfg, opts.channel);
60
+ const channelPrefix = channelCfg?.responsePrefix;
61
+ if (channelPrefix !== undefined) {
62
+ if (channelPrefix === "auto") {
63
+ return resolveIdentityNamePrefix(cfg, agentId);
64
+ }
65
+ return channelPrefix;
66
+ }
67
+ }
68
+ // L4: Global level
33
69
  const configured = cfg.messages?.responsePrefix;
34
70
  if (configured !== undefined) {
35
71
  if (configured === "auto") {
@@ -45,14 +81,18 @@ export function resolveEffectiveMessagesConfig(cfg, agentId, opts) {
45
81
  hasAllowFrom: opts?.hasAllowFrom,
46
82
  fallback: opts?.fallbackMessagePrefix,
47
83
  }),
48
- responsePrefix: resolveResponsePrefix(cfg, agentId),
84
+ responsePrefix: resolveResponsePrefix(cfg, agentId, {
85
+ channel: opts?.channel,
86
+ accountId: opts?.accountId,
87
+ }),
49
88
  };
50
89
  }
51
90
  export function resolveHumanDelayConfig(cfg, agentId) {
52
91
  const defaults = cfg.agents?.defaults?.humanDelay;
53
92
  const overrides = resolveAgentConfig(cfg, agentId)?.humanDelay;
54
- if (!defaults && !overrides)
93
+ if (!defaults && !overrides) {
55
94
  return undefined;
95
+ }
56
96
  return {
57
97
  mode: overrides?.mode ?? defaults?.mode,
58
98
  minMs: overrides?.minMs ?? defaults?.minMs,
@@ -5,6 +5,7 @@ import { clampInt, clampNumber, resolveUserPath } from "../utils.js";
5
5
  import { resolveAgentConfig } from "./agent-scope.js";
6
6
  const DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
7
7
  const DEFAULT_GEMINI_MODEL = "gemini-embedding-001";
8
+ const DEFAULT_VOYAGE_MODEL = "voyage-4-large";
8
9
  const DEFAULT_CHUNK_TOKENS = 400;
9
10
  const DEFAULT_CHUNK_OVERLAP = 80;
10
11
  const DEFAULT_WATCH_DEBOUNCE_MS = 1500;
@@ -22,20 +23,24 @@ function normalizeSources(sources, sessionMemoryEnabled) {
22
23
  const normalized = new Set();
23
24
  const input = sources?.length ? sources : DEFAULT_SOURCES;
24
25
  for (const source of input) {
25
- if (source === "memory")
26
+ if (source === "memory") {
26
27
  normalized.add("memory");
27
- if (source === "sessions" && sessionMemoryEnabled)
28
+ }
29
+ if (source === "sessions" && sessionMemoryEnabled) {
28
30
  normalized.add("sessions");
31
+ }
29
32
  }
30
- if (normalized.size === 0)
33
+ if (normalized.size === 0) {
31
34
  normalized.add("memory");
35
+ }
32
36
  return Array.from(normalized);
33
37
  }
34
38
  function resolveStorePath(agentId, raw) {
35
39
  const stateDir = resolveStateDir(process.env, os.homedir);
36
40
  const fallback = path.join(stateDir, "memory", `${agentId}.sqlite`);
37
- if (!raw)
41
+ if (!raw) {
38
42
  return fallback;
43
+ }
39
44
  const withToken = raw.includes("{agentId}") ? raw.replaceAll("{agentId}", agentId) : raw;
40
45
  return resolveUserPath(withToken);
41
46
  }
@@ -51,9 +56,13 @@ function mergeConfig(defaults, overrides, agentId) {
51
56
  defaultRemote?.baseUrl ||
52
57
  defaultRemote?.apiKey ||
53
58
  defaultRemote?.headers);
54
- const includeRemote = hasRemoteConfig || provider === "openai" || provider === "gemini" || provider === "auto";
59
+ const includeRemote = hasRemoteConfig ||
60
+ provider === "openai" ||
61
+ provider === "gemini" ||
62
+ provider === "voyage" ||
63
+ provider === "auto";
55
64
  const batch = {
56
- enabled: overrideRemote?.batch?.enabled ?? defaultRemote?.batch?.enabled ?? true,
65
+ enabled: overrideRemote?.batch?.enabled ?? defaultRemote?.batch?.enabled ?? false,
57
66
  wait: overrideRemote?.batch?.wait ?? defaultRemote?.batch?.wait ?? true,
58
67
  concurrency: Math.max(1, overrideRemote?.batch?.concurrency ?? defaultRemote?.batch?.concurrency ?? 2),
59
68
  pollIntervalMs: overrideRemote?.batch?.pollIntervalMs ?? defaultRemote?.batch?.pollIntervalMs ?? 2000,
@@ -72,13 +81,19 @@ function mergeConfig(defaults, overrides, agentId) {
72
81
  ? DEFAULT_GEMINI_MODEL
73
82
  : provider === "openai"
74
83
  ? DEFAULT_OPENAI_MODEL
75
- : undefined;
84
+ : provider === "voyage"
85
+ ? DEFAULT_VOYAGE_MODEL
86
+ : undefined;
76
87
  const model = overrides?.model ?? defaults?.model ?? modelDefault ?? "";
77
88
  const local = {
78
89
  modelPath: overrides?.local?.modelPath ?? defaults?.local?.modelPath,
79
90
  modelCacheDir: overrides?.local?.modelCacheDir ?? defaults?.local?.modelCacheDir,
80
91
  };
81
92
  const sources = normalizeSources(overrides?.sources ?? defaults?.sources, sessionMemory);
93
+ const rawPaths = [...(defaults?.extraPaths ?? []), ...(overrides?.extraPaths ?? [])]
94
+ .map((value) => value.trim())
95
+ .filter(Boolean);
96
+ const extraPaths = Array.from(new Set(rawPaths));
82
97
  const vector = {
83
98
  enabled: overrides?.store?.vector?.enabled ?? defaults?.store?.vector?.enabled ?? true,
84
99
  extensionPath: overrides?.store?.vector?.extensionPath ?? defaults?.store?.vector?.extensionPath,
@@ -144,6 +159,7 @@ function mergeConfig(defaults, overrides, agentId) {
144
159
  return {
145
160
  enabled,
146
161
  sources,
162
+ extraPaths,
147
163
  provider,
148
164
  remote,
149
165
  experimental: {
@@ -183,7 +199,8 @@ export function resolveMemorySearchConfig(cfg, agentId) {
183
199
  const defaults = cfg.agents?.defaults?.memorySearch;
184
200
  const overrides = resolveAgentConfig(cfg, agentId)?.memorySearch;
185
201
  const resolved = mergeConfig(defaults, overrides, agentId);
186
- if (!resolved.enabled)
202
+ if (!resolved.enabled) {
187
203
  return null;
204
+ }
188
205
  return resolved;
189
206
  }
@@ -68,6 +68,27 @@ export function parseModelRef(raw, defaultProvider) {
68
68
  const normalizedModel = normalizeProviderModelId(provider, model);
69
69
  return { provider, model: normalizedModel };
70
70
  }
71
+ export function resolveAllowlistModelKey(raw, defaultProvider) {
72
+ const parsed = parseModelRef(raw, defaultProvider);
73
+ if (!parsed) {
74
+ return null;
75
+ }
76
+ return modelKey(parsed.provider, parsed.model);
77
+ }
78
+ export function buildConfiguredAllowlistKeys(params) {
79
+ const rawAllowlist = Object.keys(params.cfg?.agents?.defaults?.models ?? {});
80
+ if (rawAllowlist.length === 0) {
81
+ return null;
82
+ }
83
+ const keys = new Set();
84
+ for (const raw of rawAllowlist) {
85
+ const key = resolveAllowlistModelKey(String(raw ?? ""), params.defaultProvider);
86
+ if (key) {
87
+ keys.add(key);
88
+ }
89
+ }
90
+ return keys.size > 0 ? keys : null;
91
+ }
71
92
  export function buildModelAliasIndex(params) {
72
93
  const byAlias = new Map();
73
94
  const byKey = new Map();