@poolzin/pool-bot 2026.2.0 → 2026.2.2

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 (258) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README-header.png +0 -0
  3. package/dist/agents/bash-tools.exec.js +76 -25
  4. package/dist/agents/cli-runner/helpers.js +9 -11
  5. package/dist/agents/context.js +1 -1
  6. package/dist/agents/identity.js +47 -7
  7. package/dist/agents/memory-search.js +25 -8
  8. package/dist/agents/model-catalog.js +1 -1
  9. package/dist/agents/model-selection.js +21 -0
  10. package/dist/agents/pi-embedded-block-chunker.js +117 -42
  11. package/dist/agents/pi-embedded-helpers/errors.js +183 -78
  12. package/dist/agents/pi-embedded-helpers.js +1 -1
  13. package/dist/agents/pi-embedded-runner/compact.js +8 -10
  14. package/dist/agents/pi-embedded-runner/model.js +62 -3
  15. package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
  16. package/dist/agents/pi-embedded-runner/run.js +199 -46
  17. package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
  18. package/dist/agents/pi-embedded-subscribe.js +118 -29
  19. package/dist/agents/pi-tools.js +10 -5
  20. package/dist/agents/poolbot-tools.js +15 -10
  21. package/dist/agents/sandbox-paths.js +31 -0
  22. package/dist/agents/session-tool-result-guard.js +94 -15
  23. package/dist/agents/shell-utils.js +51 -0
  24. package/dist/agents/skills/bundled-context.js +23 -0
  25. package/dist/agents/skills/bundled-dir.js +41 -7
  26. package/dist/agents/skills-install.js +60 -23
  27. package/dist/agents/subagent-announce.js +79 -34
  28. package/dist/agents/tool-policy.conformance.js +14 -0
  29. package/dist/agents/tool-policy.js +24 -0
  30. package/dist/agents/tools/cron-tool.js +166 -19
  31. package/dist/agents/tools/discord-actions-presence.js +78 -0
  32. package/dist/agents/tools/image-tool.js +1 -1
  33. package/dist/agents/tools/message-tool.js +56 -2
  34. package/dist/agents/tools/sessions-history-tool.js +69 -1
  35. package/dist/agents/tools/web-search.js +211 -42
  36. package/dist/agents/usage.js +23 -1
  37. package/dist/agents/workspace-run.js +67 -0
  38. package/dist/agents/workspace-templates.js +44 -0
  39. package/dist/auto-reply/command-auth.js +121 -6
  40. package/dist/auto-reply/envelope.js +74 -82
  41. package/dist/auto-reply/reply/commands-compact.js +1 -0
  42. package/dist/auto-reply/reply/commands-context-report.js +1 -0
  43. package/dist/auto-reply/reply/commands-context.js +1 -0
  44. package/dist/auto-reply/reply/commands-models.js +107 -60
  45. package/dist/auto-reply/reply/commands-ptt.js +171 -0
  46. package/dist/auto-reply/reply/get-reply-run.js +2 -1
  47. package/dist/auto-reply/reply/inbound-context.js +5 -1
  48. package/dist/auto-reply/reply/mentions.js +1 -1
  49. package/dist/auto-reply/reply/model-selection.js +3 -3
  50. package/dist/auto-reply/thinking.js +88 -43
  51. package/dist/browser/bridge-server.js +13 -0
  52. package/dist/browser/cdp.helpers.js +38 -24
  53. package/dist/browser/client-fetch.js +50 -7
  54. package/dist/browser/config.js +1 -10
  55. package/dist/browser/extension-relay.js +101 -40
  56. package/dist/browser/pw-ai.js +1 -1
  57. package/dist/browser/pw-session.js +143 -8
  58. package/dist/browser/pw-tools-core.interactions.js +125 -27
  59. package/dist/browser/pw-tools-core.responses.js +1 -1
  60. package/dist/browser/pw-tools-core.state.js +1 -1
  61. package/dist/browser/routes/agent.act.js +86 -41
  62. package/dist/browser/routes/dispatcher.js +4 -4
  63. package/dist/browser/screenshot.js +1 -1
  64. package/dist/browser/server.js +13 -0
  65. package/dist/build-info.json +3 -3
  66. package/dist/canvas-host/a2ui/index.html +28 -28
  67. package/dist/channels/reply-prefix.js +8 -1
  68. package/dist/cli/cron-cli/register.cron-add.js +61 -40
  69. package/dist/cli/cron-cli/register.cron-edit.js +60 -34
  70. package/dist/cli/cron-cli/shared.js +56 -41
  71. package/dist/cli/dns-cli.js +26 -14
  72. package/dist/cli/gateway-cli/register.js +37 -19
  73. package/dist/cli/memory-cli.js +5 -5
  74. package/dist/cli/parse-bytes.js +37 -0
  75. package/dist/cli/update-cli.js +173 -52
  76. package/dist/commands/agent.js +1 -0
  77. package/dist/commands/auth-choice.apply.oauth.js +1 -1
  78. package/dist/commands/doctor-config-flow.js +61 -5
  79. package/dist/commands/doctor-state-migrations.js +1 -1
  80. package/dist/commands/health.js +1 -1
  81. package/dist/commands/model-allowlist.js +29 -0
  82. package/dist/commands/model-picker.js +2 -1
  83. package/dist/commands/models/list.registry.js +1 -1
  84. package/dist/commands/models/list.status-command.js +43 -23
  85. package/dist/commands/models/shared.js +15 -0
  86. package/dist/commands/onboard-custom.js +384 -0
  87. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
  88. package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
  89. package/dist/commands/onboard-skills.js +63 -38
  90. package/dist/commands/openai-model-default.js +41 -0
  91. package/dist/compat/legacy-names.js +2 -0
  92. package/dist/config/defaults.js +3 -2
  93. package/dist/config/paths.js +136 -35
  94. package/dist/config/plugin-auto-enable.js +21 -5
  95. package/dist/config/redact-snapshot.js +153 -0
  96. package/dist/config/schema.field-metadata.js +590 -0
  97. package/dist/config/schema.js +2 -2
  98. package/dist/config/sessions/store.js +291 -23
  99. package/dist/config/zod-schema.agent-defaults.js +3 -0
  100. package/dist/config/zod-schema.agent-runtime.js +13 -2
  101. package/dist/config/zod-schema.providers-core.js +142 -0
  102. package/dist/config/zod-schema.session.js +3 -0
  103. package/dist/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
  104. package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
  105. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
  106. package/dist/control-ui/index.html +4 -4
  107. package/dist/cron/delivery.js +57 -0
  108. package/dist/cron/isolated-agent/delivery-target.js +18 -3
  109. package/dist/cron/isolated-agent/helpers.js +22 -5
  110. package/dist/cron/isolated-agent/run.js +172 -63
  111. package/dist/cron/isolated-agent/session.js +2 -0
  112. package/dist/cron/normalize.js +356 -28
  113. package/dist/cron/parse.js +10 -5
  114. package/dist/cron/run-log.js +35 -10
  115. package/dist/cron/schedule.js +41 -6
  116. package/dist/cron/service/jobs.js +208 -35
  117. package/dist/cron/service/ops.js +72 -16
  118. package/dist/cron/service/state.js +2 -0
  119. package/dist/cron/service/store.js +386 -14
  120. package/dist/cron/service/timer.js +390 -147
  121. package/dist/cron/session-reaper.js +86 -0
  122. package/dist/cron/store.js +23 -8
  123. package/dist/cron/validate-timestamp.js +43 -0
  124. package/dist/discord/monitor/agent-components.js +438 -0
  125. package/dist/discord/monitor/allow-list.js +28 -5
  126. package/dist/discord/monitor/gateway-registry.js +29 -0
  127. package/dist/discord/monitor/native-command.js +44 -23
  128. package/dist/discord/monitor/sender-identity.js +45 -0
  129. package/dist/discord/pluralkit.js +27 -0
  130. package/dist/discord/send.outbound.js +92 -5
  131. package/dist/discord/send.shared.js +60 -23
  132. package/dist/discord/targets.js +84 -1
  133. package/dist/entry.js +15 -9
  134. package/dist/extensionAPI.js +8 -0
  135. package/dist/gateway/control-ui.js +8 -1
  136. package/dist/gateway/hooks-mapping.js +3 -0
  137. package/dist/gateway/hooks.js +65 -0
  138. package/dist/gateway/net.js +96 -31
  139. package/dist/gateway/node-command-policy.js +50 -15
  140. package/dist/gateway/origin-check.js +56 -0
  141. package/dist/gateway/protocol/client-info.js +9 -0
  142. package/dist/gateway/protocol/index.js +9 -2
  143. package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
  144. package/dist/gateway/protocol/schema/cron.js +22 -10
  145. package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
  146. package/dist/gateway/protocol/schema/sessions.js +12 -0
  147. package/dist/gateway/server/hooks.js +1 -1
  148. package/dist/gateway/server-broadcast.js +26 -9
  149. package/dist/gateway/server-chat.js +112 -23
  150. package/dist/gateway/server-discovery-runtime.js +10 -2
  151. package/dist/gateway/server-http.js +109 -11
  152. package/dist/gateway/server-methods/agent-timestamp.js +60 -0
  153. package/dist/gateway/server-methods/agents.js +321 -2
  154. package/dist/gateway/server-methods/usage.js +559 -16
  155. package/dist/gateway/server-runtime-state.js +22 -8
  156. package/dist/gateway/server-startup-memory.js +16 -0
  157. package/dist/gateway/server.impl.js +5 -1
  158. package/dist/gateway/session-utils.fs.js +23 -25
  159. package/dist/gateway/session-utils.js +20 -10
  160. package/dist/gateway/sessions-patch.js +7 -22
  161. package/dist/gateway/test-helpers.mocks.js +11 -7
  162. package/dist/gateway/test-helpers.server.js +35 -2
  163. package/dist/imessage/constants.js +2 -0
  164. package/dist/imessage/monitor/deliver.js +4 -1
  165. package/dist/imessage/monitor/monitor-provider.js +51 -1
  166. package/dist/infra/bonjour-discovery.js +131 -70
  167. package/dist/infra/control-ui-assets.js +134 -12
  168. package/dist/infra/errors.js +12 -0
  169. package/dist/infra/exec-approvals.js +266 -57
  170. package/dist/infra/format-time/format-datetime.js +79 -0
  171. package/dist/infra/format-time/format-duration.js +81 -0
  172. package/dist/infra/format-time/format-relative.js +80 -0
  173. package/dist/infra/heartbeat-runner.js +140 -49
  174. package/dist/infra/home-dir.js +54 -0
  175. package/dist/infra/net/fetch-guard.js +122 -0
  176. package/dist/infra/net/ssrf.js +65 -29
  177. package/dist/infra/outbound/abort.js +14 -0
  178. package/dist/infra/outbound/message-action-runner.js +77 -13
  179. package/dist/infra/outbound/outbound-session.js +143 -37
  180. package/dist/infra/poolbot-root.js +43 -1
  181. package/dist/infra/session-cost-usage.js +631 -41
  182. package/dist/infra/state-migrations.js +317 -47
  183. package/dist/infra/update-global.js +35 -0
  184. package/dist/infra/update-runner.js +149 -43
  185. package/dist/infra/warning-filter.js +65 -0
  186. package/dist/infra/widearea-dns.js +30 -9
  187. package/dist/logging/redact-identifier.js +12 -0
  188. package/dist/media/fetch.js +81 -58
  189. package/dist/media/store.js +2 -0
  190. package/dist/media-understanding/apply.js +403 -3
  191. package/dist/media-understanding/attachments.js +38 -27
  192. package/dist/media-understanding/defaults.js +16 -0
  193. package/dist/media-understanding/providers/deepgram/audio.js +22 -14
  194. package/dist/media-understanding/providers/google/audio.js +24 -17
  195. package/dist/media-understanding/providers/google/video.js +24 -17
  196. package/dist/media-understanding/providers/image.js +3 -3
  197. package/dist/media-understanding/providers/index.js +4 -1
  198. package/dist/media-understanding/providers/openai/audio.js +22 -14
  199. package/dist/media-understanding/providers/shared.js +16 -11
  200. package/dist/media-understanding/providers/zai/index.js +6 -0
  201. package/dist/media-understanding/runner.js +158 -90
  202. package/dist/memory/batch-voyage.js +277 -0
  203. package/dist/memory/embeddings-voyage.js +75 -0
  204. package/dist/memory/embeddings.js +28 -16
  205. package/dist/memory/internal.js +101 -18
  206. package/dist/memory/manager.js +154 -48
  207. package/dist/memory/search-manager.js +173 -0
  208. package/dist/memory/session-files.js +9 -3
  209. package/dist/node-host/runner.js +34 -24
  210. package/dist/node-host/with-timeout.js +27 -0
  211. package/dist/plugins/commands.js +5 -1
  212. package/dist/plugins/config-state.js +86 -7
  213. package/dist/plugins/source-display.js +51 -0
  214. package/dist/process/exec.js +20 -2
  215. package/dist/routing/resolve-route.js +12 -0
  216. package/dist/routing/session-key.js +15 -0
  217. package/dist/runtime.js +2 -0
  218. package/dist/security/audit-extra.async.js +601 -0
  219. package/dist/security/audit-extra.js +2 -830
  220. package/dist/security/audit-extra.sync.js +505 -0
  221. package/dist/security/channel-metadata.js +34 -0
  222. package/dist/security/external-content.js +88 -6
  223. package/dist/security/skill-scanner.js +330 -0
  224. package/dist/sessions/session-key-utils.js +7 -0
  225. package/dist/signal/monitor/event-handler.js +80 -1
  226. package/dist/slack/monitor/media.js +85 -15
  227. package/dist/tailscale/detect.js +1 -2
  228. package/dist/telegram/bot/helpers.js +109 -28
  229. package/dist/telegram/bot-handlers.js +144 -3
  230. package/dist/telegram/bot-message-context.js +37 -10
  231. package/dist/telegram/bot-message-dispatch.js +54 -17
  232. package/dist/telegram/bot-native-commands.js +86 -29
  233. package/dist/telegram/bot.js +30 -29
  234. package/dist/telegram/model-buttons.js +163 -0
  235. package/dist/telegram/monitor.js +110 -85
  236. package/dist/telegram/send.js +129 -47
  237. package/dist/terminal/restore.js +45 -0
  238. package/dist/test-helpers/state-dir-env.js +16 -0
  239. package/dist/tts/tts.js +12 -6
  240. package/dist/tui/tui-session-actions.js +166 -54
  241. package/dist/utils/fetch-timeout.js +20 -0
  242. package/dist/utils/normalize-secret-input.js +19 -0
  243. package/dist/utils/transcript-tools.js +58 -0
  244. package/dist/utils.js +45 -14
  245. package/dist/version.js +42 -5
  246. package/dist/wizard/clack-prompter.js +9 -6
  247. package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
  248. package/extensions/googlechat/package.json +2 -2
  249. package/extensions/line/node_modules/.bin/poolbot +21 -0
  250. package/extensions/line/package.json +1 -1
  251. package/extensions/matrix/node_modules/.bin/poolbot +21 -0
  252. package/extensions/matrix/package.json +1 -1
  253. package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
  254. package/extensions/memory-core/package.json +4 -1
  255. package/extensions/twitch/node_modules/.bin/poolbot +21 -0
  256. package/extensions/twitch/package.json +1 -1
  257. package/package.json +183 -24
  258. package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { installSkill } from "../agents/skills-install.js";
