@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
@@ -1,38 +1,49 @@
1
1
  import path from "node:path";
2
2
  import { resolvePoolbotAgentDir } from "../../agents/agent-paths.js";
3
+ import { resolveAgentDir, resolveAgentModelFallbacksOverride, resolveAgentModelPrimary, } from "../../agents/agent-scope.js";
3
4
  import { buildAuthHealthSummary, DEFAULT_OAUTH_WARN_MS, formatRemainingShort, } from "../../agents/auth-health.js";
4
5
  import { ensureAuthProfileStore, resolveAuthStorePathForDisplay, resolveProfileUnusableUntilForDisplay, } from "../../agents/auth-profiles.js";
5
6
  import { resolveEnvApiKey } from "../../agents/model-auth.js";
6
- import { buildModelAliasIndex, parseModelRef, resolveConfiguredModelRef, resolveModelRefFromString, } from "../../agents/model-selection.js";
7
- import { CONFIG_PATH, loadConfig } from "../../config/config.js";
8
- import { getShellEnvAppliedKeys, shouldEnableShellEnvFallback } from "../../infra/shell-env.js";
7
+ import { buildModelAliasIndex, parseModelRef, resolveConfiguredModelRef, resolveDefaultModelForAgent, resolveModelRefFromString, } from "../../agents/model-selection.js";
8
+ import { formatCliCommand } from "../../cli/command-format.js";
9
9
  import { withProgressTotals } from "../../cli/progress.js";
10
+ import { CONFIG_PATH, loadConfig } from "../../config/config.js";
10
11
  import { formatUsageWindowSummary, loadProviderUsageSummary, resolveUsageProviderId, } from "../../infra/provider-usage.js";
11
- import { colorize, theme } from "../../terminal/theme.js";
12
+ import { getShellEnvAppliedKeys, shouldEnableShellEnvFallback } from "../../infra/shell-env.js";
12
13
  import { renderTable } from "../../terminal/table.js";
13
- import { formatCliCommand } from "../../cli/command-format.js";
14
+ import { colorize, theme } from "../../terminal/theme.js";
14
15
  import { shortenHomePath } from "../../utils.js";
15
16
  import { resolveProviderAuthOverview } from "./list.auth-overview.js";
16
17
  import { isRich } from "./list.format.js";
17
18
  import { describeProbeSummary, formatProbeLatency, runAuthProbes, sortProbeResults, } from "./list.probe.js";
