@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
@@ -0,0 +1,171 @@
1
+ import { callGateway, randomIdempotencyKey } from "../../gateway/call.js";
2
+ import { logVerbose } from "../../globals.js";
3
+ const PTT_COMMANDS = {
4
+ start: "talk.ptt.start",
5
+ stop: "talk.ptt.stop",
6
+ once: "talk.ptt.once",
7
+ cancel: "talk.ptt.cancel",
8
+ };
9
+ function normalizeNodeKey(value) {
10
+ return value
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, "-")
13
+ .replace(/^-+/, "")
14
+ .replace(/-+$/, "");
15
+ }
16
+ function isIOSNode(node) {
17
+ const platform = node.platform?.toLowerCase() ?? "";
18
+ const family = node.deviceFamily?.toLowerCase() ?? "";
19
+ return (platform.startsWith("ios") ||
20
+ family.includes("iphone") ||
21
+ family.includes("ipad") ||
22
+ family.includes("ios"));
23
+ }
24
+ async function loadNodes(cfg) {
25
+ try {
26
+ const res = await callGateway({
27
+ method: "node.list",
28
+ params: {},
29
+ config: cfg,
30
+ });
31
+ return Array.isArray(res.nodes) ? res.nodes : [];
32
+ }
33
+ catch {
34
+ const res = await callGateway({
35
+ method: "node.pair.list",
36
+ params: {},
37
+ config: cfg,
38
+ });
39
+ return Array.isArray(res.paired) ? res.paired : [];
40
+ }
41
+ }
42
+ function describeNodes(nodes) {
43
+ return nodes
44
+ .map((node) => node.displayName || node.remoteIp || node.nodeId)
45
+ .filter(Boolean)
46
+ .join(", ");
47
+ }
48
+ function resolveNodeId(nodes, query) {
49
+ const trimmed = String(query ?? "").trim();
50
+ if (trimmed) {
51
+ const qNorm = normalizeNodeKey(trimmed);
52
+ const matches = nodes.filter((node) => {
53
+ if (node.nodeId === trimmed) {
54
+ return true;
55
+ }
56
+ if (typeof node.remoteIp === "string" && node.remoteIp === trimmed) {
57
+ return true;
58
+ }
59
+ const name = typeof node.displayName === "string" ? node.displayName : "";
60
+ if (name && normalizeNodeKey(name) === qNorm) {
61
+ return true;
62
+ }
63
+ if (trimmed.length >= 6 && node.nodeId.startsWith(trimmed)) {
64
+ return true;
65
+ }
66
+ return false;
67
+ });
68
+ if (matches.length === 1) {
69
+ return matches[0].nodeId;
70
+ }
71
+ const known = describeNodes(nodes);
72
+ if (matches.length === 0) {
73
+ throw new Error(`unknown node: ${trimmed}${known ? ` (known: ${known})` : ""}`);
74
+ }
75
+ throw new Error(`ambiguous node: ${trimmed} (matches: ${matches
76
+ .map((node) => node.displayName || node.remoteIp || node.nodeId)
77
+ .join(", ")})`);
78
+ }
79
+ const iosNodes = nodes.filter(isIOSNode);
80
+ const iosConnected = iosNodes.filter((node) => node.connected);
81
+ const iosCandidates = iosConnected.length > 0 ? iosConnected : iosNodes;
82
+ if (iosCandidates.length === 1) {
83
+ return iosCandidates[0].nodeId;
84
+ }
85
+ if (iosCandidates.length > 1) {
86
+ throw new Error(`multiple iOS nodes found (${describeNodes(iosCandidates)}); specify node=<id>`);
87
+ }
88
+ const connected = nodes.filter((node) => node.connected);
89
+ const fallback = connected.length > 0 ? connected : nodes;
90
+ if (fallback.length === 1) {
91
+ return fallback[0].nodeId;
92
+ }
93
+ const known = describeNodes(nodes);
94
+ throw new Error(`node required${known ? ` (known: ${known})` : ""}`);
95
+ }
96
+ function parsePTTArgs(commandBody) {
97
+ const tokens = commandBody.trim().split(/\s+/).slice(1);
98
+ let action;
99
+ let node;
100
+ for (const token of tokens) {
101
+ if (!token) {
102
+ continue;
103
+ }
104
+ if (token.toLowerCase().startsWith("node=")) {
105
+ node = token.slice("node=".length);
106
+ continue;
107
+ }
108
+ if (!action) {
109
+ action = token;
110
+ }
111
+ }
112
+ return { action, node };
113
+ }
114
+ function buildPTTHelpText() {
115
+ return [
116
+ "Usage: /ptt <start|stop|once|cancel> [node=<id>]",
117
+ "Example: /ptt once node=iphone",
118
+ ].join("\n");
119
+ }
120
+ export const handlePTTCommand = async (params, allowTextCommands) => {
121
+ if (!allowTextCommands) {
122
+ return null;
123
+ }
124
+ const { command, cfg } = params;
125
+ const normalized = command.commandBodyNormalized.trim();
126
+ if (!normalized.startsWith("/ptt")) {
127
+ return null;
128
+ }
129
+ if (!command.isAuthorizedSender) {
130
+ logVerbose(`Ignoring /ptt from unauthorized sender: ${command.senderId || "<unknown>"}`);
131
+ return { shouldContinue: false, reply: { text: "PTT requires an authorized sender." } };
132
+ }
133
+ const parsed = parsePTTArgs(normalized);
134
+ const actionKey = parsed.action?.trim().toLowerCase() ?? "";
135
+ const commandId = PTT_COMMANDS[actionKey];
136
+ if (!commandId) {
137
+ return { shouldContinue: false, reply: { text: buildPTTHelpText() } };
138
+ }
139
+ try {
140
+ const nodes = await loadNodes(cfg);
141
+ const nodeId = resolveNodeId(nodes, parsed.node);
142
+ const invokeParams = {
143
+ nodeId,
144
+ command: commandId,
145
+ params: {},
146
+ idempotencyKey: randomIdempotencyKey(),
147
+ timeoutMs: 15_000,
148
+ };
149
+ const res = await callGateway({
150
+ method: "node.invoke",
151
+ params: invokeParams,
152
+ config: cfg,
153
+ });
154
+ const payload = res.payload && typeof res.payload === "object" ? res.payload : {};
155
+ const lines = [`PTT ${actionKey} → ${nodeId}`];
156
+ if (typeof payload.status === "string") {
157
+ lines.push(`status: ${payload.status}`);
158
+ }
159
+ if (typeof payload.captureId === "string") {
160
+ lines.push(`captureId: ${payload.captureId}`);
161
+ }
162
+ if (typeof payload.transcript === "string" && payload.transcript.trim()) {
163
+ lines.push(`transcript: ${payload.transcript}`);
164
+ }
165
+ return { shouldContinue: false, reply: { text: lines.join("\n") } };
166
+ }
167
+ catch (err) {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ return { shouldContinue: false, reply: { text: `PTT failed: ${message}` } };
170
+ }
171
+ };
@@ -118,7 +118,7 @@ export async function runPreparedReply(params) {
118
118
  const prefixedBody = [threadStarterNote, prefixedBodyBase].filter(Boolean).join("\n\n");
119
119
  const mediaNote = buildInboundMediaNote(ctx);
120
120
  const mediaReplyHint = mediaNote
121
- ? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:/path or MEDIA:https://example.com/image.jpg (spaces ok, quote if needed). Keep caption in the text body."
121
+ ? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:https://example.com/image.jpg (spaces ok, quote if needed) or a safe relative path like MEDIA:./image.jpg. Avoid absolute paths (MEDIA:/...) and ~ paths — they are blocked for security. Keep caption in the text body."
122
122
  : undefined;
123
123
  let prefixedCommandBody = mediaNote
124
124
  ? [mediaNote, mediaReplyHint, prefixedBody ?? ""].filter(Boolean).join("\n").trim()
@@ -242,6 +242,7 @@ export async function runPreparedReply(params) {
242
242
  senderName: sessionCtx.SenderName?.trim() || undefined,
243
243
  senderUsername: sessionCtx.SenderUsername?.trim() || undefined,
244
244
  senderE164: sessionCtx.SenderE164?.trim() || undefined,
245
+ senderIsOwner: command.senderIsOwner,
245
246
  sessionFile,
246
247
  workspaceDir,
247
248
  config: cfg,
@@ -24,7 +24,11 @@ export function finalizeInboundContext(ctx, opts = {}) {
24
24
  }
25
25
  const bodyForAgentSource = opts.forceBodyForAgent
26
26
  ? normalized.Body
27
- : (normalized.BodyForAgent ?? normalized.Body);
27
+ : (normalized.BodyForAgent ??
28
+ // Prefer "clean" text over legacy envelope-shaped Body when upstream forgets to set BodyForAgent.
29
+ normalized.CommandBody ??
30
+ normalized.RawBody ??
31
+ normalized.Body);
28
32
  normalized.BodyForAgent = normalizeInboundTextNewlines(bodyForAgentSource);
29
33
  const bodyForCommandsSource = opts.forceBodyForCommands
30
34
  ? (normalized.CommandBody ?? normalized.RawBody ?? normalized.Body)
@@ -73,7 +73,7 @@ export function matchesMentionWithExplicit(params) {
73
73
  const explicitAvailable = params.explicit?.canResolveExplicit === true;
74
74
  const hasAnyMention = params.explicit?.hasAnyMention === true;
75
75
  if (hasAnyMention && explicitAvailable)
76
- return explicit;
76
+ return explicit || params.mentionRegexes.some((re) => re.test(cleaned));
77
77
  if (!cleaned)
78
78
  return explicit;
79
79
  return explicit || params.mentionRegexes.some((re) => re.test(cleaned));
@@ -65,7 +65,7 @@ function resolveParentSessionKeyCandidate(params) {
65
65
  return derived;
66
66
  return null;
67
67
  }
68
- function resolveStoredModelOverride(params) {
68
+ export function resolveStoredModelOverride(params) {
69
69
  const direct = resolveModelOverrideFromEntry(params.sessionEntry);
70
70
  if (direct)
71
71
  return { ...direct, source: "session" };
@@ -327,9 +327,9 @@ export function resolveModelDirectiveSelection(params) {
327
327
  defaultProvider,
328
328
  defaultModel,
329
329
  });
330
- return { candidate, ...details };
330
+ return Object.assign({ candidate }, details);
331
331
  })
332
- .sort((a, b) => {
332
+ .toSorted((a, b) => {
333
333
  if (b.score !== a.score)
334
334
  return b.score - a.score;
335
335
  if (a.isDefault !== b.isDefault)
@@ -1,9 +1,11 @@
1
1
  function normalizeProviderId(provider) {
2
- if (!provider)
2
+ if (!provider) {
3
3
  return "";
4
+ }
4
5
  const normalized = provider.trim().toLowerCase();
5
- if (normalized === "z.ai" || normalized === "z-ai")
6
+ if (normalized === "z.ai" || normalized === "z-ai") {
6
7
  return "zai";
8
+ }
7
9
  return normalized;
8
10
  }
9
11
  export function isBinaryThinkingProvider(provider) {
@@ -11,38 +13,52 @@ export function isBinaryThinkingProvider(provider) {
11
13
  }
12
14
  export const XHIGH_MODEL_REFS = [
13
15
  "openai/gpt-5.2",
16
+ "openai-codex/gpt-5.3-codex",
14
17
  "openai-codex/gpt-5.2-codex",
15
18
  "openai-codex/gpt-5.1-codex",
19
+ "github-copilot/gpt-5.2-codex",
20
+ "github-copilot/gpt-5.2",
16
21
  ];
17
22
  const XHIGH_MODEL_SET = new Set(XHIGH_MODEL_REFS.map((entry) => entry.toLowerCase()));
18
23
  const XHIGH_MODEL_IDS = new Set(XHIGH_MODEL_REFS.map((entry) => entry.split("/")[1]?.toLowerCase()).filter((entry) => Boolean(entry)));
19
24
  // Normalize user-provided thinking level strings to the canonical enum.
20
25
  export function normalizeThinkLevel(raw) {
21
- if (!raw)
26
+ if (!raw) {
22
27
  return undefined;
23
- const key = raw.toLowerCase();
24
- if (["off"].includes(key))
28
+ }
29
+ const key = raw.trim().toLowerCase();
30
+ const collapsed = key.replace(/[\s_-]+/g, "");
31
+ if (collapsed === "xhigh" || collapsed === "extrahigh") {
32
+ return "xhigh";
33
+ }
34
+ if (["off"].includes(key)) {
25
35
  return "off";
26
- if (["on", "enable", "enabled"].includes(key))
36
+ }
37
+ if (["on", "enable", "enabled"].includes(key)) {
27
38
  return "low";
28
- if (["min", "minimal"].includes(key))
39
+ }
40
+ if (["min", "minimal"].includes(key)) {
29
41
  return "minimal";
30
- if (["low", "thinkhard", "think-hard", "think_hard"].includes(key))
42
+ }
43
+ if (["low", "thinkhard", "think-hard", "think_hard"].includes(key)) {
31
44
  return "low";
32
- if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key))
45
+ }
46
+ if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key)) {
33
47
  return "medium";
34
- if (["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest", "max"].includes(key))
48
+ }
49
+ if (["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest", "max"].includes(key)) {
35
50
  return "high";
36
- if (["xhigh", "x-high", "x_high"].includes(key))
37
- return "xhigh";
38
- if (["think"].includes(key))
51
+ }
52
+ if (["think"].includes(key)) {
39
53
  return "minimal";
54
+ }
40
55
  return undefined;
41
56
  }
42
57
  export function supportsXHighThinking(provider, model) {
43
58
  const modelKey = model?.trim().toLowerCase();
44
- if (!modelKey)
59
+ if (!modelKey) {
45
60
  return false;
61
+ }
46
62
  const providerKey = provider?.trim().toLowerCase();
47
63
  if (providerKey) {
48
64
  return XHIGH_MODEL_SET.has(`${providerKey}/${modelKey}`);
@@ -51,13 +67,15 @@ export function supportsXHighThinking(provider, model) {
51
67
  }
52
68
  export function listThinkingLevels(provider, model) {
53
69
  const levels = ["off", "minimal", "low", "medium", "high"];
54
- if (supportsXHighThinking(provider, model))
70
+ if (supportsXHighThinking(provider, model)) {
55
71
  levels.push("xhigh");
72
+ }
56
73
  return levels;
57
74
  }
58
75
  export function listThinkingLevelLabels(provider, model) {
59
- if (isBinaryThinkingProvider(provider))
76
+ if (isBinaryThinkingProvider(provider)) {
60
77
  return ["off", "on"];
78
+ }
61
79
  return listThinkingLevels(provider, model);
62
80
  }
63
81
  export function formatThinkingLevels(provider, model, separator = ", ") {
@@ -65,53 +83,69 @@ export function formatThinkingLevels(provider, model, separator = ", ") {
65
83
  }
66
84
  export function formatXHighModelHint() {
67
85
  const refs = [...XHIGH_MODEL_REFS];
68
- if (refs.length === 0)
86
+ if (refs.length === 0) {
69
87
  return "unknown model";
70
- if (refs.length === 1)
88
+ }
89
+ if (refs.length === 1) {
71
90
  return refs[0];
72
- if (refs.length === 2)
91
+ }
92
+ if (refs.length === 2) {
73
93
  return `${refs[0]} or ${refs[1]}`;
94
+ }
74
95
  return `${refs.slice(0, -1).join(", ")} or ${refs[refs.length - 1]}`;
75
96
  }
76
97
  // Normalize verbose flags used to toggle agent verbosity.
77
98
  export function normalizeVerboseLevel(raw) {
78
- if (!raw)
99
+ if (!raw) {
79
100
  return undefined;
101
+ }
80
102
  const key = raw.toLowerCase();
81
- if (["off", "false", "no", "0"].includes(key))
103
+ if (["off", "false", "no", "0"].includes(key)) {
82
104
  return "off";
83
- if (["full", "all", "everything"].includes(key))
105
+ }
106
+ if (["full", "all", "everything"].includes(key)) {
84
107
  return "full";
85
- if (["on", "minimal", "true", "yes", "1"].includes(key))
108
+ }
109
+ if (["on", "minimal", "true", "yes", "1"].includes(key)) {
86
110
  return "on";
111
+ }
87
112
  return undefined;
88
113
  }
89
114
  // Normalize system notice flags used to toggle system notifications.
90
115
  export function normalizeNoticeLevel(raw) {
91
- if (!raw)
116
+ if (!raw) {
92
117
  return undefined;
118
+ }
93
119
  const key = raw.toLowerCase();
94
- if (["off", "false", "no", "0"].includes(key))
120
+ if (["off", "false", "no", "0"].includes(key)) {
95
121
  return "off";
96
- if (["full", "all", "everything"].includes(key))
122
+ }
123
+ if (["full", "all", "everything"].includes(key)) {
97
124
  return "full";
98
- if (["on", "minimal", "true", "yes", "1"].includes(key))
125
+ }
126
+ if (["on", "minimal", "true", "yes", "1"].includes(key)) {
99
127
  return "on";
128
+ }
100
129
  return undefined;
101
130
  }
102
131
  // Normalize response-usage display modes used to toggle per-response usage footers.
103
132
  export function normalizeUsageDisplay(raw) {
104
- if (!raw)
133
+ if (!raw) {
105
134
  return undefined;
135
+ }
106
136
  const key = raw.toLowerCase();
107
- if (["off", "false", "no", "0", "disable", "disabled"].includes(key))
137
+ if (["off", "false", "no", "0", "disable", "disabled"].includes(key)) {
108
138
  return "off";
109
- if (["on", "true", "yes", "1", "enable", "enabled"].includes(key))
139
+ }
140
+ if (["on", "true", "yes", "1", "enable", "enabled"].includes(key)) {
110
141
  return "tokens";
111
- if (["tokens", "token", "tok", "minimal", "min"].includes(key))
142
+ }
143
+ if (["tokens", "token", "tok", "minimal", "min"].includes(key)) {
112
144
  return "tokens";
113
- if (["full", "session"].includes(key))
145
+ }
146
+ if (["full", "session"].includes(key)) {
114
147
  return "full";
148
+ }
115
149
  return undefined;
116
150
  }
117
151
  export function resolveResponseUsageMode(raw) {
@@ -119,36 +153,47 @@ export function resolveResponseUsageMode(raw) {
119
153
  }
120
154
  // Normalize elevated flags used to toggle elevated bash permissions.
121
155
  export function normalizeElevatedLevel(raw) {
122
- if (!raw)
156
+ if (!raw) {
123
157
  return undefined;
158
+ }
124
159
  const key = raw.toLowerCase();
125
- if (["off", "false", "no", "0"].includes(key))
160
+ if (["off", "false", "no", "0"].includes(key)) {
126
161
  return "off";
127
- if (["full", "auto", "auto-approve", "autoapprove"].includes(key))
162
+ }
163
+ if (["full", "auto", "auto-approve", "autoapprove"].includes(key)) {
128
164
  return "full";
129
- if (["ask", "prompt", "approval", "approve"].includes(key))
165
+ }
166
+ if (["ask", "prompt", "approval", "approve"].includes(key)) {
130
167
  return "ask";
131
- if (["on", "true", "yes", "1"].includes(key))
168
+ }
169
+ if (["on", "true", "yes", "1"].includes(key)) {
132
170
  return "on";
171
+ }
133
172
  return undefined;
134
173
  }
135
174
  export function resolveElevatedMode(level) {
136
- if (!level || level === "off")
175
+ if (!level || level === "off") {
137
176
  return "off";
138
- if (level === "full")
177
+ }
178
+ if (level === "full") {
139
179
  return "full";
180
+ }
140
181
  return "ask";
141
182
  }
142
183
  // Normalize reasoning visibility flags used to toggle reasoning exposure.
143
184
  export function normalizeReasoningLevel(raw) {
144
- if (!raw)
185
+ if (!raw) {
145
186
  return undefined;
187
+ }
146
188
  const key = raw.toLowerCase();
147
- if (["off", "false", "no", "0", "hide", "hidden", "disable", "disabled"].includes(key))
189
+ if (["off", "false", "no", "0", "hide", "hidden", "disable", "disabled"].includes(key)) {
148
190
  return "off";
149
- if (["on", "true", "yes", "1", "show", "visible", "enable", "enabled"].includes(key))
191
+ }
192
+ if (["on", "true", "yes", "1", "show", "visible", "enable", "enabled"].includes(key)) {
150
193
  return "on";
151
- if (["stream", "streaming", "draft", "live"].includes(key))
194
+ }
195
+ if (["stream", "streaming", "draft", "live"].includes(key)) {
152
196
  return "stream";
197
+ }
153
198
  return undefined;
154
199
  }
@@ -6,6 +6,19 @@ export async function startBrowserBridgeServer(params) {
6
6
  const port = params.port ?? 0;
7
7
  const app = express();
8
8
  app.use(express.json({ limit: "1mb" }));
9
+ app.use((req, res, next) => {
10
+ const ctrl = new AbortController();
11
+ const abort = () => ctrl.abort(new Error("request aborted"));
12
+ req.once("aborted", abort);
13
+ res.once("close", () => {
14
+ if (!res.writableEnded) {
15
+ abort();
16
+ }
17
+ });
18
+ // Make the signal available to browser route handlers (best-effort).
19
+ req.signal = ctrl.signal;
20
+ next();
21
+ });
9
22
  const authToken = params.authToken?.trim();
10
23
  if (authToken) {
11
24
  app.use((req, res, next) => {
@@ -1,30 +1,26 @@
1
1
  import WebSocket from "ws";
2
+ import { isLoopbackHost } from "../gateway/net.js";
2
3
  import { rawDataToString } from "../infra/ws.js";
3
- export function isLoopbackHost(host) {
4
- const h = host.trim().toLowerCase();
5
- return (h === "localhost" ||
6
- h === "127.0.0.1" ||
7
- h === "0.0.0.0" ||
8
- h === "[::1]" ||
9
- h === "::1" ||
10
- h === "[::]" ||
11
- h === "::");
12
- }
4
+ import { getChromeExtensionRelayAuthHeaders } from "./extension-relay.js";
5
+ export { isLoopbackHost };
13
6
  export function getHeadersWithAuth(url, headers = {}) {
7
+ const relayHeaders = getChromeExtensionRelayAuthHeaders(url);
8
+ const mergedHeaders = { ...relayHeaders, ...headers };
14
9
  try {
15
10
  const parsed = new URL(url);
16
- const hasAuthHeader = Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
17
- if (hasAuthHeader)
18
- return headers;
11
+ const hasAuthHeader = Object.keys(mergedHeaders).some((key) => key.toLowerCase() === "authorization");
12
+ if (hasAuthHeader) {
13
+ return mergedHeaders;
14
+ }
19
15
  if (parsed.username || parsed.password) {
20
16
  const auth = Buffer.from(`${parsed.username}:${parsed.password}`).toString("base64");
21
- return { ...headers, Authorization: `Basic ${auth}` };
17
+ return { ...mergedHeaders, Authorization: `Basic ${auth}` };
22
18
  }
23
19
  }
24
20
  catch {
25
21
  // ignore
26
22
  }
27
- return headers;
23
+ return mergedHeaders;
28
24
  }
29
25
  export function appendCdpPath(cdpUrl, path) {
30
26
  const url = new URL(cdpUrl);
@@ -36,17 +32,18 @@ export function appendCdpPath(cdpUrl, path) {
36
32
  function createCdpSender(ws) {
37
33
  let nextId = 1;
38
34
  const pending = new Map();
39
- const send = (method, params) => {
35
+ const send = (method, params, sessionId) => {
40
36
  const id = nextId++;
41
- const msg = { id, method, params };
37
+ const msg = { id, method, params, sessionId };
42
38
  ws.send(JSON.stringify(msg));
43
39
  return new Promise((resolve, reject) => {
44
40
  pending.set(id, { resolve, reject });
45
41
  });
46
42
  };
47
43
  const closeWithError = (err) => {
48
- for (const [, p] of pending)
44
+ for (const [, p] of pending) {
49
45
  p.reject(err);
46
+ }
50
47
  pending.clear();
51
48
  try {
52
49
  ws.close();
@@ -55,14 +52,19 @@ function createCdpSender(ws) {
55
52
  // ignore
56
53
  }
57
54
  };
55
+ ws.on("error", (err) => {
56
+ closeWithError(err instanceof Error ? err : new Error(String(err)));
57
+ });
58
58
  ws.on("message", (data) => {
59
59
  try {
60
60
  const parsed = JSON.parse(rawDataToString(data));
61
- if (typeof parsed.id !== "number")
61
+ if (typeof parsed.id !== "number") {
62
62
  return;
63
+ }
63
64
  const p = pending.get(parsed.id);
64
- if (!p)
65
+ if (!p) {
65
66
  return;
67
+ }
66
68
  pending.delete(parsed.id);
67
69
  if (parsed.error?.message) {
68
70
  p.reject(new Error(parsed.error.message));
@@ -85,8 +87,9 @@ export async function fetchJson(url, timeoutMs = 1500, init) {
85
87
  try {
86
88
  const headers = getHeadersWithAuth(url, init?.headers || {});
87
89
  const res = await fetch(url, { ...init, headers, signal: ctrl.signal });
88
- if (!res.ok)
90
+ if (!res.ok) {
89
91
  throw new Error(`HTTP ${res.status}`);
92
+ }
90
93
  return (await res.json());
91
94
  }
92
95
  finally {
@@ -99,8 +102,9 @@ export async function fetchOk(url, timeoutMs = 1500, init) {
99
102
  try {
100
103
  const headers = getHeadersWithAuth(url, init?.headers || {});
101
104
  const res = await fetch(url, { ...init, headers, signal: ctrl.signal });
102
- if (!res.ok)
105
+ if (!res.ok) {
103
106
  throw new Error(`HTTP ${res.status}`);
107
+ }
104
108
  }
105
109
  finally {
106
110
  clearTimeout(t);
@@ -108,16 +112,26 @@ export async function fetchOk(url, timeoutMs = 1500, init) {
108
112
  }
109
113
  export async function withCdpSocket(wsUrl, fn, opts) {
110
114
  const headers = getHeadersWithAuth(wsUrl, opts?.headers ?? {});
115
+ const handshakeTimeoutMs = typeof opts?.handshakeTimeoutMs === "number" && Number.isFinite(opts.handshakeTimeoutMs)
116
+ ? Math.max(1, Math.floor(opts.handshakeTimeoutMs))
117
+ : 5000;
111
118
  const ws = new WebSocket(wsUrl, {
112
- handshakeTimeout: 5000,
119
+ handshakeTimeout: handshakeTimeoutMs,
113
120
  ...(Object.keys(headers).length ? { headers } : {}),
114
121
  });
115
122
  const { send, closeWithError } = createCdpSender(ws);
116
123
  const openPromise = new Promise((resolve, reject) => {
117
124
  ws.once("open", () => resolve());
118
125
  ws.once("error", (err) => reject(err));
126
+ ws.once("close", () => reject(new Error("CDP socket closed")));
119
127
  });
120
- await openPromise;
128
+ try {
129
+ await openPromise;
130
+ }
131
+ catch (err) {
132
+ closeWithError(err instanceof Error ? err : new Error(String(err)));
133
+ throw err;
134
+ }
121
135
  try {
122
136
  return await fn(send);
123
137
  }