2
2
  import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
3
3
  import { formatCliCommand } from "../cli/command-format.js";
4
+ import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
4
5
  import { detectBinary, resolveNodeManagerOptions } from "./onboard-helpers.js";
5
6
  function summarizeInstallFailure(message) {
6
7
  const cleaned = message.replace(/^Install failed(?:\s*\([^)]*\))?\s*:?\s*/i, "").trim();
@@ -33,14 +34,13 @@ function upsertSkillEntry(cfg, skillKey, patch) {
33
34
  export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
34
35
  const report = buildWorkspaceSkillStatus(workspaceDir, { config: cfg });
35
36
  const eligible = report.skills.filter((s) => s.eligible);
36
- const missing = report.skills.filter((s) => !s.eligible && !s.disabled && !s.blockedByAllowlist);
37
+ const unsupportedOs = report.skills.filter((s) => !s.disabled && !s.blockedByAllowlist && s.missing.os.length > 0);
38
+ const missing = report.skills.filter((s) => !s.eligible && !s.disabled && !s.blockedByAllowlist && s.missing.os.length === 0);
37
39
  const blocked = report.skills.filter((s) => s.blockedByAllowlist);
38
- const needsBrewPrompt = process.platform !== "win32" &&
39
- report.skills.some((skill) => skill.install.some((option) => option.kind === "brew")) &&
40
- !(await detectBinary("brew"));
41
40
  await prompter.note([
42
41
  `Eligible: ${eligible.length}`,
43
42
  `Missing requirements: ${missing.length}`,
43
+ `Unsupported on this OS: ${unsupportedOs.length}`,
44
44
  `Blocked by allowlist: ${blocked.length}`,
45
45
  ].join("\n"), "Skills status");
46
46
  const shouldConfigure = await prompter.confirm({
@@ -49,36 +49,7 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
49
49
  });
50
50
  if (!shouldConfigure)
51
51
  return cfg;
52
- if (needsBrewPrompt) {
53
- await prompter.note([
54
- "Many skill dependencies are shipped via Homebrew.",
55
- "Without brew, you'll need to build from source or download releases manually.",
56
- ].join("\n"), "Homebrew recommended");
57
- const showBrewInstall = await prompter.confirm({
58
- message: "Show Homebrew install command?",
59
- initialValue: true,
60
- });
61
- if (showBrewInstall) {
62
- await prompter.note([
63
- "Run:",
64
- '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
65
- ].join("\n"), "Homebrew install");
66
- }
67
- }
68
- const nodeManager = (await prompter.select({
69
- message: "Preferred node manager for skill installs",
70
- options: resolveNodeManagerOptions(),
71
- }));
72
- let next = {
73
- ...cfg,
74
- skills: {
75
- ...cfg.skills,
76
- install: {
77
- ...cfg.skills?.install,
78
- nodeManager,
79
- },
80
- },
81
- };
52
+ let next = cfg;
82
53
  const installable = missing.filter((skill) => skill.install.length > 0 && skill.missing.bins.length > 0);
83
54
  if (installable.length > 0) {
84
55
  const toInstall = await prompter.multiselect({
@@ -96,8 +67,50 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
96
67
  })),
97
68
  ],
