@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
@@ -2,27 +2,18 @@ import { constants as fsConstants } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js";
5
6
  import { findModelInCatalog, loadModelCatalog, modelSupportsVision, } from "../agents/model-catalog.js";
6
7
  import { applyTemplate } from "../auto-reply/templating.js";
7
- import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js";
8
8
  import { logVerbose, shouldLogVerbose } from "../globals.js";
9
9
  import { runExec } from "../process/exec.js";
10
10
  import { MediaAttachmentCache, normalizeAttachments, selectAttachments } from "./attachments.js";
11
- import { CLI_OUTPUT_MAX_BUFFER, DEFAULT_AUDIO_MODELS, DEFAULT_TIMEOUT_SECONDS, } from "./defaults.js";
11
+ import { AUTO_AUDIO_KEY_PROVIDERS, AUTO_IMAGE_KEY_PROVIDERS, AUTO_VIDEO_KEY_PROVIDERS, CLI_OUTPUT_MAX_BUFFER, DEFAULT_AUDIO_MODELS, DEFAULT_IMAGE_MODELS, DEFAULT_TIMEOUT_SECONDS, } from "./defaults.js";
12
12
  import { isMediaUnderstandingSkipError, MediaUnderstandingSkipError } from "./errors.js";
13
- import { resolveMaxBytes, resolveMaxChars, resolveModelEntries, resolvePrompt, resolveScopeDecision, resolveTimeoutMs, } from "./resolve.js";
14
- import { buildMediaUnderstandingRegistry, getMediaUnderstandingProvider, normalizeMediaProviderId, } from "./providers/index.js";
15
13
  import { describeImageWithModel } from "./providers/image.js";
14
+ import { buildMediaUnderstandingRegistry, getMediaUnderstandingProvider, normalizeMediaProviderId, } from "./providers/index.js";
15
+ import { resolveMaxBytes, resolveMaxChars, resolveModelEntries, resolvePrompt, resolveScopeDecision, resolveTimeoutMs, } from "./resolve.js";
16
16
  import { estimateBase64Size, resolveVideoMaxBase64Bytes } from "./video.js";
17
- const AUTO_AUDIO_KEY_PROVIDERS = ["openai", "groq", "deepgram", "google"];
18
- const AUTO_IMAGE_KEY_PROVIDERS = ["openai", "anthropic", "google", "minimax"];
19
- const AUTO_VIDEO_KEY_PROVIDERS = ["google"];
20
- const DEFAULT_IMAGE_MODELS = {
21
- openai: "gpt-5-mini",
22
- anthropic: "claude-opus-4-5",
23
- google: "gemini-3-flash-preview",
24
- minimax: "MiniMax-VL-01",
25
- };
26
17
  export function buildProviderRegistry(overrides) {
27
18
  return buildMediaUnderstandingRegistry(overrides);
28
19
  }