18
- import { DEFAULT_MODEL, DEFAULT_PROVIDER, ensureFlagCompatibility } from "./shared.js";
19
+ import { DEFAULT_MODEL, DEFAULT_PROVIDER, ensureFlagCompatibility, resolveKnownAgentId, } from "./shared.js";
19
20
  export async function modelsStatusCommand(opts, runtime) {
20
21
  ensureFlagCompatibility(opts);
21
22
  if (opts.plain && opts.probe) {
22
23
  throw new Error("--probe cannot be used with --plain output.");
23
24
  }
24
25
  const cfg = loadConfig();
25
- const resolved = resolveConfiguredModelRef({
26
- cfg,
27
- defaultProvider: DEFAULT_PROVIDER,
28
- defaultModel: DEFAULT_MODEL,
29
- });
26
+ const agentId = resolveKnownAgentId({ cfg, rawAgentId: opts.agent });
27
+ const agentDir = agentId ? resolveAgentDir(cfg, agentId) : resolvePoolbotAgentDir();
28
+ const agentModelPrimary = agentId ? resolveAgentModelPrimary(cfg, agentId) : undefined;
29
+ const agentFallbacksOverride = agentId
30
+ ? resolveAgentModelFallbacksOverride(cfg, agentId)
31
+ : undefined;
32
+ const resolved = agentId
33
+ ? resolveDefaultModelForAgent({ cfg, agentId })
34
+ : resolveConfiguredModelRef({
35
+ cfg,
36
+ defaultProvider: DEFAULT_PROVIDER,
37
+ defaultModel: DEFAULT_MODEL,
38
+ });
30
39
  const modelConfig = cfg.agents?.defaults?.model;
31
40
  const imageConfig = cfg.agents?.defaults?.imageModel;
32
- const rawModel = typeof modelConfig === "string" ? modelConfig.trim() : (modelConfig?.primary?.trim() ?? "");
41
+ const rawDefaultsModel = typeof modelConfig === "string" ? modelConfig.trim() : (modelConfig?.primary?.trim() ?? "");
42
+ const rawModel = agentModelPrimary ?? rawDefaultsModel;
33
43
  const resolvedLabel = `${resolved.provider}/${resolved.model}`;
34
44
  const defaultLabel = rawModel || resolvedLabel;
35
- const fallbacks = typeof modelConfig === "object" ? (modelConfig?.fallbacks ?? []) : [];
45
+ const defaultsFallbacks = typeof modelConfig === "object" ? (modelConfig?.fallbacks ?? []) : [];
46
+ const fallbacks = agentFallbacksOverride ?? defaultsFallbacks;
36
47
  const imageModel = typeof imageConfig === "string" ? imageConfig.trim() : (imageConfig?.primary?.trim() ?? "");
37
48
  const imageFallbacks = typeof imageConfig === "object" ? (imageConfig?.fallbacks ?? []) : [];
38
49
  const aliases = Object.entries(cfg.agents?.defaults?.models ?? {}).reduce((acc, [key, entry]) => {
@@ -42,8 +53,7 @@ export async function modelsStatusCommand(opts, runtime) {
42
53
  return acc;
43
54
  }, {});
44
55
  const allowed = Object.keys(cfg.agents?.defaults?.models ?? {});
45
- const agentDir = resolvePoolbotAgentDir();
46
- const store = ensureAuthProfileStore();
56
+ const store = ensureAuthProfileStore(agentDir);
47
57
  const modelsPath = path.join(agentDir, "models.json");
48
58
  const providersFromStore = new Set(Object.values(store.profiles)
49
59
  .map((profile) => profile.provider)
@@ -92,7 +102,7 @@ export async function modelsStatusCommand(opts, runtime) {
92
102
  ]))
93
103
  .map((p) => p.trim())
94
104
  .filter(Boolean)
95
- .sort((a, b) => a.localeCompare(b));
105
+ .toSorted((a, b) => a.localeCompare(b));
96
106
  const applied = getShellEnvAppliedKeys();
97
107
  const shellFallbackEnabled = shouldEnableShellEnvFallback(process.env) || cfg.env?.shellEnv?.enabled === true;
98
108
  const providerAuth = providers
@@ -104,7 +114,7 @@ export async function modelsStatusCommand(opts, runtime) {
104
114
  const providerAuthMap = new Map(providerAuth.map((entry) => [entry.provider, entry]));
105
115
  const missingProvidersInUse = Array.from(providersInUse)
106
116
  .filter((provider) => !providerAuthMap.has(provider))
107
- .sort((a, b) => a.localeCompare(b));
117
+ .toSorted((a, b) => a.localeCompare(b));
108
118
  const probeProfileIds = (() => {
109
119
  if (!opts.probeProfile)
110
120
  return [];
@@ -193,7 +203,7 @@ export async function modelsStatusCommand(opts, runtime) {
193
203
  remainingMs: unusableUntil - now,
194
204
  });
195
205
  }
196
- return out.sort((a, b) => a.remainingMs - b.remainingMs);
206
+ return out.toSorted((a, b) => a.remainingMs - b.remainingMs);
197
207
  })();