98
69
  });
99
- const selected = toInstall.filter((name) => name !== "__skip__");
100
- for (const name of selected) {
70
+ const selectedSkills = toInstall.filter((name) => name !== "__skip__");
71
+ const needsBrewPrompt = process.platform !== "win32" &&
72
+ selectedSkills.some((name) => {
73
+ const skill = installable.find((s) => s.name === name);
74
+ return skill?.install.some((option) => option.kind === "brew");
75
+ }) &&
76
+ !(await detectBinary("brew"));
77
+ if (needsBrewPrompt) {
78
+ await prompter.note([
79
+ "Many skill dependencies are shipped via Homebrew.",
80
+ "Without brew, you'll need to build from source or download releases manually.",
81
+ ].join("\n"), "Homebrew recommended");
82
+ const showBrewInstall = await prompter.confirm({
83
+ message: "Show Homebrew install command?",
84
+ initialValue: true,
85
+ });
86
+ if (showBrewInstall) {
87
+ await prompter.note([
88
+ "Run:",
89
+ '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
90
+ ].join("\n"), "Homebrew install");
91
+ }
92
+ }
93
+ const needsNodeManagerPrompt = selectedSkills.some((name) => {
94
+ const skill = installable.find((s) => s.name === name);
95
+ return skill?.install.some((option) => option.kind === "node");
96
+ });
97
+ if (needsNodeManagerPrompt) {
98
+ const nodeManager = (await prompter.select({
99
+ message: "Preferred node manager for skill installs",
100
+ options: resolveNodeManagerOptions(),
101
+ }));
102
+ next = {
103
+ ...next,
104
+ skills: {
105
+ ...next.skills,
106
+ install: {
107
+ ...next.skills?.install,
108
+ nodeManager,
109
+ },
110
+ },
111
+ };
112
+ }
113
+ for (const name of selectedSkills) {
101
114
  const target = installable.find((s) => s.name === name);
102
115
  if (!target || target.install.length === 0)
103
116
  continue;
@@ -111,13 +124,25 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
111
124
  installId,
112
125
  config: next,
113
126
  });