@@ -35,24 +26,29 @@ export function createMediaAttachmentCache(attachments) {
35
26
  const binaryCache = new Map();
36
27
  const geminiProbeCache = new Map();
37
28
  function expandHomeDir(value) {
38
- if (!value.startsWith("~"))
29
+ if (!value.startsWith("~")) {
39
30
  return value;
31
+ }
40
32
  const home = os.homedir();
41
- if (value === "~")
33
+ if (value === "~") {
42
34
  return home;
43
- if (value.startsWith("~/"))
35
+ }
36
+ if (value.startsWith("~/")) {
44
37
  return path.join(home, value.slice(2));
38
+ }
45
39
  return value;
46
40
  }
47
41
  function hasPathSeparator(value) {
48
42
  return value.includes("/") || value.includes("\\");
49
43
  }
50
44
  function candidateBinaryNames(name) {
51
- if (process.platform !== "win32")
45
+ if (process.platform !== "win32") {
52
46
  return [name];
47
+ }
53
48
  const ext = path.extname(name);
54
- if (ext)
49
+ if (ext) {
55
50
  return [name];
51
+ }
56
52
  const pathext = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM")
57
53
  .split(";")
58
54
  .map((item) => item.trim())
@@ -64,10 +60,12 @@ function candidateBinaryNames(name) {
64
60
  async function isExecutable(filePath) {
65
61
  try {
66
62
  const stat = await fs.stat(filePath);
67
- if (!stat.isFile())
63
+ if (!stat.isFile()) {
68
64
  return false;
69
- if (process.platform === "win32")
65
+ }
66
+ if (process.platform === "win32") {
70
67
  return true;
68
+ }
71
69
  await fs.access(filePath, fsConstants.X_OK);
72
70
  return true;
73
71
  }
@@ -77,29 +75,34 @@ async function isExecutable(filePath) {
77
75
  }
78
76
  async function findBinary(name) {
79
77
  const cached = binaryCache.get(name);
80
- if (cached)
78
+ if (cached) {
81
79
  return cached;
80
+ }
82
81
  const resolved = (async () => {
83
82
  const direct = expandHomeDir(name.trim());
84
83
  if (direct && hasPathSeparator(direct)) {
85
84
  for (const candidate of candidateBinaryNames(direct)) {
86
- if (await isExecutable(candidate))
85
+ if (await isExecutable(candidate)) {
87
86
  return candidate;
87
+ }
88
88
  }
89
89
  }
90
90
  const searchName = name.trim();
91
- if (!searchName)
91
+ if (!searchName) {
92
92
  return null;
93
+ }
93
94
  const pathEntries = (process.env.PATH ?? "").split(path.delimiter);
94
95
  const candidates = candidateBinaryNames(searchName);
95
96
  for (const entryRaw of pathEntries) {
96
97
  const entry = expandHomeDir(entryRaw.trim().replace(/^"(.*)"$/, "$1"));
97
- if (!entry)
98
+ if (!entry) {
98
99
  continue;
100
+ }
99
101
  for (const candidate of candidates) {
100
102
  const fullPath = path.join(entry, candidate);
101
- if (await isExecutable(fullPath))
103
+ if (await isExecutable(fullPath)) {
102
104
  return fullPath;
105
+ }
103
106
  }
104
107
  }
105
108
  return null;
@@ -111,8 +114,9 @@ async function hasBinary(name) {
111
114
  return Boolean(await findBinary(name));
112
115
  }
113
116
  async function fileExists(filePath) {
114
- if (!filePath)
117
+ if (!filePath) {
115
118
  return false;
119
+ }
116
120
  try {
117
121
  await fs.stat(filePath);
118
122
  return true;
@@ -124,8 +128,9 @@ async function fileExists(filePath) {
124
128
  function extractLastJsonObject(raw) {
125
129
  const trimmed = raw.trim();
126
130
  const start = trimmed.lastIndexOf("{");
127
- if (start === -1)
131
+ if (start === -1) {
128
132
  return null;
133
+ }
129
134
  const slice = trimmed.slice(start);
130
135
  try {
131
136
  return JSON.parse(slice);
@@ -136,22 +141,26 @@ function extractLastJsonObject(raw) {
136
141
  }
137
142
  function extractGeminiResponse(raw) {
138
143
  const payload = extractLastJsonObject(raw);
139
- if (!payload || typeof payload !== "object")
144
+ if (!payload || typeof payload !== "object") {
140
145
  return null;
146
+ }
141
147
  const response = payload.response;
142
- if (typeof response !== "string")
148
+ if (typeof response !== "string") {
143
149
  return null;
150
+ }
144
151
  const trimmed = response.trim();
145
152
  return trimmed || null;
146
153
  }
147
154
  function extractSherpaOnnxText(raw) {
148
155
  const tryParse = (value) => {
149
156
  const trimmed = value.trim();
150
- if (!trimmed)
157
+ if (!trimmed) {
151
158
  return null;
159
+ }
152
160
  const head = trimmed[0];
153
- if (head !== "{" && head !== '"')
161
+ if (head !== "{" && head !== '"') {
154
162
  return null;
163
+ }
155
164
  try {
156
165
  const parsed = JSON.parse(trimmed);
157
166
  if (typeof parsed === "string") {
@@ -168,26 +177,30 @@ function extractSherpaOnnxText(raw) {
168
177
  return null;
169
178
  };
170
179
  const direct = tryParse(raw);
171
- if (direct)
180
+ if (direct) {
172
181
  return direct;
182
+ }
173
183
  const lines = raw
174
184
  .split("\n")
175
185
  .map((line) => line.trim())
176
186
  .filter(Boolean);
177
187
  for (let i = lines.length - 1; i >= 0; i -= 1) {
178
188
  const parsed = tryParse(lines[i] ?? "");
179
- if (parsed)
189
+ if (parsed) {
180
190
  return parsed;
191
+ }
181
192
  }
182
193
  return null;
183
194
  }
184
195
  async function probeGeminiCli() {
185
196
  const cached = geminiProbeCache.get("gemini");
186
- if (cached)
197
+ if (cached) {
187
198
  return cached;
199
+ }
188
200
  const resolved = (async () => {
189
- if (!(await hasBinary("gemini")))
201
+ if (!(await hasBinary("gemini"))) {
190
202
  return false;
203
+ }
191
204
  try {
192
205
  const { stdout } = await runExec("gemini", ["--output-format", "json", "ok"], {
193
206
  timeoutMs: 8000,
@@ -202,13 +215,15 @@ async function probeGeminiCli() {
202
215
  return resolved;
203
216
  }
204
217
  async function resolveLocalWhisperCppEntry() {
205
- if (!(await hasBinary("whisper-cli")))
218
+ if (!(await hasBinary("whisper-cli"))) {
206
219
  return null;
220
+ }
207
221
  const envModel = process.env.WHISPER_CPP_MODEL?.trim();
208
222
  const defaultModel = "/opt/homebrew/share/whisper-cpp/for-tests-ggml-tiny.bin";
209
223
  const modelPath = envModel && (await fileExists(envModel)) ? envModel : defaultModel;
210
- if (!(await fileExists(modelPath)))
224
+ if (!(await fileExists(modelPath))) {
211
225
  return null;
226
+ }
212
227
  return {
213
228
  type: "cli",
214
229
  command: "whisper-cli",
@@ -216,8 +231,9 @@ async function resolveLocalWhisperCppEntry() {
216
231
  };
217
232
  }
218
233
  async function resolveLocalWhisperEntry() {
219
- if (!(await hasBinary("whisper")))
234
+ if (!(await hasBinary("whisper"))) {
220
235
  return null;
236
+ }
221
237
  return {
222
238
  type: "cli",
223
239
  command: "whisper",
@@ -235,23 +251,29 @@ async function resolveLocalWhisperEntry() {
235
251
  };
236
252
  }
237
253
  async function resolveSherpaOnnxEntry() {
238
- if (!(await hasBinary("sherpa-onnx-offline")))
254
+ if (!(await hasBinary("sherpa-onnx-offline"))) {
239
255
  return null;
256
+ }
240
257
  const modelDir = process.env.SHERPA_ONNX_MODEL_DIR?.trim();
241
- if (!modelDir)
258
+ if (!modelDir) {
242
259
  return null;
260
+ }
243
261
  const tokens = path.join(modelDir, "tokens.txt");
244
262
  const encoder = path.join(modelDir, "encoder.onnx");
245
263
  const decoder = path.join(modelDir, "decoder.onnx");
246
264
  const joiner = path.join(modelDir, "joiner.onnx");
247
- if (!(await fileExists(tokens)))
265
+ if (!(await fileExists(tokens))) {
248
266
  return null;
249
- if (!(await fileExists(encoder)))
267
+ }
268
+ if (!(await fileExists(encoder))) {
250
269
  return null;
251
- if (!(await fileExists(decoder)))
270
+ }
271
+ if (!(await fileExists(decoder))) {
252
272
  return null;
253
- if (!(await fileExists(joiner)))
273
+ }
274
+ if (!(await fileExists(joiner))) {
254
275
  return null;
276
+ }
255
277
  return {
256
278
  type: "cli",
257
279
  command: "sherpa-onnx-offline",
@@ -266,16 +288,19 @@ async function resolveSherpaOnnxEntry() {
266
288
  }
267
289
  async function resolveLocalAudioEntry() {
268
290
  const sherpa = await resolveSherpaOnnxEntry();
269
- if (sherpa)
291
+ if (sherpa) {
270
292
  return sherpa;
293
+ }
271
294
  const whisperCpp = await resolveLocalWhisperCppEntry();
272
- if (whisperCpp)
295
+ if (whisperCpp) {
273
296
  return whisperCpp;
297
+ }
274
298
  return await resolveLocalWhisperEntry();
275
299
  }
276
300
  async function resolveGeminiCliEntry(_capability) {
277
- if (!(await probeGeminiCli()))
301
+ if (!(await probeGeminiCli())) {
278
302
  return null;
303
+ }
279
304
  return {
280
305
  type: "cli",
281
306
  command: "gemini",
@@ -295,14 +320,18 @@ async function resolveKeyEntry(params) {
295
320
  const { cfg, agentDir, providerRegistry, capability } = params;
296
321
  const checkProvider = async (providerId, model) => {
297
322
  const provider = getMediaUnderstandingProvider(providerId, providerRegistry);
298
- if (!provider)
323
+ if (!provider) {
299
324
  return null;
300
- if (capability === "audio" && !provider.transcribeAudio)
325
+ }
326
+ if (capability === "audio" && !provider.transcribeAudio) {
301
327
  return null;
302
- if (capability === "image" && !provider.describeImage)
328
+ }
329
+ if (capability === "image" && !provider.describeImage) {
303
330
  return null;
304
- if (capability === "video" && !provider.describeVideo)
331
+ }
332
+ if (capability === "video" && !provider.describeVideo) {
305
333
  return null;
334
+ }
306
335
  try {
307
336
  await resolveApiKeyForProvider({ provider: providerId, cfg, agentDir });
308
337
  return { type: "provider", provider: providerId, model };
@@ -315,14 +344,16 @@ async function resolveKeyEntry(params) {
315
344
  const activeProvider = params.activeModel?.provider?.trim();
316
345
  if (activeProvider) {
317
346
  const activeEntry = await checkProvider(activeProvider, params.activeModel?.model);
318
- if (activeEntry)
347
+ if (activeEntry) {
319
348
  return activeEntry;
349
+ }
320
350
  }
321
351
  for (const providerId of AUTO_IMAGE_KEY_PROVIDERS) {
322
352
  const model = DEFAULT_IMAGE_MODELS[providerId];
323
353
  const entry = await checkProvider(providerId, model);
324
- if (entry)
354
+ if (entry) {
325
355
  return entry;
356
+ }
326
357
  }
327
358
  return null;
328
359
  }
@@ -330,57 +361,68 @@ async function resolveKeyEntry(params) {
330
361
  const activeProvider = params.activeModel?.provider?.trim();
331
362
  if (activeProvider) {
332
363
  const activeEntry = await checkProvider(activeProvider, params.activeModel?.model);
333
- if (activeEntry)
364
+ if (activeEntry) {
334
365
  return activeEntry;
366
+ }
335
367
  }
336
368
  for (const providerId of AUTO_VIDEO_KEY_PROVIDERS) {
337
369
  const entry = await checkProvider(providerId, undefined);
338
- if (entry)
370
+ if (entry) {
339
371
  return entry;
372
+ }
340
373
  }
341
374
  return null;
342
375
  }
343
376
  const activeProvider = params.activeModel?.provider?.trim();
344
377
  if (activeProvider) {
345
378
  const activeEntry = await checkProvider(activeProvider, params.activeModel?.model);
346
- if (activeEntry)
379
+ if (activeEntry) {
347
380
  return activeEntry;
381
+ }
348
382
  }
349
383
  for (const providerId of AUTO_AUDIO_KEY_PROVIDERS) {
350
384
  const entry = await checkProvider(providerId, undefined);
351
- if (entry)
385
+ if (entry) {
352
386
  return entry;
387
+ }
353
388
  }
354
389
  return null;
355
390
  }
356
391
  async function resolveAutoEntries(params) {
357
392
  const activeEntry = await resolveActiveModelEntry(params);
358
- if (activeEntry)
393
+ if (activeEntry) {
359
394
  return [activeEntry];
395
+ }
360
396
  if (params.capability === "audio") {
361
397
  const localAudio = await resolveLocalAudioEntry();
362
- if (localAudio)
398
+ if (localAudio) {
363
399
  return [localAudio];
400
+ }
364
401
  }
365
402
  const gemini = await resolveGeminiCliEntry(params.capability);
366
- if (gemini)
403
+ if (gemini) {
367
404
  return [gemini];
405
+ }
368
406
  const keys = await resolveKeyEntry(params);
369
- if (keys)
407
+ if (keys) {
370
408
  return [keys];
409
+ }
371
410
  return [];
372
411
  }
373
412
  export async function resolveAutoImageModel(params) {
374
413
  const providerRegistry = buildProviderRegistry();
375
414
  const toActive = (entry) => {
376
- if (!entry || entry.type === "cli")
415
+ if (!entry || entry.type === "cli") {
377
416
  return null;
417
+ }
378
418
  const provider = entry.provider;
379
- if (!provider)
419
+ if (!provider) {
380
420
  return null;
421
+ }
381
422
  const model = entry.model ?? DEFAULT_IMAGE_MODELS[provider];
382
- if (!model)
423
+ if (!model) {
383
424
  return null;
425
+ }
384
426
  return { provider, model };
385
427
  };
386
428
  const activeEntry = await resolveActiveModelEntry({
@@ -391,8 +433,9 @@ export async function resolveAutoImageModel(params) {
391
433
  activeModel: params.activeModel,
392
434
  });
393
435
  const resolvedActive = toActive(activeEntry);
394
- if (resolvedActive)
436
+ if (resolvedActive) {
395
437
  return resolvedActive;
438
+ }
396
439
  const keyEntry = await resolveKeyEntry({
397
440
  cfg: params.cfg,
398
441
  agentDir: params.agentDir,
@@ -404,20 +447,26 @@ export async function resolveAutoImageModel(params) {
404
447
  }
405
448
  async function resolveActiveModelEntry(params) {
406
449
  const activeProviderRaw = params.activeModel?.provider?.trim();
407
- if (!activeProviderRaw)
450
+ if (!activeProviderRaw) {
408
451
  return null;
452
+ }
409
453
  const providerId = normalizeMediaProviderId(activeProviderRaw);
410
- if (!providerId)
454
+ if (!providerId) {
411
455
  return null;
456
+ }
412
457
  const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry);
413
- if (!provider)
458
+ if (!provider) {
414
459
  return null;
415
- if (params.capability === "audio" && !provider.transcribeAudio)
460
+ }
461
+ if (params.capability === "audio" && !provider.transcribeAudio) {
416
462
  return null;
417
- if (params.capability === "image" && !provider.describeImage)
463
+ }
464
+ if (params.capability === "image" && !provider.describeImage) {
418
465
  return null;
419
- if (params.capability === "video" && !provider.describeVideo)
466
+ }
467
+ if (params.capability === "video" && !provider.describeVideo) {
420
468
  return null;
469
+ }
421
470
  try {
422
471
  await resolveApiKeyForProvider({
423
472
  provider: providerId,
@@ -436,8 +485,9 @@ async function resolveActiveModelEntry(params) {
436
485
  }
437
486
  function trimOutput(text, maxChars) {
438
487
  const trimmed = text.trim();
439
- if (!maxChars || trimmed.length <= maxChars)
488
+ if (!maxChars || trimmed.length <= maxChars) {
440
489
  return trimmed;
490
+ }
441
491
  return trimmed.slice(0, maxChars).trim();
442
492
  }
443
493
  function commandBase(command) {
@@ -447,8 +497,9 @@ function findArgValue(args, keys) {
447
497
  for (let i = 0; i < args.length; i += 1) {
448
498
  if (keys.includes(args[i] ?? "")) {
449
499
  const value = args[i + 1];
450
- if (value)
500
+ if (value) {
451
501
  return value;
502
+ }
452
503
  }
453
504
  }
454
505
  return undefined;
@@ -459,20 +510,24 @@ function hasArg(args, keys) {
459
510
  function resolveWhisperOutputPath(args, mediaPath) {
460
511
  const outputDir = findArgValue(args, ["--output_dir", "-o"]);
461
512
  const outputFormat = findArgValue(args, ["--output_format"]);
462
- if (!outputDir || !outputFormat)
513
+ if (!outputDir || !outputFormat) {
463
514
  return null;
515
+ }
464
516
  const formats = outputFormat.split(",").map((value) => value.trim());
465
- if (!formats.includes("txt"))
517
+ if (!formats.includes("txt")) {
466
518
  return null;
519
+ }
467
520
  const base = path.parse(mediaPath).name;
468
521
  return path.join(outputDir, `${base}.txt`);
469
522
  }
470
523
  function resolveWhisperCppOutputPath(args) {
471
- if (!hasArg(args, ["-otxt", "--output-txt"]))
524
+ if (!hasArg(args, ["-otxt", "--output-txt"])) {
472
525
  return null;
526
+ }
473
527
  const outputBase = findArgValue(args, ["-of", "--output-file"]);
474
- if (!outputBase)
528
+ if (!outputBase) {
475
529
  return null;
530
+ }
476
531
  return `${outputBase}.txt`;
477
532
  }
478
533
  async function resolveCliOutput(params) {
@@ -485,44 +540,53 @@ async function resolveCliOutput(params) {
485
540
  if (fileOutput && (await fileExists(fileOutput))) {
486
541
  try {
487
542
  const content = await fs.readFile(fileOutput, "utf8");
488
- if (content.trim())
543
+ if (content.trim()) {
489
544
  return content.trim();
545
+ }
490
546
  }
491
547
  catch { }
492
548
  }
493
549
  if (commandId === "gemini") {
494
550
  const response = extractGeminiResponse(params.stdout);
495
- if (response)
551
+ if (response) {
496
552
  return response;
553
+ }
497
554
  }
498
555
  if (commandId === "sherpa-onnx-offline") {
499
556
  const response = extractSherpaOnnxText(params.stdout);
500
- if (response)
557
+ if (response) {
501
558
  return response;
559
+ }
502
560
  }
503
561
  return params.stdout.trim();
504
562
  }
505
563
  function normalizeProviderQuery(options) {
506
- if (!options)
564
+ if (!options) {
507
565
  return undefined;
566
+ }
508
567
  const query = {};
509
568
  for (const [key, value] of Object.entries(options)) {
510
- if (value === undefined)
569
+ if (value === undefined) {
511
570
  continue;
571
+ }
512
572
  query[key] = value;
513
573
  }
514
574
  return Object.keys(query).length > 0 ? query : undefined;
515
575
  }
516
576
  function buildDeepgramCompatQuery(options) {
517
- if (!options)
577
+ if (!options) {
518
578
  return undefined;
579
+ }
519
580
  const query = {};
520
- if (typeof options.detectLanguage === "boolean")
581
+ if (typeof options.detectLanguage === "boolean") {
521
582
  query.detect_language = options.detectLanguage;
522
- if (typeof options.punctuate === "boolean")
583
+ }
584
+ if (typeof options.punctuate === "boolean") {
523
585
  query.punctuate = options.punctuate;
524
- if (typeof options.smartFormat === "boolean")
586
+ }
587
+ if (typeof options.smartFormat === "boolean") {
525
588
  query.smart_format = options.smartFormat;
589
+ }
526
590
  return Object.keys(query).length > 0 ? query : undefined;
527
591
  }
528
592
  function normalizeDeepgramQueryKeys(query) {
@@ -797,8 +861,9 @@ async function runCliEntry(params) {
797
861
  mediaPath,
798
862
  });
799
863
  const text = trimOutput(resolved, maxChars);
800
- if (!text)
864
+ if (!text) {
801
865
  return null;
866
+ }
802
867
  return {
803
868
  kind: capability === "audio" ? "audio.transcription" : `${capability}.description`,
804
869
  attachmentIndex: params.attachmentIndex,
@@ -840,10 +905,12 @@ async function runAttachmentEntries(params) {
840
905
  });
841
906
  if (result) {
842
907
  const decision = buildModelDecision({ entry, entryType, outcome: "success" });
843
- if (result.provider)
908
+ if (result.provider) {
844
909
  decision.provider = result.provider;
845
- if (result.model)
910
+ }
911
+ if (result.model) {
846
912
  decision.model = result.model;
913
+ }
847
914
  attempts.push(decision);
848
915
  return { output: result, attempts };
849
916
  }
@@ -985,8 +1052,9 @@ export async function runCapability(params) {
985
1052
  entries: resolvedEntries,
986
1053
  config,
987
1054
  });
988
- if (output)
1055
+ if (output) {
989
1056
  outputs.push(output);
1057
+ }
990
1058
  attachmentDecisions.push({
991
1059
  attachmentIndex: attachment.index,
992
1060
  attempts,