198
208
  const checkStatus = (() => {
199
209
  const hasExpiredOrMissing = oauthProfiles.some((profile) => ["expired", "missing"].includes(profile.status)) ||
@@ -208,16 +218,25 @@ export async function modelsStatusCommand(opts, runtime) {
208
218
  if (opts.json) {
209
219
  runtime.log(JSON.stringify({
210
220
  configPath: CONFIG_PATH,
221
+ ...(agentId ? { agentId } : {}),
211
222
  agentDir,
212
223
  defaultModel: defaultLabel,
213
224
  resolvedDefault: resolvedLabel,
214
225
  fallbacks,
215
226
  imageModel: imageModel || null,
216
227
  imageFallbacks,
228
+ ...(agentId
229
+ ? {
230
+ modelConfig: {
231
+ defaultSource: agentModelPrimary ? "agent" : "defaults",
232
+ fallbacksSource: agentFallbacksOverride !== undefined ? "agent" : "defaults",
233
+ },
234
+ }
235
+ : {}),
217
236
  aliases,
218
237
  allowed,
219
238
  auth: {
220
- storePath: resolveAuthStorePathForDisplay(),
239
+ storePath: resolveAuthStorePathForDisplay(agentDir),
221
240
  shellEnvFallback: {
222
241
  enabled: shellFallbackEnabled,
223
242
  appliedKeys: applied,
@@ -246,13 +265,14 @@ export async function modelsStatusCommand(opts, runtime) {
246
265
  }
247
266
  const rich = isRich(opts);
248
267
  const label = (value) => colorize(rich, theme.accent, value.padEnd(14));
268
+ const labelWithSource = (value, source) => label(source ? `${value} (${source})` : value);
249
269
  const displayDefault = rawModel && rawModel !== resolvedLabel ? `${resolvedLabel} (from ${rawModel})` : resolvedLabel;
250
270
  runtime.log(`${label("Config")}${colorize(rich, theme.muted, ":")} ${colorize(rich, theme.info, shortenHomePath(CONFIG_PATH))}`);
251
271
  runtime.log(`${label("Agent dir")}${colorize(rich, theme.muted, ":")} ${colorize(rich, theme.info, shortenHomePath(agentDir))}`);
252
- runtime.log(`${label("Default")}${colorize(rich, theme.muted, ":")} ${colorize(rich, theme.success, displayDefault)}`);
253
- runtime.log(`${label(`Fallbacks (${fallbacks.length || 0})`)}${colorize(rich, theme.muted, ":")} ${colorize(rich, fallbacks.length ? theme.warn : theme.muted, fallbacks.length ? fallbacks.join(", ") : "-")}`);
254
- runtime.log(`${label("Image model")}${colorize(rich, theme.muted, ":")} ${colorize(rich, imageModel ? theme.accentBright : theme.muted, imageModel || "-")}`);
255
- runtime.log(`${label(`Image fallbacks (${imageFallbacks.length || 0})`)}${colorize(rich, theme.muted, ":")} ${colorize(rich, imageFallbacks.length ? theme.accentBright : theme.muted, imageFallbacks.length ? imageFallbacks.join(", ") : "-")}`);
272
+ runtime.log(`${labelWithSource("Default", agentId ? (agentModelPrimary ? "agent" : "defaults") : undefined)}${colorize(rich, theme.muted, ":")} ${colorize(rich, theme.success, displayDefault)}`);
273
+ runtime.log(`${labelWithSource(`Fallbacks (${fallbacks.length || 0})`, agentId ? (agentFallbacksOverride !== undefined ? "agent" : "defaults") : undefined)}${colorize(rich, theme.muted, ":")} ${colorize(rich, fallbacks.length ? theme.warn : theme.muted, fallbacks.length ? fallbacks.join(", ") : "-")}`);
274
+ runtime.log(`${labelWithSource("Image model", agentId ? "defaults" : undefined)}${colorize(rich, theme.muted, ":")} ${colorize(rich, imageModel ? theme.accentBright : theme.muted, imageModel || "-")}`);
275
+ runtime.log(`${labelWithSource(`Image fallbacks (${imageFallbacks.length || 0})`, agentId ? "defaults" : undefined)}${colorize(rich, theme.muted, ":")} ${colorize(rich, imageFallbacks.length ? theme.accentBright : theme.muted, imageFallbacks.length ? imageFallbacks.join(", ") : "-")}`);
256
276
  runtime.log(`${label(`Aliases (${Object.keys(aliases).length || 0})`)}${colorize(rich, theme.muted, ":")} ${colorize(rich, Object.keys(aliases).length ? theme.accent : theme.muted, Object.keys(aliases).length
257
277
  ? Object.entries(aliases)
258
278
  .map(([alias, target]) => rich
@@ -1,6 +1,9 @@
1
+ import { listAgentIds } from "../../agents/agent-scope.js";
1
2
  import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
2
3
  import { buildModelAliasIndex, modelKey, parseModelRef, resolveModelRefFromString, } from "../../agents/model-selection.js";
4
+ import { formatCliCommand } from "../../cli/command-format.js";
3
5
  import { readConfigFileSnapshot, writeConfigFile, } from "../../config/config.js";
6
+ import { normalizeAgentId } from "../../routing/session-key.js";
4
7
  export const ensureFlagCompatibility = (opts) => {
5
8
  if (opts.json && opts.plain) {
6
9
  throw new Error("Choose either --json or --plain, not both.");
@@ -67,6 +70,18 @@ export function normalizeAlias(alias) {
67
70
  }
68
71
  return trimmed;
69
72
  }
73
+ export function resolveKnownAgentId(params) {
74
+ const raw = params.rawAgentId?.trim();
75
+ if (!raw) {
76
+ return undefined;
77
+ }
78
+ const agentId = normalizeAgentId(raw);
79
+ const knownAgents = listAgentIds(params.cfg);
80
+ if (!knownAgents.includes(agentId)) {
81
+ throw new Error(`Unknown agent id "${raw}". Use "${formatCliCommand("poolbot agents list")}" to see configured agents.`);
82
+ }
83
+ return agentId;
84
+ }
70
85
  export { modelKey };
71
86
  export { DEFAULT_MODEL, DEFAULT_PROVIDER };
72
87
  /**
@@ -0,0 +1,384 @@
1
+ import { DEFAULT_PROVIDER } from "../agents/defaults.js";
2
+ import { buildModelAliasIndex, modelKey } from "../agents/model-selection.js";
3
+ import { fetchWithTimeout } from "../utils/fetch-timeout.js";
4
+ import { applyPrimaryModel } from "./model-picker.js";
5
+ import { normalizeAlias } from "./models/shared.js";
6
+ const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434/v1";
7
+ const DEFAULT_CONTEXT_WINDOW = 4096;
8
+ const DEFAULT_MAX_TOKENS = 4096;
9
+ const VERIFY_TIMEOUT_MS = 10000;
10
+ const COMPATIBILITY_OPTIONS = [
11
+ {
12
+ value: "openai",
13
+ label: "OpenAI-compatible",
14
+ hint: "Uses /chat/completions",
15
+ api: "openai-completions",
16
+ },
17
+ {
18
+ value: "anthropic",
19
+ label: "Anthropic-compatible",
20
+ hint: "Uses /messages",
21
+ api: "anthropic-messages",
22
+ },
23
+ {
24
+ value: "unknown",
25
+ label: "Unknown (detect automatically)",
26
+ hint: "Probes OpenAI then Anthropic endpoints",
27
+ },
28
+ ];
29
+ function normalizeEndpointId(raw) {
30
+ const trimmed = raw.trim().toLowerCase();
31
+ if (!trimmed) {
32
+ return "";
33
+ }
34
+ return trimmed.replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
35
+ }
36
+ function buildEndpointIdFromUrl(baseUrl) {
37
+ try {
38
+ const url = new URL(baseUrl);
39
+ const host = url.hostname.replace(/[^a-z0-9]+/gi, "-").toLowerCase();
40
+ const port = url.port ? `-${url.port}` : "";
41
+ const candidate = `custom-${host}${port}`;
42
+ return normalizeEndpointId(candidate) || "custom";
43
+ }
44
+ catch {
45
+ return "custom";
46
+ }
47
+ }
48
+ function resolveUniqueEndpointId(params) {
49
+ const normalized = normalizeEndpointId(params.requestedId) || "custom";
50
+ const existing = params.providers[normalized];
51
+ if (!existing?.baseUrl || existing.baseUrl === params.baseUrl) {
52
+ return { providerId: normalized, renamed: false };
53
+ }
54
+ let suffix = 2;
55
+ let candidate = `${normalized}-${suffix}`;
56
+ while (params.providers[candidate]) {
57
+ suffix += 1;
58
+ candidate = `${normalized}-${suffix}`;
59
+ }
60
+ return { providerId: candidate, renamed: true };
61
+ }
62
+ function resolveAliasError(params) {
63
+ const trimmed = params.raw.trim();
64
+ if (!trimmed) {
65
+ return undefined;
66
+ }
67
+ let normalized;
68
+ try {
69
+ normalized = normalizeAlias(trimmed);
70
+ }
71
+ catch (err) {
72
+ return err instanceof Error ? err.message : "Alias is invalid.";
73
+ }
74
+ const aliasIndex = buildModelAliasIndex({
75
+ cfg: params.cfg,
76
+ defaultProvider: DEFAULT_PROVIDER,
77
+ });
78
+ const aliasKey = normalized.toLowerCase();
79
+ const existing = aliasIndex.byAlias.get(aliasKey);
80
+ if (!existing) {
81
+ return undefined;
82
+ }
83
+ const existingKey = modelKey(existing.ref.provider, existing.ref.model);
84
+ if (existingKey === params.modelRef) {
85
+ return undefined;
86
+ }
87
+ return `Alias ${normalized} already points to ${existingKey}.`;
88
+ }
89
+ function buildOpenAiHeaders(apiKey) {
90
+ const headers = {};
91
+ if (apiKey) {
92
+ headers.Authorization = `Bearer ${apiKey}`;
93
+ }
94
+ return headers;
95
+ }
96
+ function buildAnthropicHeaders(apiKey) {
97
+ const headers = {
98
+ "anthropic-version": "2023-06-01",
99
+ };
100
+ if (apiKey) {
101
+ headers["x-api-key"] = apiKey;
102
+ }
103
+ return headers;
104
+ }
105
+ function formatVerificationError(error) {
106
+ if (!error) {
107
+ return "unknown error";
108
+ }
109
+ if (error instanceof Error) {
110
+ return error.message;
111
+ }
112
+ if (typeof error === "string") {
113
+ return error;
114
+ }
115
+ try {
116
+ return JSON.stringify(error);
117
+ }
118
+ catch {
119
+ return "unknown error";
120
+ }
121
+ }
122
+ async function requestOpenAiVerification(params) {
123
+ const endpoint = new URL("chat/completions", params.baseUrl.endsWith("/") ? params.baseUrl : `${params.baseUrl}/`).href;
124
+ try {
125
+ const res = await fetchWithTimeout(endpoint, {
126
+ method: "POST",
127
+ headers: {
128
+ "Content-Type": "application/json",
129
+ ...buildOpenAiHeaders(params.apiKey),
130
+ },
131
+ body: JSON.stringify({
132
+ model: params.modelId,
133
+ messages: [{ role: "user", content: "Hi" }],
134
+ max_tokens: 5,
135
+ }),
136
+ }, VERIFY_TIMEOUT_MS);
137
+ return { ok: res.ok, status: res.status };
138
+ }
139
+ catch (error) {
140
+ return { ok: false, error };
141
+ }
142
+ }
143
+ async function requestAnthropicVerification(params) {
144
+ const endpoint = new URL("messages", params.baseUrl.endsWith("/") ? params.baseUrl : `${params.baseUrl}/`).href;
145
+ try {
146
+ const res = await fetchWithTimeout(endpoint, {
147
+ method: "POST",
148
+ headers: {
149
+ "Content-Type": "application/json",
150
+ ...buildAnthropicHeaders(params.apiKey),
151
+ },
152
+ body: JSON.stringify({
153
+ model: params.modelId,
154
+ max_tokens: 16,
155
+ messages: [{ role: "user", content: "Hi" }],
156
+ }),
157
+ }, VERIFY_TIMEOUT_MS);
158
+ return { ok: res.ok, status: res.status };
159
+ }
160
+ catch (error) {
161
+ return { ok: false, error };
162
+ }
163
+ }
164
+ async function promptBaseUrlAndKey(params) {
165
+ const baseUrlInput = await params.prompter.text({
166
+ message: "API Base URL",
167
+ initialValue: params.initialBaseUrl ?? DEFAULT_OLLAMA_BASE_URL,
168
+ placeholder: "https://api.example.com/v1",
169
+ validate: (val) => {
170
+ try {
171
+ new URL(val);
172
+ return undefined;
173
+ }
174
+ catch {
175
+ return "Please enter a valid URL (e.g. http://...)";
176
+ }
177
+ },
178
+ });
179
+ const apiKeyInput = await params.prompter.text({
180
+ message: "API Key (leave blank if not required)",
181
+ placeholder: "sk-...",
182
+ initialValue: "",
183
+ });
184
+ return { baseUrl: baseUrlInput.trim(), apiKey: apiKeyInput.trim() };
185
+ }
186
+ export async function promptCustomApiConfig(params) {
187
+ const { prompter, runtime, config } = params;
188
+ const baseInput = await promptBaseUrlAndKey({ prompter });
189
+ let baseUrl = baseInput.baseUrl;
190
+ let apiKey = baseInput.apiKey;
191
+ const compatibilityChoice = await prompter.select({
192
+ message: "Endpoint compatibility",
193
+ options: COMPATIBILITY_OPTIONS.map((option) => ({
194
+ value: option.value,
195
+ label: option.label,
196
+ hint: option.hint,
197
+ })),
198
+ });
199
+ let modelId = (await prompter.text({
200
+ message: "Model ID",
201
+ placeholder: "e.g. llama3, claude-3-7-sonnet",
202
+ validate: (val) => (val.trim() ? undefined : "Model ID is required"),
203
+ })).trim();
204
+ let compatibility = compatibilityChoice === "unknown" ? null : compatibilityChoice;
205
+ let providerApi = COMPATIBILITY_OPTIONS.find((entry) => entry.value === compatibility)?.api ??
206
+ "openai-completions";
207
+ while (true) {
208
+ let verifiedFromProbe = false;
209
+ if (!compatibility) {
210
+ const probeSpinner = prompter.progress("Detecting endpoint type...");
211
+ const openaiProbe = await requestOpenAiVerification({ baseUrl, apiKey, modelId });
212
+ if (openaiProbe.ok) {
213
+ probeSpinner.stop("Detected OpenAI-compatible endpoint.");
214
+ compatibility = "openai";
215
+ providerApi = "openai-completions";
216
+ verifiedFromProbe = true;
217
+ }
218
+ else {
219
+ const anthropicProbe = await requestAnthropicVerification({ baseUrl, apiKey, modelId });
220
+ if (anthropicProbe.ok) {
221
+ probeSpinner.stop("Detected Anthropic-compatible endpoint.");
222
+ compatibility = "anthropic";
223
+ providerApi = "anthropic-messages";
224
+ verifiedFromProbe = true;
225
+ }
226
+ else {
227
+ probeSpinner.stop("Could not detect endpoint type.");
228
+ await prompter.note("This endpoint did not respond to OpenAI or Anthropic style requests.", "Endpoint detection");
229
+ const retryChoice = await prompter.select({
230
+ message: "What would you like to change?",
231
+ options: [
232
+ { value: "baseUrl", label: "Change base URL" },
233
+ { value: "model", label: "Change model" },
234
+ { value: "both", label: "Change base URL and model" },
235
+ ],
236
+ });
237
+ if (retryChoice === "baseUrl" || retryChoice === "both") {
238
+ const retryInput = await promptBaseUrlAndKey({
239
+ prompter,
240
+ initialBaseUrl: baseUrl,
241
+ });
242
+ baseUrl = retryInput.baseUrl;
243
+ apiKey = retryInput.apiKey;
244
+ }
245
+ if (retryChoice === "model" || retryChoice === "both") {
246
+ modelId = (await prompter.text({
247
+ message: "Model ID",
248
+ placeholder: "e.g. llama3, claude-3-7-sonnet",
249
+ validate: (val) => (val.trim() ? undefined : "Model ID is required"),
250
+ })).trim();
251
+ }
252
+ continue;
253
+ }
254
+ }
255
+ }
256
+ if (verifiedFromProbe) {
257
+ break;
258
+ }
259
+ const verifySpinner = prompter.progress("Verifying...");
260
+ const result = compatibility === "anthropic"
261
+ ? await requestAnthropicVerification({ baseUrl, apiKey, modelId })
262
+ : await requestOpenAiVerification({ baseUrl, apiKey, modelId });
263
+ if (result.ok) {
264
+ verifySpinner.stop("Verification successful.");
265
+ break;
266
+ }
267
+ if (result.status !== undefined) {
268
+ verifySpinner.stop(`Verification failed: status ${result.status}`);
269
+ }
270
+ else {
271
+ verifySpinner.stop(`Verification failed: ${formatVerificationError(result.error)}`);
272
+ }
273
+ const retryChoice = await prompter.select({
274
+ message: "What would you like to change?",
275
+ options: [
276
+ { value: "baseUrl", label: "Change base URL" },
277
+ { value: "model", label: "Change model" },
278
+ { value: "both", label: "Change base URL and model" },
279
+ ],
280
+ });
281
+ if (retryChoice === "baseUrl" || retryChoice === "both") {
282
+ const retryInput = await promptBaseUrlAndKey({
283
+ prompter,
284
+ initialBaseUrl: baseUrl,
285
+ });
286
+ baseUrl = retryInput.baseUrl;
287
+ apiKey = retryInput.apiKey;
288
+ }
289
+ if (retryChoice === "model" || retryChoice === "both") {
290
+ modelId = (await prompter.text({
291
+ message: "Model ID",
292
+ placeholder: "e.g. llama3, claude-3-7-sonnet",
293
+ validate: (val) => (val.trim() ? undefined : "Model ID is required"),
294
+ })).trim();
295
+ }
296
+ if (compatibilityChoice === "unknown") {
297
+ compatibility = null;
298
+ }
299
+ }
300
+ const providers = config.models?.providers ?? {};
301
+ const suggestedId = buildEndpointIdFromUrl(baseUrl);
302
+ const providerIdInput = await prompter.text({
303
+ message: "Endpoint ID",
304
+ initialValue: suggestedId,
305
+ placeholder: "custom",
306
+ validate: (value) => {
307
+ const normalized = normalizeEndpointId(value);
308
+ if (!normalized) {
309
+ return "Endpoint ID is required.";
310
+ }
311
+ return undefined;
312
+ },
313
+ });
314
+ const providerIdResult = resolveUniqueEndpointId({
315
+ requestedId: providerIdInput,
316
+ baseUrl,
317
+ providers,
318
+ });
319
+ if (providerIdResult.renamed) {
320
+ await prompter.note(`Endpoint ID "${providerIdInput}" already exists for a different base URL. Using "${providerIdResult.providerId}".`, "Endpoint ID");
321
+ }
322
+ const providerId = providerIdResult.providerId;
323
+ const modelRef = modelKey(providerId, modelId);
324
+ const aliasInput = await prompter.text({
325
+ message: "Model alias (optional)",
326
+ placeholder: "e.g. local, ollama",
327
+ initialValue: "",
328
+ validate: (value) => resolveAliasError({ raw: value, cfg: config, modelRef }),
329
+ });
330
+ const alias = aliasInput.trim();
331
+ const existingProvider = providers[providerId];
332
+ const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
333
+ const hasModel = existingModels.some((model) => model.id === modelId);
334
+ const nextModel = {
335
+ id: modelId,
336
+ name: `${modelId} (Custom Provider)`,
337
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
338
+ maxTokens: DEFAULT_MAX_TOKENS,
339
+ input: ["text"],
340
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
341
+ reasoning: false,
342
+ };
343
+ const mergedModels = hasModel ? existingModels : [...existingModels, nextModel];
344
+ const { apiKey: existingApiKey, ...existingProviderRest } = existingProvider ?? {};
345
+ const normalizedApiKey = apiKey.trim() || (existingApiKey ? existingApiKey.trim() : undefined);
346
+ let newConfig = {
347
+ ...config,
348
+ models: {
349
+ ...config.models,
350
+ mode: config.models?.mode ?? "merge",
351
+ providers: {
352
+ ...providers,
353
+ [providerId]: {
354
+ ...existingProviderRest,
355
+ baseUrl,
356
+ api: providerApi,
357
+ ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
358
+ models: mergedModels.length > 0 ? mergedModels : [nextModel],
359
+ },
360
+ },
361
+ },
362
+ };
363
+ newConfig = applyPrimaryModel(newConfig, modelRef);
364
+ if (alias) {
365
+ newConfig = {
366
+ ...newConfig,
367
+ agents: {
368
+ ...newConfig.agents,
369
+ defaults: {
370
+ ...newConfig.agents?.defaults,
371
+ models: {
372
+ ...newConfig.agents?.defaults?.models,
373
+ [modelRef]: {
374
+ ...newConfig.agents?.defaults?.models?.[modelRef],
375
+ alias,
376
+ },
377
+ },
378
+ },
379
+ },
380
+ };
381
+ }
382
+ runtime.log(`Configured custom provider: ${providerId}/${modelId}`);
383
+ return { config: newConfig, providerId, modelId };
384
+ }
@@ -0,0 +1,35 @@
1
+ const AUTH_CHOICE_FLAG_MAP = [
2
+ { flag: "anthropicApiKey", authChoice: "apiKey", label: "--anthropic-api-key" },
3
+ { flag: "geminiApiKey", authChoice: "gemini-api-key", label: "--gemini-api-key" },
4
+ { flag: "openaiApiKey", authChoice: "openai-api-key", label: "--openai-api-key" },
5
+ { flag: "openrouterApiKey", authChoice: "openrouter-api-key", label: "--openrouter-api-key" },
6
+ { flag: "aiGatewayApiKey", authChoice: "ai-gateway-api-key", label: "--ai-gateway-api-key" },
7
+ {
8
+ flag: "cloudflareAiGatewayApiKey",
9
+ authChoice: "cloudflare-ai-gateway-api-key",
10
+ label: "--cloudflare-ai-gateway-api-key",
11
+ },
12
+ { flag: "moonshotApiKey", authChoice: "moonshot-api-key", label: "--moonshot-api-key" },
13
+ { flag: "kimiCodeApiKey", authChoice: "kimi-code-api-key", label: "--kimi-code-api-key" },
14
+ { flag: "syntheticApiKey", authChoice: "synthetic-api-key", label: "--synthetic-api-key" },
15
+ { flag: "veniceApiKey", authChoice: "venice-api-key", label: "--venice-api-key" },
16
+ { flag: "zaiApiKey", authChoice: "zai-api-key", label: "--zai-api-key" },
17
+ { flag: "xiaomiApiKey", authChoice: "xiaomi-api-key", label: "--xiaomi-api-key" },
18
+ { flag: "xaiApiKey", authChoice: "xai-api-key", label: "--xai-api-key" },
19
+ { flag: "minimaxApiKey", authChoice: "minimax-api", label: "--minimax-api-key" },
20
+ { flag: "opencodeZenApiKey", authChoice: "opencode-zen", label: "--opencode-zen-api-key" },
21
+ ];
22
+ // Infer auth choice from explicit provider API key flags.
23
+ export function inferAuthChoiceFromFlags(opts) {
24
+ const matches = AUTH_CHOICE_FLAG_MAP.filter(({ flag }) => {
25
+ const value = opts[flag];
26
+ if (typeof value === "string") {
27
+ return value.trim().length > 0;
28
+ }
29
+ return Boolean(value);
30
+ });
31
+ return {
32
+ choice: matches[0]?.authChoice,
33
+ matches,
34
+ };
35
+ }
@@ -2,8 +2,10 @@ import { upsertAuthProfile } from "../../../agents/auth-profiles.js";
2
2
  import { normalizeProviderId } from "../../../agents/model-selection.js";
3
3
  import { parseDurationMs } from "../../../cli/parse-duration.js";
4
4
  import { upsertSharedEnvVar } from "../../../infra/env-file.js";
5
+ import { normalizeSecretInput } from "../../../utils/normalize-secret-input.js";
5
6
  import { buildTokenProfileId, validateAnthropicSetupToken } from "../../auth-token.js";
6
7
  import { applyGoogleGeminiModelDefault } from "../../google-gemini-model-default.js";
8
+ import { applyOpenAIConfig } from "../../openai-model-default.js";
7
9
  import { applyAuthProfileConfig, applyCloudflareAiGatewayConfig, applyKimiCodeConfig, applyMinimaxApiConfig, applyMinimaxConfig, applyMoonshotConfig, applyMoonshotConfigCn, applyOpencodeZenConfig, applyOpenrouterConfig, applyQianfanConfig, applySyntheticConfig, applyTogetherConfig, applyVeniceConfig, applyVercelAiGatewayConfig, applyXaiConfig, applyXiaomiConfig, applyZaiConfig, setAnthropicApiKey, setCloudflareAiGatewayConfig, setGeminiApiKey, setKimiCodingApiKey, setMinimaxApiKey, setMoonshotApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, setQianfanApiKey, setSyntheticApiKey, setTogetherApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, setXaiApiKey, setXiaomiApiKey, setZaiApiKey, } from "../../onboard-auth.js";
8
10
  import { resolveNonInteractiveApiKey } from "../api-keys.js";
9
11
  import { shortenHomePath } from "../../../utils.js";
@@ -58,7 +60,7 @@ export async function applyNonInteractiveAuthChoice(params) {
58
60
  runtime.exit(1);
59
61
  return null;
60
62
  }
61
- const tokenRaw = opts.token?.trim();
63
+ const tokenRaw = normalizeSecretInput(opts.token);
62
64
  if (!tokenRaw) {
63
65
  runtime.error("Missing --token for --auth-choice token.");
64
66
  runtime.exit(1);
@@ -154,7 +156,7 @@ export async function applyNonInteractiveAuthChoice(params) {
154
156
  const result = upsertSharedEnvVar({ key: "OPENAI_API_KEY", value: key });
155
157
  process.env.OPENAI_API_KEY = key;
156
158
  runtime.log(`Saved OPENAI_API_KEY to ${shortenHomePath(result.path)}`);
157
- return nextConfig;
159
+ return applyOpenAIConfig(nextConfig);
158
160
  }
159
161
  if (authChoice === "openrouter-api-key") {
160
162
  const resolved = await resolveNonInteractiveApiKey({
@@ -458,7 +460,8 @@ export async function applyNonInteractiveAuthChoice(params) {
458
460
  if (authChoice === "oauth" ||
459
461
  authChoice === "chutes" ||
460
462
  authChoice === "openai-codex" ||
461
- authChoice === "qwen-portal") {
463
+ authChoice === "qwen-portal" ||
464
+ authChoice === "minimax-portal") {
462
465
  runtime.error("OAuth requires interactive mode.");
463
466
  runtime.exit(1);
464
467
  return null;