127
+ const warnings = result.warnings ?? [];
114
128
  if (result.ok) {
115
- spin.stop(`Installed ${name}`);
129
+ if (warnings.length > 0) {
130
+ spin.stop(`Installed ${name} (${warnings.length} warning${warnings.length === 1 ? "" : "s"})`);
131
+ for (const warning of warnings)
132
+ runtime.log(` ⚠ ${warning}`);
133
+ }
134
+ else {
135
+ spin.stop(`Installed ${name}`);
136
+ }
116
137
  }
117
138
  else {
118
139
  const code = result.code == null ? "" : ` (exit ${result.code})`;
119
140
  const detail = summarizeInstallFailure(result.message);
120
141
  spin.stop(`Install failed: ${name}${code}${detail ? ` — ${detail}` : ""}`);
142
+ if (warnings.length > 0) {
143
+ for (const warning of warnings)
144
+ runtime.log(` ⚠ ${warning}`);
145
+ }
121
146
  if (result.stderr)
122
147
  runtime.log(result.stderr.trim());
123
148
  else if (result.stdout)
@@ -140,7 +165,7 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
140
165
  message: `Enter ${skill.primaryEnv}`,
141
166
  validate: (value) => (value?.trim() ? undefined : "Required"),
142
167
  }));
143
- next = upsertSkillEntry(next, skill.skillKey, { apiKey: apiKey.trim() });
168
+ next = upsertSkillEntry(next, skill.skillKey, { apiKey: normalizeSecretInput(apiKey) ?? "" });
144
169
  }
145
170
  return next;
146
171
  }
@@ -0,0 +1,41 @@
1
+ import { ensureModelAllowlistEntry } from "./model-allowlist.js";
2
+ export const OPENAI_DEFAULT_MODEL = "openai/gpt-5.1-codex";
3
+ export function applyOpenAIProviderConfig(cfg) {
4
+ const next = ensureModelAllowlistEntry({
5
+ cfg,
6
+ modelRef: OPENAI_DEFAULT_MODEL,
7
+ });
8
+ const models = { ...next.agents?.defaults?.models };
9
+ models[OPENAI_DEFAULT_MODEL] = {
10
+ ...models[OPENAI_DEFAULT_MODEL],
11
+ alias: models[OPENAI_DEFAULT_MODEL]?.alias ?? "GPT",
12
+ };
13
+ return {
14
+ ...next,
15
+ agents: {
16
+ ...next.agents,
17
+ defaults: {
18
+ ...next.agents?.defaults,
19
+ models,
20
+ },
21
+ },
22
+ };
23
+ }
24
+ export function applyOpenAIConfig(cfg) {
25
+ const next = applyOpenAIProviderConfig(cfg);
26
+ return {
27
+ ...next,
28
+ agents: {
29
+ ...next.agents,
30
+ defaults: {
31
+ ...next.agents?.defaults,
32
+ model: next.agents?.defaults?.model && typeof next.agents.defaults.model === "object"
33
+ ? {
34
+ ...next.agents.defaults.model,
35
+ primary: OPENAI_DEFAULT_MODEL,
36
+ }
37
+ : { primary: OPENAI_DEFAULT_MODEL },
38
+ },
39
+ },
40
+ };
41
+ }
@@ -3,3 +3,5 @@ export const LEGACY_MANIFEST_KEY = LEGACY_PROJECT_NAME;
3
3
  export const LEGACY_PLUGIN_MANIFEST_FILENAME = `${LEGACY_PROJECT_NAME}.plugin.json`;
4
4
  export const LEGACY_CANVAS_HANDLER_NAME = `${LEGACY_PROJECT_NAME}CanvasA2UIAction`;
5
5
  export const LEGACY_MACOS_APP_SOURCES_DIR = "apps/macos/Sources/Pool-Bot";
6
+ // Alias used by conformance tests; points to the actual macOS app sources directory
7
+ export const MACOS_APP_SOURCES_DIR = "apps/macos/Sources/Clawdbot";
@@ -5,7 +5,7 @@ import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from ".
5
5
  let defaultWarnState = { warned: false };
6
6
  const DEFAULT_MODEL_ALIASES = {
7
7
  // Anthropic (pi-ai catalog uses "latest" ids without date suffix)
8
- opus: "anthropic/claude-opus-4-5",
8
+ opus: "anthropic/claude-opus-4-6",
9
9
  sonnet: "anthropic/claude-sonnet-4-5",
10
10
  // OpenAI
11
11
  gpt: "openai/gpt-5.2",
@@ -145,7 +145,8 @@ export function applyModelDefaults(cfg) {
145
145
  if (raw.contextWindow !== contextWindow)
146
146
  modelMutated = true;
147
147
  const defaultMaxTokens = Math.min(DEFAULT_MODEL_MAX_TOKENS, contextWindow);
148
- const maxTokens = isPositiveNumber(raw.maxTokens) ? raw.maxTokens : defaultMaxTokens;
148
+ const rawMaxTokens = isPositiveNumber(raw.maxTokens) ? raw.maxTokens : defaultMaxTokens;
149
+ const maxTokens = Math.min(rawMaxTokens, contextWindow);
149
150
  if (raw.maxTokens !== maxTokens)
150
151
  modelMutated = true;
151
152
  if (!modelMutated)
@@ -1,5 +1,7 @@
1
+ import fs from "node:fs";
1
2
  import os from "node:os";
2
3
  import path from "node:path";
4
+ import { expandHomePrefix, resolveRequiredHomeDir } from "../infra/home-dir.js";
3
5
  /**
4
6
  * Nix mode detection: When CLAWDBOT_NIX_MODE=1, the gateway is running under Nix.
5
7
  * In this mode:
@@ -11,32 +13,73 @@ export function resolveIsNixMode(env = process.env) {
11
13
  return env.CLAWDBOT_NIX_MODE === "1";
12
14
  }
13
15
  export const isNixMode = resolveIsNixMode();
14
- const LEGACY_STATE_DIRNAME = ".poolbot";
16
+ const LEGACY_STATE_DIRNAMES = [".clawdbot", ".moltbot", ".moldbot"];
15
17
  const NEW_STATE_DIRNAME = ".poolbot";
16
18
  const CONFIG_FILENAME = "poolbot.json";
17
- function legacyStateDir(homedir = os.homedir) {
18
- return path.join(homedir(), LEGACY_STATE_DIRNAME);
19
+ const LEGACY_CONFIG_FILENAMES = ["clawdbot.json", "moltbot.json", "moldbot.json"];
20
+ function resolveDefaultHomeDir() {
21
+ return resolveRequiredHomeDir(process.env, os.homedir);
19
22
  }
20
- function newStateDir(homedir = os.homedir) {
23
+ /** Build a homedir thunk that respects CLAWDBOT_HOME for the given env. */
24
+ function envHomedir(env) {
25
+ return () => resolveRequiredHomeDir(env, os.homedir);
26
+ }
27
+ function legacyStateDirs(homedir = resolveDefaultHomeDir) {
28
+ return LEGACY_STATE_DIRNAMES.map((dir) => path.join(homedir(), dir));
29
+ }
30
+ function newStateDir(homedir = resolveDefaultHomeDir) {
21
31
  return path.join(homedir(), NEW_STATE_DIRNAME);
22
32
  }
33
+ export function resolveLegacyStateDir(homedir = resolveDefaultHomeDir) {
34
+ return legacyStateDirs(homedir)[0] ?? newStateDir(homedir);
35
+ }
36
+ export function resolveLegacyStateDirs(homedir = resolveDefaultHomeDir) {
37
+ return legacyStateDirs(homedir);
38
+ }
39
+ export function resolveNewStateDir(homedir = resolveDefaultHomeDir) {
40
+ return newStateDir(homedir);
41
+ }
23
42
  /**
24
43
  * State directory for mutable data (sessions, logs, caches).
25
44
  * Can be overridden via MOLTBOT_STATE_DIR (preferred) or CLAWDBOT_STATE_DIR (legacy).
26
- * Default: ~/.poolbot (legacy default for compatibility)
45
+ * Default: ~/.poolbot
27
46
  */
28
- export function resolveStateDir(env = process.env, homedir = os.homedir) {
47
+ export function resolveStateDir(env = process.env, homedir = envHomedir(env)) {
48
+ const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
29
49
  const override = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
30
- if (override)
31
- return resolveUserPath(override);
32
- return legacyStateDir(homedir);
50
+ if (override) {
51
+ return resolveUserPath(override, env, effectiveHomedir);
52
+ }
53
+ const newDir = newStateDir(effectiveHomedir);
54
+ const legacyDirs = legacyStateDirs(effectiveHomedir);
55
+ const hasNew = fs.existsSync(newDir);
56
+ if (hasNew) {
57
+ return newDir;
58
+ }
59
+ const existingLegacy = legacyDirs.find((dir) => {
60
+ try {
61
+ return fs.existsSync(dir);
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ });
67
+ if (existingLegacy) {
68
+ return existingLegacy;
69
+ }
70
+ return newDir;
33
71
  }
34
- function resolveUserPath(input) {
72
+ function resolveUserPath(input, env = process.env, homedir = envHomedir(env)) {
35
73
  const trimmed = input.trim();
36
- if (!trimmed)
74
+ if (!trimmed) {
37
75
  return trimmed;
76
+ }
38
77
  if (trimmed.startsWith("~")) {
39
- const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir());
78
+ const expanded = expandHomePrefix(trimmed, {
79
+ home: resolveRequiredHomeDir(env, homedir),
80
+ env,
81
+ homedir,
82
+ });
40
83
  return path.resolve(expanded);
41
84
  }
42
85
  return path.resolve(trimmed);
@@ -47,32 +90,88 @@ export const STATE_DIR = resolveStateDir();
47
90
  * Can be overridden via MOLTBOT_CONFIG_PATH (preferred) or CLAWDBOT_CONFIG_PATH (legacy).
48
91
  * Default: ~/.poolbot/poolbot.json (or $*_STATE_DIR/poolbot.json)
49
92
  */
50
- export function resolveConfigPath(env = process.env, stateDir = resolveStateDir(env, os.homedir)) {
93
+ export function resolveCanonicalConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
51
94
  const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
52
- if (override)
53
- return resolveUserPath(override);
95
+ if (override) {
96
+ return resolveUserPath(override, env, envHomedir(env));
97
+ }
54
98
  return path.join(stateDir, CONFIG_FILENAME);
55
99
  }
56
- export const CONFIG_PATH = resolveConfigPath();
57
100
  /**
58
- * Resolve default config path candidates across new + legacy locations.
59
- * Order: explicit config path state-dir-derived paths → new default → legacy default.
101
+ * Resolve the active config path by preferring existing config candidates
102
+ * before falling back to the canonical path.
60
103
  */
61
- export function resolveDefaultConfigCandidates(env = process.env, homedir = os.homedir) {
104
+ export function resolveConfigPathCandidate(env = process.env, homedir = envHomedir(env)) {
105
+ const candidates = resolveDefaultConfigCandidates(env, homedir);
106
+ const existing = candidates.find((candidate) => {
107
+ try {
108
+ return fs.existsSync(candidate);
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ });
114
+ if (existing) {
115
+ return existing;
116
+ }
117
+ return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
118
+ }
119
+ /**
120
+ * Active config path (prefers existing config files).
121
+ */
122
+ export function resolveConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env)), homedir = envHomedir(env)) {
123
+ const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
124
+ if (override) {
125
+ return resolveUserPath(override, env, homedir);
126
+ }
127
+ const stateOverride = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
128
+ const candidates = [
129
+ path.join(stateDir, CONFIG_FILENAME),
130
+ ...LEGACY_CONFIG_FILENAMES.map((name) => path.join(stateDir, name)),
131
+ ];
132
+ const existing = candidates.find((candidate) => {
133
+ try {
134
+ return fs.existsSync(candidate);
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ });
140
+ if (existing) {
141
+ return existing;
142
+ }
143
+ if (stateOverride) {
144
+ return path.join(stateDir, CONFIG_FILENAME);
145
+ }
146
+ const defaultStateDir = resolveStateDir(env, homedir);
147
+ if (path.resolve(stateDir) === path.resolve(defaultStateDir)) {
148
+ return resolveConfigPathCandidate(env, homedir);
149
+ }
150
+ return path.join(stateDir, CONFIG_FILENAME);
151
+ }
152
+ export const CONFIG_PATH = resolveConfigPathCandidate();
153
+ /**
154
+ * Resolve default config path candidates across default locations.
155
+ * Order: explicit config path → state-dir-derived paths → new default.
156
+ */
157
+ export function resolveDefaultConfigCandidates(env = process.env, homedir = envHomedir(env)) {
158
+ const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
62
159
  const explicit = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
63
- if (explicit)
64
- return [resolveUserPath(explicit)];
160
+ if (explicit) {
161
+ return [resolveUserPath(explicit, env, effectiveHomedir)];
162
+ }
65
163
  const candidates = [];
66
- const poolbotStateDir = env.MOLTBOT_STATE_DIR?.trim();
164
+ const poolbotStateDir = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
67
165
  if (poolbotStateDir) {
68
- candidates.push(path.join(resolveUserPath(poolbotStateDir), CONFIG_FILENAME));
166
+ const resolved = resolveUserPath(poolbotStateDir, env, effectiveHomedir);
167
+ candidates.push(path.join(resolved, CONFIG_FILENAME));
168
+ candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(resolved, name)));
69
169
  }
70
- const legacyStateDirOverride = env.CLAWDBOT_STATE_DIR?.trim();
71
- if (legacyStateDirOverride) {
72
- candidates.push(path.join(resolveUserPath(legacyStateDirOverride), CONFIG_FILENAME));
170
+ const defaultDirs = [newStateDir(effectiveHomedir), ...legacyStateDirs(effectiveHomedir)];
171
+ for (const dir of defaultDirs) {
172
+ candidates.push(path.join(dir, CONFIG_FILENAME));
173
+ candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(dir, name)));
73
174
  }
74
- candidates.push(path.join(newStateDir(homedir), CONFIG_FILENAME));
75
- candidates.push(path.join(legacyStateDir(homedir), CONFIG_FILENAME));
76
175
  return candidates;
77
176
  }
78
177
  export const DEFAULT_GATEWAY_PORT = 18789;
@@ -93,28 +192,30 @@ const OAUTH_FILENAME = "oauth.json";
93
192
  * Precedence:
94
193
  * - `CLAWDBOT_OAUTH_DIR` (explicit override)
95
194
  * - `$*_STATE_DIR/credentials` (canonical server/default)
96
- * - `~/.poolbot/credentials` (legacy default)
97
195
  */
98
- export function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, os.homedir)) {
196
+ export function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
99
197
  const override = env.CLAWDBOT_OAUTH_DIR?.trim();
100
- if (override)
101
- return resolveUserPath(override);
198
+ if (override) {
199
+ return resolveUserPath(override, env, envHomedir(env));
200
+ }
102
201
  return path.join(stateDir, "credentials");
103
202
  }
104
- export function resolveOAuthPath(env = process.env, stateDir = resolveStateDir(env, os.homedir)) {
203
+ export function resolveOAuthPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
105
204
  return path.join(resolveOAuthDir(env, stateDir), OAUTH_FILENAME);
106
205
  }
107
206
  export function resolveGatewayPort(cfg, env = process.env) {
108
207
  const envRaw = env.CLAWDBOT_GATEWAY_PORT?.trim();
109
208
  if (envRaw) {
110
209
  const parsed = Number.parseInt(envRaw, 10);
111
- if (Number.isFinite(parsed) && parsed > 0)
210
+ if (Number.isFinite(parsed) && parsed > 0) {
112
211
  return parsed;
212
+ }
113
213
  }
114
214
  const configPort = cfg?.gateway?.port;
115
215
  if (typeof configPort === "number" && Number.isFinite(configPort)) {
116
- if (configPort > 0)
216
+ if (configPort > 0) {
117
217
  return configPort;
218
+ }
118
219
  }
119
220
  return DEFAULT_GATEWAY_PORT;
120
221
  }
@@ -1,6 +1,7 @@
1
- import { getChatChannelMeta, listChatChannels, normalizeChatChannelId, } from "../channels/registry.js";
2
- import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries, } from "../channels/plugins/catalog.js";
3
1
  import { normalizeProviderId } from "../agents/model-selection.js";
2
+ import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries, } from "../channels/plugins/catalog.js";
3
+ import { getChatChannelMeta, listChatChannels, normalizeChatChannelId, } from "../channels/registry.js";
4
+ import { isRecord } from "../utils.js";
4
5
  import { hasAnyWhatsAppAuth } from "../web/accounts.js";
5
6
  const CHANNEL_PLUGIN_IDS = Array.from(new Set([
6
7
  ...listChatChannels().map((meta) => meta.id),
@@ -11,10 +12,8 @@ const PROVIDER_PLUGIN_IDS = [
11
12
  { pluginId: "google-gemini-cli-auth", providerId: "google-gemini-cli" },
12
13
  { pluginId: "qwen-portal-auth", providerId: "qwen-portal" },
13
14
  { pluginId: "copilot-proxy", providerId: "copilot-proxy" },
15
+ { pluginId: "minimax-portal-auth", providerId: "minimax-portal" },
14
16
  ];
15
- function isRecord(value) {
16
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
17
- }
18
17
  function hasNonEmptyString(value) {
19
18
  return typeof value === "string" && value.trim().length > 0;
20
19
  }
@@ -63,6 +62,21 @@ function isDiscordConfigured(cfg, env) {
63
62
  return true;
64
63
  return recordHasKeys(entry);
65
64
  }
65
+ function isIrcConfigured(cfg, env) {
66
+ if (hasNonEmptyString(env.IRC_HOST) && hasNonEmptyString(env.IRC_NICK)) {
67
+ return true;
68
+ }
69
+ const entry = resolveChannelConfig(cfg, "irc");
70
+ if (!entry)
71
+ return false;
72
+ if (hasNonEmptyString(entry.host) || hasNonEmptyString(entry.nick)) {
73
+ return true;
74
+ }
75
+ if (accountsHaveKeys(entry.accounts, ["host", "nick"])) {
76
+ return true;
77
+ }
78
+ return recordHasKeys(entry);
79
+ }
66
80
  function isSlackConfigured(cfg, env) {
67
81
  if (hasNonEmptyString(env.SLACK_BOT_TOKEN) ||
68
82
  hasNonEmptyString(env.SLACK_APP_TOKEN) ||
@@ -124,6 +138,8 @@ export function isChannelConfigured(cfg, channelId, env = process.env) {
124
138
  return isTelegramConfigured(cfg, env);
125
139
  case "discord":
126
140
  return isDiscordConfigured(cfg, env);
141
+ case "irc":
142
+ return isIrcConfigured(cfg, env);
127
143
  case "slack":
128
144
  return isSlackConfigured(cfg, env);
129
145
  case "signal":
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Sentinel value used to replace sensitive config fields in gateway responses.
3
+ * Write-side handlers (config.set, config.apply, config.patch) detect this
4
+ * sentinel and restore the original value from the on-disk config, so a
5
+ * round-trip through the Web UI does not corrupt credentials.
6
+ */
7
+ export const REDACTED_SENTINEL = "__POOLBOT_REDACTED__";
8
+ /**
9
+ * Patterns that identify sensitive config field names.
10
+ * Aligned with the UI-hint logic in schema.ts.
11
+ */
12
+ const SENSITIVE_KEY_PATTERNS = [/token/i, /password/i, /secret/i, /api.?key/i];
13
+ function isSensitiveKey(key) {
14
+ return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key));
15
+ }
16
+ /**
17
+ * Deep-walk an object and replace values whose key matches a sensitive pattern
18
+ * with the redaction sentinel.
19
+ */
20
+ function redactObject(obj) {
21
+ if (obj === null || obj === undefined) {
22
+ return obj;
23
+ }
24
+ if (typeof obj !== "object") {
25
+ return obj;
26
+ }
27
+ if (Array.isArray(obj)) {
28
+ return obj.map(redactObject);
29
+ }
30
+ const result = {};
31
+ for (const [key, value] of Object.entries(obj)) {
32
+ if (isSensitiveKey(key) && value !== null && value !== undefined) {
33
+ result[key] = REDACTED_SENTINEL;
34
+ }
35
+ else if (typeof value === "object" && value !== null) {
36
+ result[key] = redactObject(value);
37
+ }
38
+ else {
39
+ result[key] = value;
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+ export function redactConfigObject(value) {
45
+ return redactObject(value);
46
+ }
47
+ /**
48
+ * Collect all sensitive string values from a config object.
49
+ * Used for text-based redaction of the raw JSON5 source.
50
+ */
51
+ function collectSensitiveValues(obj) {
52
+ const values = [];
53
+ if (obj === null || obj === undefined || typeof obj !== "object") {
54
+ return values;
55
+ }
56
+ if (Array.isArray(obj)) {
57
+ for (const item of obj) {
58
+ values.push(...collectSensitiveValues(item));
59
+ }
60
+ return values;
61
+ }
62
+ for (const [key, value] of Object.entries(obj)) {
63
+ if (isSensitiveKey(key) && typeof value === "string" && value.length > 0) {
64
+ values.push(value);
65
+ }
66
+ else if (typeof value === "object" && value !== null) {
67
+ values.push(...collectSensitiveValues(value));
68
+ }
69
+ }
70
+ return values;
71
+ }
72
+ /**
73
+ * Replace known sensitive values in a raw JSON5 string with the sentinel.
74
+ * Values are replaced longest-first to avoid partial matches.
75
+ */
76
+ function redactRawText(raw, config) {
77
+ const sensitiveValues = collectSensitiveValues(config);
78
+ sensitiveValues.sort((a, b) => b.length - a.length);
79
+ let result = raw;
80
+ for (const value of sensitiveValues) {
81
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
82
+ result = result.replace(new RegExp(escaped, "g"), REDACTED_SENTINEL);
83
+ }
84
+ const keyValuePattern = /(^|[{\s,])((["'])([^"']+)\3|([A-Za-z0-9_$.-]+))(\s*:\s*)(["'])([^"']*)\7/g;
85
+ result = result.replace(keyValuePattern, (match, prefix, keyExpr, _keyQuote, keyQuoted, keyBare, sep, valQuote, val) => {
86
+ const key = (keyQuoted ?? keyBare);
87
+ if (!key || !isSensitiveKey(key)) {
88
+ return match;
89
+ }
90
+ if (val === REDACTED_SENTINEL) {
91
+ return match;
92
+ }
93
+ return `${prefix}${keyExpr}${sep}${valQuote}${REDACTED_SENTINEL}${valQuote}`;
94
+ });
95
+ return result;
96
+ }
97
+ /**
98
+ * Returns a copy of the config snapshot with all sensitive fields
99
+ * replaced by {@link REDACTED_SENTINEL}. The `hash` is preserved
100
+ * (it tracks config identity, not content).
101
+ *
102
+ * Both `config` (the parsed object) and `raw` (the JSON5 source) are scrubbed
103
+ * so no credential can leak through either path.
104
+ */
105
+ export function redactConfigSnapshot(snapshot) {
106
+ const redactedConfig = redactConfigObject(snapshot.config);
107
+ const redactedRaw = snapshot.raw ? redactRawText(snapshot.raw, snapshot.config) : null;
108
+ const redactedParsed = snapshot.parsed ? redactConfigObject(snapshot.parsed) : snapshot.parsed;
109
+ return {
110
+ ...snapshot,
111
+ config: redactedConfig,
112
+ raw: redactedRaw,
113
+ parsed: redactedParsed,
114
+ };
115
+ }
116
+ /**
117
+ * Deep-walk `incoming` and replace any {@link REDACTED_SENTINEL} values
118
+ * (on sensitive keys) with the corresponding value from `original`.
119
+ *
120
+ * This is called by config.set / config.apply / config.patch before writing,
121
+ * so that credentials survive a Web UI round-trip unmodified.
122
+ */
123
+ export function restoreRedactedValues(incoming, original) {
124
+ if (incoming === null || incoming === undefined) {
125
+ return incoming;
126
+ }
127
+ if (typeof incoming !== "object") {
128
+ return incoming;
129
+ }
130
+ if (Array.isArray(incoming)) {
131
+ const origArr = Array.isArray(original) ? original : [];
132
+ return incoming.map((item, i) => restoreRedactedValues(item, origArr[i]));
133
+ }
134
+ const orig = original && typeof original === "object" && !Array.isArray(original)
135
+ ? original
136
+ : {};
137
+ const result = {};
138
+ for (const [key, value] of Object.entries(incoming)) {
139
+ if (isSensitiveKey(key) && value === REDACTED_SENTINEL) {
140
+ if (!(key in orig)) {
141
+ throw new Error(`config write rejected: "${key}" is redacted; set an explicit value instead of ${REDACTED_SENTINEL}`);
142
+ }
143
+ result[key] = orig[key];
144
+ }
145
+ else if (typeof value === "object" && value !== null) {
146
+ result[key] = restoreRedactedValues(value, orig[key]);
147
+ }
148
+ else {
149
+ result[key] = value;
150
+ }
151
+ }
152
+ return result;
153
+ }