@poolzin/pool-bot 2026.2.23 → 2026.2.25

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 (235) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/acp/client.js +207 -18
  3. package/dist/acp/secret-file.js +22 -0
  4. package/dist/agents/agent-scope.js +10 -0
  5. package/dist/agents/bash-process-registry.test-helpers.js +29 -0
  6. package/dist/agents/bash-tools.exec-approval-request.js +20 -0
  7. package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
  8. package/dist/agents/bash-tools.exec-host-node.js +235 -0
  9. package/dist/agents/bash-tools.exec-types.js +1 -0
  10. package/dist/agents/bash-tools.process.js +224 -218
  11. package/dist/agents/content-blocks.js +16 -0
  12. package/dist/agents/model-fallback.js +96 -101
  13. package/dist/agents/models-config.providers.js +299 -182
  14. package/dist/agents/pi-embedded-payloads.js +1 -0
  15. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
  16. package/dist/agents/skills.test-helpers.js +13 -0
  17. package/dist/agents/stable-stringify.js +12 -0
  18. package/dist/agents/subagent-registry.mocks.shared.js +12 -0
  19. package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
  20. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
  21. package/dist/agents/tool-policy-shared.js +108 -0
  22. package/dist/agents/tools/browser-tool.js +160 -54
  23. package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
  24. package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
  25. package/dist/agents/tools/image-tool.js +214 -99
  26. package/dist/agents/tools/sessions-history-tool.js +140 -108
  27. package/dist/agents/workspace.js +222 -46
  28. package/dist/auto-reply/commands-registry.js +15 -18
  29. package/dist/auto-reply/fallback-state.js +114 -0
  30. package/dist/auto-reply/model-runtime.js +68 -0
  31. package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
  32. package/dist/auto-reply/reply/agent-runner.js +165 -39
  33. package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
  34. package/dist/browser/config.js +26 -0
  35. package/dist/browser/navigation-guard.js +31 -0
  36. package/dist/browser/routes/agent.act.js +431 -424
  37. package/dist/browser/routes/agent.shared.js +47 -3
  38. package/dist/browser/routes/agent.snapshot.js +122 -116
  39. package/dist/browser/routes/agent.storage.js +303 -297
  40. package/dist/browser/routes/tabs.js +154 -100
  41. package/dist/browser/server-lifecycle.js +37 -0
  42. package/dist/build-info.json +3 -3
  43. package/dist/channels/allow-from.js +25 -0
  44. package/dist/channels/plugins/account-action-gate.js +13 -0
  45. package/dist/channels/plugins/message-actions.js +10 -0
  46. package/dist/channels/telegram/api.js +18 -0
  47. package/dist/cli/argv.js +84 -21
  48. package/dist/cli/banner.js +2 -1
  49. package/dist/cli/exec-approvals-cli.js +92 -124
  50. package/dist/cli/memory-cli.js +158 -61
  51. package/dist/cli/nodes-cli/register.push.js +63 -0
  52. package/dist/cli/nodes-media-utils.js +21 -0
  53. package/dist/cli/plugins-cli.js +245 -61
  54. package/dist/cli/program/build-program.js +3 -1
  55. package/dist/cli/program/command-registry.js +223 -136
  56. package/dist/cli/program/help.js +43 -12
  57. package/dist/cli/route.js +1 -1
  58. package/dist/cli/test-runtime-capture.js +24 -0
  59. package/dist/commands/agent.js +163 -87
  60. package/dist/commands/channels.mock-harness.js +23 -0
  61. package/dist/commands/daemon-install-runtime-warning.js +11 -0
  62. package/dist/commands/onboard-helpers.js +4 -4
  63. package/dist/commands/sessions.test-helpers.js +61 -0
  64. package/dist/compat/legacy-names.js +2 -2
  65. package/dist/config/commands.js +3 -0
  66. package/dist/config/config.js +1 -1
  67. package/dist/config/env-substitution.js +62 -34
  68. package/dist/config/env-vars.js +9 -0
  69. package/dist/config/io.js +571 -171
  70. package/dist/config/merge-patch.js +50 -4
  71. package/dist/config/redact-snapshot.js +404 -76
  72. package/dist/config/schema.js +58 -570
  73. package/dist/config/validation.js +140 -85
  74. package/dist/config/zod-schema.hooks.js +40 -11
  75. package/dist/config/zod-schema.installs.js +20 -0
  76. package/dist/config/zod-schema.js +8 -7
  77. package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
  78. package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
  79. package/dist/control-ui/index.html +1 -1
  80. package/dist/daemon/cmd-argv.js +21 -0
  81. package/dist/daemon/cmd-set.js +58 -0
  82. package/dist/daemon/service-types.js +1 -0
  83. package/dist/discord/monitor/exec-approvals.js +357 -162
  84. package/dist/gateway/auth.js +38 -3
  85. package/dist/gateway/call.js +149 -68
  86. package/dist/gateway/canvas-capability.js +75 -0
  87. package/dist/gateway/control-plane-audit.js +28 -0
  88. package/dist/gateway/control-plane-rate-limit.js +53 -0
  89. package/dist/gateway/events.js +1 -0
  90. package/dist/gateway/hooks.js +109 -54
  91. package/dist/gateway/http-common.js +22 -0
  92. package/dist/gateway/method-scopes.js +169 -0
  93. package/dist/gateway/net.js +23 -0
  94. package/dist/gateway/openresponses-http.js +120 -110
  95. package/dist/gateway/probe-auth.js +2 -0
  96. package/dist/gateway/protocol/index.js +3 -2
  97. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
  98. package/dist/gateway/protocol/schema/push.js +18 -0
  99. package/dist/gateway/protocol/schema.js +1 -0
  100. package/dist/gateway/server-http.js +236 -52
  101. package/dist/gateway/server-methods/agent.js +162 -24
  102. package/dist/gateway/server-methods/chat.js +461 -130
  103. package/dist/gateway/server-methods/config.js +193 -150
  104. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  105. package/dist/gateway/server-methods/nodes.js +251 -69
  106. package/dist/gateway/server-methods/push.js +53 -0
  107. package/dist/gateway/server-reload-handlers.js +2 -3
  108. package/dist/gateway/server-runtime-config.js +5 -0
  109. package/dist/gateway/server-runtime-state.js +2 -0
  110. package/dist/gateway/server-ws-runtime.js +1 -0
  111. package/dist/gateway/server.impl.js +296 -139
  112. package/dist/gateway/session-preview.test-helpers.js +11 -0
  113. package/dist/gateway/startup-auth.js +126 -0
  114. package/dist/gateway/test-helpers.agent-results.js +15 -0
  115. package/dist/gateway/test-helpers.mocks.js +37 -14
  116. package/dist/gateway/test-helpers.server.js +161 -77
  117. package/dist/hooks/bundled/session-memory/handler.js +165 -34
  118. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  119. package/dist/infra/archive-path.js +49 -0
  120. package/dist/infra/device-pairing.js +148 -167
  121. package/dist/infra/exec-approvals-allowlist.js +19 -70
  122. package/dist/infra/exec-approvals-analysis.js +44 -17
  123. package/dist/infra/exec-safe-bin-policy.js +269 -0
  124. package/dist/infra/fixed-window-rate-limit.js +33 -0
  125. package/dist/infra/git-root.js +61 -0
  126. package/dist/infra/heartbeat-active-hours.js +2 -2
  127. package/dist/infra/heartbeat-reason.js +40 -0
  128. package/dist/infra/heartbeat-runner.js +72 -32
  129. package/dist/infra/install-source-utils.js +91 -7
  130. package/dist/infra/node-pairing.js +50 -105
  131. package/dist/infra/npm-integrity.js +45 -0
  132. package/dist/infra/npm-pack-install.js +40 -0
  133. package/dist/infra/outbound/channel-adapters.js +20 -7
  134. package/dist/infra/outbound/message-action-runner.js +107 -327
  135. package/dist/infra/outbound/message.js +59 -36
  136. package/dist/infra/outbound/outbound-policy.js +52 -25
  137. package/dist/infra/outbound/outbound-send-service.js +58 -71
  138. package/dist/infra/pairing-files.js +10 -0
  139. package/dist/infra/plain-object.js +9 -0
  140. package/dist/infra/push-apns.js +365 -0
  141. package/dist/infra/restart-sentinel.js +16 -1
  142. package/dist/infra/restart.js +229 -26
  143. package/dist/infra/scp-host.js +54 -0
  144. package/dist/infra/update-startup.js +86 -9
  145. package/dist/media/inbound-path-policy.js +114 -0
  146. package/dist/media/input-files.js +16 -0
  147. package/dist/memory/test-manager.js +8 -0
  148. package/dist/plugin-sdk/temp-path.js +47 -0
  149. package/dist/plugins/discovery.js +217 -23
  150. package/dist/plugins/hook-runner-global.js +16 -0
  151. package/dist/plugins/loader.js +192 -26
  152. package/dist/plugins/logger.js +8 -0
  153. package/dist/plugins/manifest-registry.js +3 -0
  154. package/dist/plugins/path-safety.js +34 -0
  155. package/dist/plugins/registry.js +5 -2
  156. package/dist/plugins/runtime/index.js +271 -206
  157. package/dist/providers/github-copilot-models.js +4 -1
  158. package/dist/security/audit-channel.js +8 -19
  159. package/dist/security/audit-extra.async.js +354 -182
  160. package/dist/security/audit-extra.js +11 -1
  161. package/dist/security/audit-extra.sync.js +340 -33
  162. package/dist/security/audit-fs.js +31 -13
  163. package/dist/security/audit.js +145 -371
  164. package/dist/security/dm-policy-shared.js +24 -0
  165. package/dist/security/external-content.js +20 -8
  166. package/dist/security/fix.js +49 -85
  167. package/dist/security/scan-paths.js +20 -0
  168. package/dist/security/secret-equal.js +3 -7
  169. package/dist/security/windows-acl.js +30 -15
  170. package/dist/shared/node-list-parse.js +13 -0
  171. package/dist/shared/operator-scope-compat.js +37 -0
  172. package/dist/shared/text-chunking.js +29 -0
  173. package/dist/slack/blocks.test-helpers.js +31 -0
  174. package/dist/slack/monitor/mrkdwn.js +8 -0
  175. package/dist/telegram/bot-message-dispatch.js +366 -164
  176. package/dist/telegram/draft-stream.js +30 -7
  177. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  178. package/dist/terminal/prompt-select-styled.js +9 -0
  179. package/dist/test-utils/command-runner.js +6 -0
  180. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  181. package/dist/test-utils/model-auth-mock.js +12 -0
  182. package/dist/test-utils/provider-usage-fetch.js +14 -0
  183. package/dist/test-utils/temp-home.js +33 -0
  184. package/dist/tui/components/chat-log.js +9 -0
  185. package/dist/tui/tui-command-handlers.js +36 -27
  186. package/dist/tui/tui-event-handlers.js +122 -32
  187. package/dist/tui/tui.js +181 -45
  188. package/dist/utils/mask-api-key.js +10 -0
  189. package/dist/utils/run-with-concurrency.js +39 -0
  190. package/dist/web/media.js +4 -0
  191. package/docs/tools/slash-commands.md +5 -1
  192. package/extensions/bluebubbles/package.json +1 -1
  193. package/extensions/copilot-proxy/package.json +1 -1
  194. package/extensions/diagnostics-otel/package.json +1 -1
  195. package/extensions/discord/package.json +1 -1
  196. package/extensions/feishu/package.json +1 -1
  197. package/extensions/feishu/src/external-keys.ts +19 -0
  198. package/extensions/google-antigravity-auth/package.json +1 -1
  199. package/extensions/google-gemini-cli-auth/package.json +1 -1
  200. package/extensions/googlechat/package.json +1 -1
  201. package/extensions/imessage/package.json +1 -1
  202. package/extensions/irc/package.json +1 -1
  203. package/extensions/line/package.json +1 -1
  204. package/extensions/llm-task/package.json +1 -1
  205. package/extensions/lobster/package.json +1 -1
  206. package/extensions/lobster/src/windows-spawn.ts +193 -0
  207. package/extensions/matrix/CHANGELOG.md +5 -0
  208. package/extensions/matrix/package.json +1 -1
  209. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  210. package/extensions/mattermost/package.json +1 -1
  211. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  212. package/extensions/memory-core/package.json +1 -1
  213. package/extensions/memory-lancedb/package.json +1 -1
  214. package/extensions/minimax-portal-auth/package.json +1 -1
  215. package/extensions/msteams/CHANGELOG.md +5 -0
  216. package/extensions/msteams/package.json +1 -1
  217. package/extensions/nextcloud-talk/package.json +1 -1
  218. package/extensions/nostr/CHANGELOG.md +5 -0
  219. package/extensions/nostr/package.json +1 -1
  220. package/extensions/open-prose/package.json +1 -1
  221. package/extensions/openai-codex-auth/package.json +1 -1
  222. package/extensions/signal/package.json +1 -1
  223. package/extensions/slack/package.json +1 -1
  224. package/extensions/telegram/package.json +1 -1
  225. package/extensions/tlon/package.json +1 -1
  226. package/extensions/twitch/CHANGELOG.md +5 -0
  227. package/extensions/twitch/package.json +1 -1
  228. package/extensions/voice-call/CHANGELOG.md +5 -0
  229. package/extensions/voice-call/package.json +1 -1
  230. package/extensions/whatsapp/package.json +1 -1
  231. package/extensions/zalo/CHANGELOG.md +5 -0
  232. package/extensions/zalo/package.json +1 -1
  233. package/extensions/zalouser/CHANGELOG.md +5 -0
  234. package/extensions/zalouser/package.json +1 -1
  235. package/package.json +1 -1
@@ -4,6 +4,7 @@ import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/registry.js";
4
4
  import { normalizePluginsConfig, resolveEnableState, resolveMemorySlotDecision, } from "../plugins/config-state.js";
5
5
  import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
6
6
  import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
7
+ import { isRecord } from "../utils.js";
7
8
  import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-dirs.js";
8
9
  import { applyAgentDefaults, applyModelDefaults, applySessionDefaults } from "./defaults.js";
9
10
  import { findLegacyConfigIssues } from "./legacy.js";
@@ -16,28 +17,35 @@ function isWorkspaceAvatarPath(value, workspaceDir) {
16
17
  const workspaceRoot = path.resolve(workspaceDir);
17
18
  const resolved = path.resolve(workspaceRoot, value);
18
19
  const relative = path.relative(workspaceRoot, resolved);
19
- if (relative === "")
20
+ if (relative === "") {
20
21
  return true;
21
- if (relative.startsWith(".."))
22
+ }
23
+ if (relative.startsWith("..")) {
22
24
  return false;
25
+ }
23
26
  return !path.isAbsolute(relative);
24
27
  }
25
28
  function validateIdentityAvatar(config) {
26
29
  const agents = config.agents?.list;
27
- if (!Array.isArray(agents) || agents.length === 0)
30
+ if (!Array.isArray(agents) || agents.length === 0) {
28
31
  return [];
32
+ }
29
33
  const issues = [];
30
34
  for (const [index, entry] of agents.entries()) {
31
- if (!entry || typeof entry !== "object")
35
+ if (!entry || typeof entry !== "object") {
32
36
  continue;
37
+ }
33
38
  const avatarRaw = entry.identity?.avatar;
34
- if (typeof avatarRaw !== "string")
39
+ if (typeof avatarRaw !== "string") {
35
40
  continue;
41
+ }
36
42
  const avatar = avatarRaw.trim();
37
- if (!avatar)
43
+ if (!avatar) {
38
44
  continue;
39
- if (AVATAR_DATA_RE.test(avatar) || AVATAR_HTTP_RE.test(avatar))
45
+ }
46
+ if (AVATAR_DATA_RE.test(avatar) || AVATAR_HTTP_RE.test(avatar)) {
40
47
  continue;
48
+ }
41
49
  if (avatar.startsWith("~")) {
42
50
  issues.push({
43
51
  path: `agents.list.${index}.identity.avatar`,
@@ -63,7 +71,11 @@ function validateIdentityAvatar(config) {
63
71
  }
64
72
  return issues;
65
73
  }
66
- export function validateConfigObject(raw) {
74
+ /**
75
+ * Validates config without applying runtime defaults.
76
+ * Use this when you need the raw validated config (e.g., for writing back to file).
77
+ */
78
+ export function validateConfigObjectRaw(raw) {
67
79
  const legacyIssues = findLegacyConfigIssues(raw);
68
80
  if (legacyIssues.length > 0) {
69
81
  return {
@@ -102,42 +114,136 @@ export function validateConfigObject(raw) {
102
114
  }
103
115
  return {
104
116
  ok: true,
105
- config: applyModelDefaults(applyAgentDefaults(applySessionDefaults(validated.data))),
117
+ config: validated.data,
106
118
  };
107
119
  }
108
- function isRecord(value) {
109
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
120
+ export function validateConfigObject(raw) {
121
+ const result = validateConfigObjectRaw(raw);
122
+ if (!result.ok) {
123
+ return result;
124
+ }
125
+ return {
126
+ ok: true,
127
+ config: applyModelDefaults(applyAgentDefaults(applySessionDefaults(result.config))),
128
+ };
110
129
  }
111
130
  export function validateConfigObjectWithPlugins(raw) {
112
- const base = validateConfigObject(raw);
131
+ return validateConfigObjectWithPluginsBase(raw, { applyDefaults: true });
132
+ }
133
+ export function validateConfigObjectRawWithPlugins(raw) {
134
+ return validateConfigObjectWithPluginsBase(raw, { applyDefaults: false });
135
+ }
136
+ function validateConfigObjectWithPluginsBase(raw, opts) {
137
+ const base = opts.applyDefaults ? validateConfigObject(raw) : validateConfigObjectRaw(raw);
113
138
  if (!base.ok) {
114
139
  return { ok: false, issues: base.issues, warnings: [] };
115
140
  }
116
141
  const config = base.config;
117
142
  const issues = [];
118
143
  const warnings = [];
119
- const pluginsConfig = config.plugins;
120
- const normalizedPlugins = normalizePluginsConfig(pluginsConfig);
121
- const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
122
- const registry = loadPluginManifestRegistry({
123
- config,
124
- workspaceDir: workspaceDir ?? undefined,
125
- });
126
- const knownIds = new Set(registry.plugins.map((record) => record.id));
127
- for (const diag of registry.diagnostics) {
128
- let path = diag.pluginId ? `plugins.entries.${diag.pluginId}` : "plugins";
129
- if (!diag.pluginId && diag.message.includes("plugin path not found")) {
130
- path = "plugins.load.paths";
144
+ const hasExplicitPluginsConfig = isRecord(raw) && Object.prototype.hasOwnProperty.call(raw, "plugins");
145
+ let registryInfo = null;
146
+ const ensureRegistry = () => {
147
+ if (registryInfo) {
148
+ return registryInfo;
149
+ }
150
+ const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
151
+ const registry = loadPluginManifestRegistry({
152
+ config,
153
+ workspaceDir: workspaceDir ?? undefined,
154
+ });
155
+ const knownIds = new Set(registry.plugins.map((record) => record.id));
156
+ const normalizedPlugins = normalizePluginsConfig(config.plugins);
157
+ for (const diag of registry.diagnostics) {
158
+ let path = diag.pluginId ? `plugins.entries.${diag.pluginId}` : "plugins";
159
+ if (!diag.pluginId && diag.message.includes("plugin path not found")) {
160
+ path = "plugins.load.paths";
161
+ }
162
+ const pluginLabel = diag.pluginId ? `plugin ${diag.pluginId}` : "plugin";
163
+ const message = `${pluginLabel}: ${diag.message}`;
164
+ if (diag.level === "error") {
165
+ issues.push({ path, message });
166
+ }
167
+ else {
168
+ warnings.push({ path, message });
169
+ }
170
+ }
171
+ registryInfo = { registry, knownIds, normalizedPlugins };
172
+ return registryInfo;
173
+ };
174
+ const allowedChannels = new Set(["defaults", ...CHANNEL_IDS]);
175
+ if (config.channels && isRecord(config.channels)) {
176
+ for (const key of Object.keys(config.channels)) {
177
+ const trimmed = key.trim();
178
+ if (!trimmed) {
179
+ continue;
180
+ }
181
+ if (!allowedChannels.has(trimmed)) {
182
+ const { registry } = ensureRegistry();
183
+ for (const record of registry.plugins) {
184
+ for (const channelId of record.channels) {
185
+ allowedChannels.add(channelId);
186
+ }
187
+ }
188
+ }
189
+ if (!allowedChannels.has(trimmed)) {
190
+ issues.push({
191
+ path: `channels.${trimmed}`,
192
+ message: `unknown channel id: ${trimmed}`,
193
+ });
194
+ }
195
+ }
196
+ }
197
+ const heartbeatChannelIds = new Set();
198
+ for (const channelId of CHANNEL_IDS) {
199
+ heartbeatChannelIds.add(channelId.toLowerCase());
200
+ }
201
+ const validateHeartbeatTarget = (target, path) => {
202
+ if (typeof target !== "string") {
203
+ return;
204
+ }
205
+ const trimmed = target.trim();
206
+ if (!trimmed) {
207
+ issues.push({ path, message: "heartbeat target must not be empty" });
208
+ return;
209
+ }
210
+ const normalized = trimmed.toLowerCase();
211
+ if (normalized === "last" || normalized === "none") {
212
+ return;
213
+ }
214
+ if (normalizeChatChannelId(trimmed)) {
215
+ return;
131
216
  }
132
- const pluginLabel = diag.pluginId ? `plugin ${diag.pluginId}` : "plugin";
133
- const message = `${pluginLabel}: ${diag.message}`;
134
- if (diag.level === "error") {
135
- issues.push({ path, message });
217
+ if (!heartbeatChannelIds.has(normalized)) {
218
+ const { registry } = ensureRegistry();
219
+ for (const record of registry.plugins) {
220
+ for (const channelId of record.channels) {
221
+ const pluginChannel = channelId.trim();
222
+ if (pluginChannel) {
223
+ heartbeatChannelIds.add(pluginChannel.toLowerCase());
224
+ }
225
+ }
226
+ }
136
227
  }
137
- else {
138
- warnings.push({ path, message });
228
+ if (heartbeatChannelIds.has(normalized)) {
229
+ return;
230
+ }
231
+ issues.push({ path, message: `unknown heartbeat target: ${target}` });
232
+ };
233
+ validateHeartbeatTarget(config.agents?.defaults?.heartbeat?.target, "agents.defaults.heartbeat.target");
234
+ if (Array.isArray(config.agents?.list)) {
235
+ for (const [index, entry] of config.agents.list.entries()) {
236
+ validateHeartbeatTarget(entry?.heartbeat?.target, `agents.list.${index}.heartbeat.target`);
139
237
  }
140
238
  }
239
+ if (!hasExplicitPluginsConfig) {
240
+ if (issues.length > 0) {
241
+ return { ok: false, issues, warnings };
242
+ }
243
+ return { ok: true, config, warnings };
244
+ }
245
+ const { registry, knownIds, normalizedPlugins } = ensureRegistry();
246
+ const pluginsConfig = config.plugins;
141
247
  const entries = pluginsConfig?.entries;
142
248
  if (entries && isRecord(entries)) {
143
249
  for (const pluginId of Object.keys(entries)) {
@@ -151,8 +257,9 @@ export function validateConfigObjectWithPlugins(raw) {
151
257
  }
152
258
  const allow = pluginsConfig?.allow ?? [];
153
259
  for (const pluginId of allow) {
154
- if (typeof pluginId !== "string" || !pluginId.trim())
260
+ if (typeof pluginId !== "string" || !pluginId.trim()) {
155
261
  continue;
262
+ }
156
263
  if (!knownIds.has(pluginId)) {
157
264
  issues.push({
158
265
  path: "plugins.allow",
@@ -162,8 +269,9 @@ export function validateConfigObjectWithPlugins(raw) {
162
269
  }
163
270
  const deny = pluginsConfig?.deny ?? [];
164
271
  for (const pluginId of deny) {
165
- if (typeof pluginId !== "string" || !pluginId.trim())
272
+ if (typeof pluginId !== "string" || !pluginId.trim()) {
166
273
  continue;
274
+ }
167
275
  if (!knownIds.has(pluginId)) {
168
276
  issues.push({
169
277
  path: "plugins.deny",
@@ -178,59 +286,6 @@ export function validateConfigObjectWithPlugins(raw) {
178
286
  message: `plugin not found: ${memorySlot}`,
179
287
  });
180
288
  }
181
- const allowedChannels = new Set(["defaults", ...CHANNEL_IDS]);
182
- for (const record of registry.plugins) {
183
- for (const channelId of record.channels) {
184
- allowedChannels.add(channelId);
185
- }
186
- }
187
- if (config.channels && isRecord(config.channels)) {
188
- for (const key of Object.keys(config.channels)) {
189
- const trimmed = key.trim();
190
- if (!trimmed)
191
- continue;
192
- if (!allowedChannels.has(trimmed)) {
193
- issues.push({
194
- path: `channels.${trimmed}`,
195
- message: `unknown channel id: ${trimmed}`,
196
- });
197
- }
198
- }
199
- }
200
- const heartbeatChannelIds = new Set();
201
- for (const channelId of CHANNEL_IDS) {
202
- heartbeatChannelIds.add(channelId.toLowerCase());
203
- }
204
- for (const record of registry.plugins) {
205
- for (const channelId of record.channels) {
206
- const trimmed = channelId.trim();
207
- if (trimmed)
208
- heartbeatChannelIds.add(trimmed.toLowerCase());
209
- }
210
- }
211
- const validateHeartbeatTarget = (target, path) => {
212
- if (typeof target !== "string")
213
- return;
214
- const trimmed = target.trim();
215
- if (!trimmed) {
216
- issues.push({ path, message: "heartbeat target must not be empty" });
217
- return;
218
- }
219
- const normalized = trimmed.toLowerCase();
220
- if (normalized === "last" || normalized === "none")
221
- return;
222
- if (normalizeChatChannelId(trimmed))
223
- return;
224
- if (heartbeatChannelIds.has(normalized))
225
- return;
226
- issues.push({ path, message: `unknown heartbeat target: ${target}` });
227
- };
228
- validateHeartbeatTarget(config.agents?.defaults?.heartbeat?.target, "agents.defaults.heartbeat.target");
229
- if (Array.isArray(config.agents?.list)) {
230
- for (const [index, entry] of config.agents.list.entries()) {
231
- validateHeartbeatTarget(entry?.heartbeat?.target, `agents.list.${index}.heartbeat.target`);
232
- }
233
- }
234
289
  let selectedMemoryPluginId = null;
235
290
  const seenPlugins = new Set();
236
291
  for (const record of registry.plugins) {
@@ -1,4 +1,33 @@
1
+ import path from "node:path";
1
2
  import { z } from "zod";
3
+ import { InstallRecordShape } from "./zod-schema.installs.js";
4
+ import { sensitive } from "./zod-schema.sensitive.js";
5
+ function isSafeRelativeModulePath(raw) {
6
+ const value = raw.trim();
7
+ if (!value) {
8
+ return false;
9
+ }
10
+ // Hook modules are loaded via file-path resolution + dynamic import().
11
+ // Keep this strictly relative to a configured base dir to avoid path traversal and surprises.
12
+ if (path.isAbsolute(value)) {
13
+ return false;
14
+ }
15
+ if (value.startsWith("~")) {
16
+ return false;
17
+ }
18
+ // Disallow URL-ish and drive-relative forms (e.g. "file:...", "C:foo").
19
+ if (value.includes(":")) {
20
+ return false;
21
+ }
22
+ const parts = value.split(/[\\/]+/g);
23
+ if (parts.some((part) => part === "..")) {
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ const SafeRelativeModulePathSchema = z
29
+ .string()
30
+ .refine(isSafeRelativeModulePath, "module must be a safe relative path (no absolute paths)");
2
31
  export const HookMappingSchema = z
3
32
  .object({
4
33
  id: z.string().optional(),
@@ -11,7 +40,8 @@ export const HookMappingSchema = z
11
40
  action: z.union([z.literal("wake"), z.literal("agent")]).optional(),
12
41
  wakeMode: z.union([z.literal("now"), z.literal("next-heartbeat")]).optional(),
13
42
  name: z.string().optional(),
14
- sessionKey: z.string().optional(),
43
+ agentId: z.string().optional(),
44
+ sessionKey: z.string().optional().register(sensitive),
15
45
  messageTemplate: z.string().optional(),
16
46
  textTemplate: z.string().optional(),
17
47
  deliver: z.boolean().optional(),
@@ -22,6 +52,7 @@ export const HookMappingSchema = z
22
52
  z.literal("whatsapp"),
23
53
  z.literal("telegram"),
24
54
  z.literal("discord"),
55
+ z.literal("irc"),
25
56
  z.literal("slack"),
26
57
  z.literal("signal"),
27
58
  z.literal("imessage"),
@@ -34,7 +65,7 @@ export const HookMappingSchema = z
34
65
  timeoutSeconds: z.number().int().positive().optional(),
35
66
  transform: z
36
67
  .object({
37
- module: z.string(),
68
+ module: SafeRelativeModulePathSchema,
38
69
  export: z.string().optional(),
39
70
  })
40
71
  .strict()
@@ -45,7 +76,7 @@ export const HookMappingSchema = z
45
76
  export const InternalHookHandlerSchema = z
46
77
  .object({
47
78
  event: z.string(),
48
- module: z.string(),
79
+ module: SafeRelativeModulePathSchema,
49
80
  export: z.string().optional(),
50
81
  })
51
82
  .strict();
@@ -54,15 +85,13 @@ const HookConfigSchema = z
54
85
  enabled: z.boolean().optional(),
55
86
  env: z.record(z.string(), z.string()).optional(),
56
87
  })
57
- .strict();
88
+ // Hook configs are intentionally open-ended (handlers can define their own keys).
89
+ // Keep enabled/env typed, but allow additional per-hook keys without marking the
90
+ // whole config invalid (which triggers doctor/best-effort loads).
91
+ .passthrough();
58
92
  const HookInstallRecordSchema = z
59
93
  .object({
60
- source: z.union([z.literal("npm"), z.literal("archive"), z.literal("path")]),
61
- spec: z.string().optional(),
62
- sourcePath: z.string().optional(),
63
- installPath: z.string().optional(),
64
- version: z.string().optional(),
65
- installedAt: z.string().optional(),
94
+ ...InstallRecordShape,
66
95
  hooks: z.array(z.string()).optional(),
67
96
  })
68
97
  .strict();
@@ -87,7 +116,7 @@ export const HooksGmailSchema = z
87
116
  label: z.string().optional(),
88
117
  topic: z.string().optional(),
89
118
  subscription: z.string().optional(),
90
- pushToken: z.string().optional(),
119
+ pushToken: z.string().optional().register(sensitive),
91
120
  hookUrl: z.string().optional(),
92
121
  includeBody: z.boolean().optional(),
93
122
  maxBytes: z.number().int().positive().optional(),
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ export const InstallSourceSchema = z.union([
3
+ z.literal("npm"),
4
+ z.literal("archive"),
5
+ z.literal("path"),
6
+ ]);
7
+ export const InstallRecordShape = {
8
+ source: InstallSourceSchema,
9
+ spec: z.string().optional(),
10
+ sourcePath: z.string().optional(),
11
+ installPath: z.string().optional(),
12
+ version: z.string().optional(),
13
+ resolvedName: z.string().optional(),
14
+ resolvedVersion: z.string().optional(),
15
+ resolvedSpec: z.string().optional(),
16
+ integrity: z.string().optional(),
17
+ shasum: z.string().optional(),
18
+ resolvedAt: z.string().optional(),
19
+ installedAt: z.string().optional(),
20
+ };
@@ -6,6 +6,7 @@ import { HexColorSchema, ModelsConfigSchema } from "./zod-schema.core.js";
6
6
  import { HookMappingSchema, HooksGmailSchema, InternalHooksSchema } from "./zod-schema.hooks.js";
7
7
  import { ChannelsSchema } from "./zod-schema.providers.js";
8
8
  import { CommandsSchema, MessagesSchema, SessionSchema } from "./zod-schema.session.js";
9
+ import { sensitive } from "./zod-schema.sensitive.js";
9
10
  const BrowserSnapshotDefaultsSchema = z
10
11
  .object({
11
12
  mode: z.literal("efficient").optional(),
@@ -226,7 +227,7 @@ export const PoolBotSchema = z
226
227
  .object({
227
228
  enabled: z.boolean().optional(),
228
229
  path: z.string().optional(),
229
- token: z.string().optional(),
230
+ token: z.string().optional().register(sensitive),
230
231
  maxBodyBytes: z.number().int().positive().optional(),
231
232
  presets: z.array(z.string()).optional(),
232
233
  transformsDir: z.string().optional(),
@@ -286,7 +287,7 @@ export const PoolBotSchema = z
286
287
  voiceAliases: z.record(z.string(), z.string()).optional(),
287
288
  modelId: z.string().optional(),
288
289
  outputFormat: z.string().optional(),
289
- apiKey: z.string().optional(),
290
+ apiKey: z.string().optional().register(sensitive),
290
291
  interruptOnSpeech: z.boolean().optional(),
291
292
  })
292
293
  .strict()
@@ -316,8 +317,8 @@ export const PoolBotSchema = z
316
317
  auth: z
317
318
  .object({
318
319
  mode: z.union([z.literal("token"), z.literal("password")]).optional(),
319
- token: z.string().optional(),
320
- password: z.string().optional(),
320
+ token: z.string().optional().register(sensitive),
321
+ password: z.string().optional().register(sensitive),
321
322
  allowTailscale: z.boolean().optional(),
322
323
  })
323
324
  .strict()
@@ -334,8 +335,8 @@ export const PoolBotSchema = z
334
335
  .object({
335
336
  url: z.string().optional(),
336
337
  transport: z.union([z.literal("ssh"), z.literal("direct")]).optional(),
337
- token: z.string().optional(),
338
- password: z.string().optional(),
338
+ token: z.string().optional().register(sensitive),
339
+ password: z.string().optional().register(sensitive),
339
340
  tlsFingerprint: z.string().optional(),
340
341
  sshTarget: z.string().optional(),
341
342
  sshIdentity: z.string().optional(),
@@ -460,7 +461,7 @@ export const PoolBotSchema = z
460
461
  .record(z.string(), z
461
462
  .object({
462
463
  enabled: z.boolean().optional(),
463
- apiKey: z.string().optional(),
464
+ apiKey: z.string().optional().register(sensitive),
464
465
  env: z.record(z.string(), z.string()).optional(),
465
466
  config: z.record(z.string(), z.unknown()).optional(),
466
467
  })
@@ -1679,6 +1679,7 @@ ${e.snapshot?JSON.stringify(e.snapshot,null,2):"No snapshot yet."}
1679
1679
  >
1680
1680
  <option value="announce">Announce summary (default)</option>
1681
1681
  <option value="none">None (internal)</option>
1682
+ <option value="webhook">Webhook</option>
1682
1683
  </select>
1683
1684
  </label>
1684
1685
  <label class="field">
@@ -3191,4 +3192,4 @@ ${e.snapshot?JSON.stringify(e.snapshot,null,2):"No snapshot yet."}
3191
3192
  `}const qf={trace:!0,debug:!0,info:!0,warn:!0,error:!0,fatal:!0},Wf={name:"",description:"",agentId:"",enabled:!0,scheduleKind:"every",scheduleAt:"",everyAmount:"30",everyUnit:"minutes",cronExpr:"0 7 * * *",cronTz:"",sessionTarget:"main",wakeMode:"next-heartbeat",payloadKind:"systemEvent",payloadText:"",deliver:!1,channel:"last",to:"",timeoutSeconds:"",postToMainPrefix:""};async function Vf(e){if(!(!e.client||!e.connected)&&!e.agentsLoading){e.agentsLoading=!0,e.agentsError=null;try{const t=await e.client.request("agents.list",{});t&&(e.agentsList=t)}catch(t){e.agentsError=String(t)}finally{e.agentsLoading=!1}}}const $r={WEBCHAT_UI:"webchat-ui",CONTROL_UI:"poolbot-control-ui",WEBCHAT:"webchat",CLI:"cli",GATEWAY_CLIENT:"gateway-client",MACOS_APP:"poolbot-macos",IOS_APP:"poolbot-ios",ANDROID_APP:"poolbot-android",NODE_HOST:"node-host",TEST:"test",FINGERPRINT:"fingerprint",PROBE:"poolbot-probe"},Va=$r,Ts={WEBCHAT:"webchat",CLI:"cli",UI:"ui",BACKEND:"backend",NODE:"node",PROBE:"probe",TEST:"test"};new Set(Object.values($r));new Set(Object.values(Ts));function Gf(e){const t=e.version??(e.nonce?"v2":"v1"),n=e.scopes.join(","),s=e.token??"",i=[t,e.deviceId,e.clientId,e.clientMode,e.role,n,String(e.signedAtMs),s];return t==="v2"&&i.push(e.nonce??""),i.join("|")}const Yf=4008;class Qf{constructor(t){this.opts=t,this.ws=null,this.pending=new Map,this.closed=!1,this.lastSeq=null,this.connectNonce=null,this.connectSent=!1,this.connectTimer=null,this.backoffMs=800}start(){this.closed=!1,this.connect()}stop(){this.closed=!0,this.ws?.close(),this.ws=null,this.flushPending(new Error("gateway client stopped"))}get connected(){return this.ws?.readyState===WebSocket.OPEN}connect(){this.closed||(this.ws=new WebSocket(this.opts.url),this.ws.onopen=()=>this.queueConnect(),this.ws.onmessage=t=>this.handleMessage(String(t.data??"")),this.ws.onclose=t=>{const n=String(t.reason??"");this.ws=null,this.flushPending(new Error(`gateway closed (${t.code}): ${n}`)),this.opts.onClose?.({code:t.code,reason:n}),this.scheduleReconnect()},this.ws.onerror=()=>{})}scheduleReconnect(){if(this.closed)return;const t=this.backoffMs;this.backoffMs=Math.min(this.backoffMs*1.7,15e3),window.setTimeout(()=>this.connect(),t)}flushPending(t){for(const[,n]of this.pending)n.reject(t);this.pending.clear()}async sendConnect(){if(this.connectSent)return;this.connectSent=!0,this.connectTimer!==null&&(window.clearTimeout(this.connectTimer),this.connectTimer=null);const t=typeof crypto<"u"&&!!crypto.subtle,n=["operator.admin","operator.approvals","operator.pairing"],s="operator";let i=null,a=!1,o=this.opts.token;if(t){i=await Us();const d=Wc({deviceId:i.deviceId,role:s})?.token;o=d??this.opts.token,a=!!(d&&this.opts.token)}const l=o||this.opts.password?{token:o,password:this.opts.password}:void 0;let r;if(t&&i){const d=Date.now(),u=this.connectNonce??void 0,g=Gf({deviceId:i.deviceId,clientId:this.opts.clientName??Va.CONTROL_UI,clientMode:this.opts.mode??Ts.WEBCHAT,role:s,scopes:n,signedAtMs:d,token:o??null,nonce:u}),v=await jc(i.privateKey,g);r={id:i.deviceId,publicKey:i.publicKey,signature:v,signedAt:d,nonce:u}}const p={minProtocol:3,maxProtocol:3,client:{id:this.opts.clientName??Va.CONTROL_UI,version:this.opts.clientVersion??"dev",platform:this.opts.platform??navigator.platform??"web",mode:this.opts.mode??Ts.WEBCHAT,instanceId:this.opts.instanceId},role:s,scopes:n,device:r,caps:[],auth:l,userAgent:navigator.userAgent,locale:navigator.language};this.request("connect",p).then(d=>{d?.auth?.deviceToken&&i&&Ro({deviceId:i.deviceId,role:d.auth.role??s,token:d.auth.deviceToken,scopes:d.auth.scopes??[]}),this.backoffMs=800,this.opts.onHello?.(d)}).catch(()=>{a&&i&&Po({deviceId:i.deviceId,role:s}),this.ws?.close(Yf,"connect failed")})}handleMessage(t){let n;try{n=JSON.parse(t)}catch{return}const s=n;if(s.type==="event"){const i=n;if(i.event==="connect.challenge"){const o=i.payload,l=o&&typeof o.nonce=="string"?o.nonce:null;l&&(this.connectNonce=l,this.sendConnect());return}const a=typeof i.seq=="number"?i.seq:null;a!==null&&(this.lastSeq!==null&&a>this.lastSeq+1&&this.opts.onGap?.({expected:this.lastSeq+1,received:a}),this.lastSeq=a);try{this.opts.onEvent?.(i)}catch(o){console.error("[gateway] event handler error:",o)}return}if(s.type==="res"){const i=n,a=this.pending.get(i.id);if(!a)return;this.pending.delete(i.id),i.ok?a.resolve(i.payload):a.reject(new Error(i.error?.message??"request failed"));return}}request(t,n){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return Promise.reject(new Error("gateway not connected"));const s=Ns(),i={type:"req",id:s,method:t,params:n},a=new Promise((o,l)=>{this.pending.set(s,{resolve:r=>o(r),reject:l})});return this.ws.send(JSON.stringify(i)),a}queueConnect(){this.connectNonce=null,this.connectSent=!1,this.connectTimer!==null&&window.clearTimeout(this.connectTimer),this.connectTimer=window.setTimeout(()=>{this.sendConnect()},750)}}function Cs(e){return typeof e=="object"&&e!==null}function Zf(e){if(!Cs(e))return null;const t=typeof e.id=="string"?e.id.trim():"",n=e.request;if(!t||!Cs(n))return null;const s=typeof n.command=="string"?n.command.trim():"";if(!s)return null;const i=typeof e.createdAtMs=="number"?e.createdAtMs:0,a=typeof e.expiresAtMs=="number"?e.expiresAtMs:0;return!i||!a?null:{id:t,request:{command:s,cwd:typeof n.cwd=="string"?n.cwd:null,host:typeof n.host=="string"?n.host:null,security:typeof n.security=="string"?n.security:null,ask:typeof n.ask=="string"?n.ask:null,agentId:typeof n.agentId=="string"?n.agentId:null,resolvedPath:typeof n.resolvedPath=="string"?n.resolvedPath:null,sessionKey:typeof n.sessionKey=="string"?n.sessionKey:null},createdAtMs:i,expiresAtMs:a}}function Jf(e){if(!Cs(e))return null;const t=typeof e.id=="string"?e.id.trim():"";return t?{id:t,decision:typeof e.decision=="string"?e.decision:null,resolvedBy:typeof e.resolvedBy=="string"?e.resolvedBy:null,ts:typeof e.ts=="number"?e.ts:null}:null}function xr(e){const t=Date.now();return e.filter(n=>n.expiresAtMs>t)}function Xf(e,t){const n=xr(e).filter(s=>s.id!==t.id);return n.push(t),n}function Ga(e,t){return xr(e).filter(n=>n.id!==t)}async function kr(e,t){if(!e.client||!e.connected)return;const n=e.sessionKey.trim(),s=n?{sessionKey:n}:{};try{const i=await e.client.request("agent.identity.get",s);if(!i)return;const a=is(i);e.assistantName=a.name,e.assistantAvatar=a.avatar,e.assistantAgentId=a.agentId??null}catch{}}function ts(e,t){const n=(e??"").trim(),s=t.mainSessionKey?.trim();if(!s)return n;if(!n)return s;const i=t.mainKey?.trim()||"main",a=t.defaultAgentId?.trim();return n==="main"||n===i||a&&(n===`agent:${a}:main`||n===`agent:${a}:${i}`)?s:n}function eg(e,t){if(!t?.mainSessionKey)return;const n=ts(e.sessionKey,t),s=ts(e.settings.sessionKey,t),i=ts(e.settings.lastActiveSessionKey,t),a=n||s||e.sessionKey,o={...e.settings,sessionKey:s||a,lastActiveSessionKey:i||a},l=o.sessionKey!==e.settings.sessionKey||o.lastActiveSessionKey!==e.settings.lastActiveSessionKey;a!==e.sessionKey&&(e.sessionKey=a),l&&ke(e,o)}function Ar(e){e.lastError=null,e.hello=null,e.connected=!1,e.execApprovalQueue=[],e.execApprovalError=null,e.client?.stop(),e.client=new Qf({url:e.settings.gatewayUrl,token:e.settings.token.trim()?e.settings.token:void 0,password:e.password.trim()?e.password:void 0,clientName:"poolbot-control-ui",mode:"webchat",onHello:t=>{e.connected=!0,e.lastError=null,e.hello=t,sg(e,t),e.chatRunId=null,e.chatStream=null,e.chatStreamStartedAt=null,cn(e),kr(e),Vf(e),fn(e,{quiet:!0}),Te(e,{quiet:!0}),Qs(e)},onClose:({code:t,reason:n})=>{e.connected=!1,t!==1012&&(e.lastError=`disconnected (${t}): ${n||"no reason"}`)},onEvent:t=>tg(e,t),onGap:({expected:t,received:n})=>{e.lastError=`event gap detected (expected seq ${t}, got ${n}); refresh recommended`}}),e.client.start()}function tg(e,t){try{ng(e,t)}catch(n){console.error("[gateway] handleGatewayEvent error:",t.event,n)}}function ng(e,t){if(e.eventLogBuffer=[{ts:Date.now(),event:t.event,payload:t.payload},...e.eventLogBuffer].slice(0,250),e.tab==="debug"&&(e.eventLog=e.eventLogBuffer),t.event==="agent"){if(e.onboarding)return;Yl(e,t.payload);return}if(t.event==="chat"){const n=t.payload;n?.sessionKey&&No(e,n.sessionKey);const s=Dl(e,n);(s==="final"||s==="error"||s==="aborted")&&(cn(e),Cd(e)),s==="final"&&Xe(e);return}if(t.event==="presence"){const n=t.payload;n?.presence&&Array.isArray(n.presence)&&(e.presenceEntries=n.presence,e.presenceError=null,e.presenceStatus=null);return}if(t.event==="cron"&&e.tab==="cron"&&Zs(e),(t.event==="device.pair.requested"||t.event==="device.pair.resolved")&&Te(e,{quiet:!0}),t.event==="exec.approval.requested"){const n=Zf(t.payload);if(n){e.execApprovalQueue=Xf(e.execApprovalQueue,n),e.execApprovalError=null;const s=Math.max(0,n.expiresAtMs-Date.now()+500);window.setTimeout(()=>{e.execApprovalQueue=Ga(e.execApprovalQueue,n.id)},s)}return}if(t.event==="exec.approval.resolved"){const n=Jf(t.payload);n&&(e.execApprovalQueue=Ga(e.execApprovalQueue,n.id))}}function sg(e,t){const n=t.snapshot;n?.presence&&Array.isArray(n.presence)&&(e.presenceEntries=n.presence),n?.health&&(e.debugHealth=n.health),n?.sessionDefaults&&eg(e,n.sessionDefaults)}function ig(e){e.basePath=gd(),bd(e,!0),vd(e),md(e),window.addEventListener("popstate",e.popStateHandler),pd(e),Ar(e),dd(e),e.tab==="logs"&&Ws(e),e.tab==="debug"&&Gs(e)}function ag(e){ec(e)}function og(e){window.removeEventListener("popstate",e.popStateHandler),ud(e),Vs(e),Ys(e),yd(e),e.topbarObserver?.disconnect(),e.topbarObserver=null}function rg(e,t){if(e.tab==="chat"&&(t.has("chatMessages")||t.has("chatToolMessages")||t.has("chatStream")||t.has("chatLoading")||t.has("tab"))){const n=t.has("tab"),s=t.has("chatLoading")&&t.get("chatLoading")===!0&&e.chatLoading===!1;dn(e,n||s||!e.chatHasAutoScrolled)}e.tab==="logs"&&(t.has("logsEntries")||t.has("logsAutoFollow")||t.has("tab"))&&e.logsAutoFollow&&e.logsAtBottom&&po(e,t.has("tab")||t.has("logsAutoFollow"))}async function lg(e,t){await uc(e,t),await oe(e,!0)}async function cg(e){await pc(e),await oe(e,!0)}async function dg(e){await hc(e),await oe(e,!0)}async function ug(e){await ds(e),await ye(e),await oe(e,!0)}async function pg(e){await ye(e),await oe(e,!0)}function hg(e){if(!Array.isArray(e))return{};const t={};for(const n of e){if(typeof n!="string")continue;const[s,...i]=n.split(":");if(!s||i.length===0)continue;const a=s.trim(),o=i.join(":").trim();a&&o&&(t[a]=o)}return t}function Sr(e){return(e.channelsSnapshot?.channelAccounts?.nostr??[])[0]?.accountId??e.nostrProfileAccountId??"default"}function _r(e,t=""){return`/api/channels/nostr/${encodeURIComponent(e)}/profile${t}`}function fg(e,t,n){e.nostrProfileAccountId=t,e.nostrProfileFormState=uh(n??void 0)}function gg(e){e.nostrProfileFormState=null,e.nostrProfileAccountId=null}function vg(e,t,n){const s=e.nostrProfileFormState;s&&(e.nostrProfileFormState={...s,values:{...s.values,[t]:n},fieldErrors:{...s.fieldErrors,[t]:""}})}function mg(e){const t=e.nostrProfileFormState;t&&(e.nostrProfileFormState={...t,showAdvanced:!t.showAdvanced})}async function yg(e){const t=e.nostrProfileFormState;if(!t||t.saving)return;const n=Sr(e);e.nostrProfileFormState={...t,saving:!0,error:null,success:null,fieldErrors:{}};try{const s=await fetch(_r(n),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t.values)}),i=await s.json().catch(()=>null);if(!s.ok||i?.ok===!1||!i){const a=i?.error??`Profile update failed (${s.status})`;e.nostrProfileFormState={...t,saving:!1,error:a,success:null,fieldErrors:hg(i?.details)};return}if(!i.persisted){e.nostrProfileFormState={...t,saving:!1,error:"Profile publish failed on all relays.",success:null};return}e.nostrProfileFormState={...t,saving:!1,error:null,success:"Profile published to relays.",fieldErrors:{},original:{...t.values}},await oe(e,!0)}catch(s){e.nostrProfileFormState={...t,saving:!1,error:`Profile update failed: ${String(s)}`,success:null}}}async function bg(e){const t=e.nostrProfileFormState;if(!t||t.importing)return;const n=Sr(e);e.nostrProfileFormState={...t,importing:!0,error:null,success:null};try{const s=await fetch(_r(n,"/import"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({autoMerge:!0})}),i=await s.json().catch(()=>null);if(!s.ok||i?.ok===!1||!i){const r=i?.error??`Profile import failed (${s.status})`;e.nostrProfileFormState={...t,importing:!1,error:r,success:null};return}const a=i.merged??i.imported??null,o=a?{...t.values,...a}:t.values,l=!!(o.banner||o.website||o.nip05||o.lud16);e.nostrProfileFormState={...t,importing:!1,values:o,error:null,success:i.saved?"Profile imported from relays. Review and publish.":"Profile imported. Review and publish.",showAdvanced:l},i.saved&&await oe(e,!0)}catch(s){e.nostrProfileFormState={...t,importing:!1,error:`Profile import failed: ${String(s)}`,success:null}}}var wg=Object.defineProperty,$g=Object.getOwnPropertyDescriptor,y=(e,t,n,s)=>{for(var i=s>1?void 0:s?$g(t,n):t,a=e.length-1,o;a>=0;a--)(o=e[a])&&(i=(s?o(t,n,i):o(i))||i);return s&&i&&wg(t,n,i),i};const ns=gl();function xg(){if(!window.location.search)return!1;const t=new URLSearchParams(window.location.search).get("onboarding");if(!t)return!1;const n=t.trim().toLowerCase();return n==="1"||n==="true"||n==="yes"||n==="on"}let m=class extends Ze{constructor(){super(...arguments),this.settings=vl(),this.password="",this.tab="chat",this.onboarding=xg(),this.connected=!1,this.theme=this.settings.theme??"system",this.themeResolved="dark",this.hello=null,this.lastError=null,this.eventLog=[],this.eventLogBuffer=[],this.toolStreamSyncTimer=null,this.sidebarCloseTimer=null,this.assistantName=ns.name,this.assistantAvatar=ns.avatar,this.assistantAgentId=ns.agentId??null,this.sessionKey=this.settings.sessionKey,this.chatLoading=!1,this.chatSending=!1,this.chatMessage="",this.chatMessages=[],this.chatToolMessages=[],this.chatStream=null,this.chatStreamStartedAt=null,this.chatRunId=null,this.compactionStatus=null,this.chatAvatarUrl=null,this.chatThinkingLevel=null,this.chatQueue=[],this.chatAttachments=[],this.sidebarOpen=!1,this.sidebarContent=null,this.sidebarError=null,this.splitRatio=this.settings.splitRatio,this.nodesLoading=!1,this.nodes=[],this.devicesLoading=!1,this.devicesError=null,this.devicesList=null,this.execApprovalsLoading=!1,this.execApprovalsSaving=!1,this.execApprovalsDirty=!1,this.execApprovalsSnapshot=null,this.execApprovalsForm=null,this.execApprovalsSelectedAgent=null,this.execApprovalsTarget="gateway",this.execApprovalsTargetNodeId=null,this.execApprovalQueue=[],this.execApprovalBusy=!1,this.execApprovalError=null,this.configLoading=!1,this.configRaw=`{
3192
3193
  }
3193
3194
  `,this.configRawOriginal="",this.configValid=null,this.configIssues=[],this.configSaving=!1,this.configApplying=!1,this.updateRunning=!1,this.applySessionKey=this.settings.lastActiveSessionKey,this.configSnapshot=null,this.configSchema=null,this.configSchemaVersion=null,this.configSchemaLoading=!1,this.configUiHints={},this.configForm=null,this.configFormOriginal=null,this.configFormDirty=!1,this.configFormMode="form",this.configSearchQuery="",this.configActiveSection=null,this.configActiveSubsection=null,this.channelsLoading=!1,this.channelsSnapshot=null,this.channelsError=null,this.channelsLastSuccess=null,this.whatsappLoginMessage=null,this.whatsappLoginQrDataUrl=null,this.whatsappLoginConnected=null,this.whatsappBusy=!1,this.nostrProfileFormState=null,this.nostrProfileAccountId=null,this.presenceLoading=!1,this.presenceEntries=[],this.presenceError=null,this.presenceStatus=null,this.agentsLoading=!1,this.agentsList=null,this.agentsError=null,this.sessionsLoading=!1,this.sessionsResult=null,this.sessionsError=null,this.sessionsFilterActive="",this.sessionsFilterLimit="120",this.sessionsIncludeGlobal=!0,this.sessionsIncludeUnknown=!1,this.cronLoading=!1,this.cronJobs=[],this.cronStatus=null,this.cronError=null,this.cronForm={...Wf},this.cronRunsJobId=null,this.cronRuns=[],this.cronBusy=!1,this.skillsLoading=!1,this.skillsReport=null,this.skillsError=null,this.skillsFilter="",this.skillEdits={},this.skillsBusyKey=null,this.skillMessages={},this.debugLoading=!1,this.debugStatus=null,this.debugHealth=null,this.debugModels=[],this.debugHeartbeat=null,this.debugCallMethod="",this.debugCallParams="{}",this.debugCallResult=null,this.debugCallError=null,this.logsLoading=!1,this.logsError=null,this.logsFile=null,this.logsEntries=[],this.logsFilterText="",this.logsLevelFilters={...qf},this.logsAutoFollow=!0,this.logsTruncated=!1,this.logsCursor=null,this.logsLastFetchAt=null,this.logsLimit=500,this.logsMaxBytes=25e4,this.logsAtBottom=!0,this.client=null,this.chatScrollFrame=null,this.chatScrollTimeout=null,this.chatHasAutoScrolled=!1,this.chatUserNearBottom=!0,this.nodesPollInterval=null,this.logsPollInterval=null,this.debugPollInterval=null,this.logsScrollFrame=null,this.toolStreamById=new Map,this.toolStreamOrder=[],this.basePath="",this.popStateHandler=()=>wd(this),this.themeMedia=null,this.themeMediaHandler=null,this.topbarObserver=null}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),ig(this)}firstUpdated(){ag(this)}disconnectedCallback(){og(this),super.disconnectedCallback()}updated(e){rg(this,e)}connect(){Ar(this)}handleChatScroll(e){Ql(this,e)}handleLogsScroll(e){Zl(this,e)}exportLogs(e,t){Xl(e,t)}resetToolStream(){cn(this)}resetChatScroll(){Jl(this)}async loadAssistantIdentity(){await kr(this)}applySettings(e){ke(this,e)}setTab(e){hd(this,e)}setTheme(e,t){fd(this,e,t)}async loadOverview(){await Bo(this)}async loadCron(){await Zs(this)}async handleAbortChat(){await Uo(this)}removeQueuedMessage(e){Sd(this,e)}async handleSendChat(e,t){await _d(this,e,t)}async handleWhatsAppStart(e){await lg(this,e)}async handleWhatsAppWait(){await cg(this)}async handleWhatsAppLogout(){await dg(this)}async handleChannelConfigSave(){await ug(this)}async handleChannelConfigReload(){await pg(this)}handleNostrProfileEdit(e,t){fg(this,e,t)}handleNostrProfileCancel(){gg(this)}handleNostrProfileFieldChange(e,t){vg(this,e,t)}async handleNostrProfileSave(){await yg(this)}async handleNostrProfileImport(){await bg(this)}handleNostrProfileToggleAdvanced(){mg(this)}async handleExecApprovalDecision(e){const t=this.execApprovalQueue[0];if(!(!t||!this.client||this.execApprovalBusy)){this.execApprovalBusy=!0,this.execApprovalError=null;try{await this.client.request("exec.approval.resolve",{id:t.id,decision:e}),this.execApprovalQueue=this.execApprovalQueue.filter(n=>n.id!==t.id)}catch(n){this.execApprovalError=`Exec approval failed: ${String(n)}`}finally{this.execApprovalBusy=!1}}}handleOpenSidebar(e){this.sidebarCloseTimer!=null&&(window.clearTimeout(this.sidebarCloseTimer),this.sidebarCloseTimer=null),this.sidebarContent=e,this.sidebarError=null,this.sidebarOpen=!0}handleCloseSidebar(){this.sidebarOpen=!1,this.sidebarCloseTimer!=null&&window.clearTimeout(this.sidebarCloseTimer),this.sidebarCloseTimer=window.setTimeout(()=>{this.sidebarOpen||(this.sidebarContent=null,this.sidebarError=null,this.sidebarCloseTimer=null)},200)}handleSplitRatioChange(e){const t=Math.max(.4,Math.min(.7,e));this.splitRatio=t,this.applySettings({...this.settings,splitRatio:t})}render(){return jf(this)}};y([b()],m.prototype,"settings",2);y([b()],m.prototype,"password",2);y([b()],m.prototype,"tab",2);y([b()],m.prototype,"onboarding",2);y([b()],m.prototype,"connected",2);y([b()],m.prototype,"theme",2);y([b()],m.prototype,"themeResolved",2);y([b()],m.prototype,"hello",2);y([b()],m.prototype,"lastError",2);y([b()],m.prototype,"eventLog",2);y([b()],m.prototype,"assistantName",2);y([b()],m.prototype,"assistantAvatar",2);y([b()],m.prototype,"assistantAgentId",2);y([b()],m.prototype,"sessionKey",2);y([b()],m.prototype,"chatLoading",2);y([b()],m.prototype,"chatSending",2);y([b()],m.prototype,"chatMessage",2);y([b()],m.prototype,"chatMessages",2);y([b()],m.prototype,"chatToolMessages",2);y([b()],m.prototype,"chatStream",2);y([b()],m.prototype,"chatStreamStartedAt",2);y([b()],m.prototype,"chatRunId",2);y([b()],m.prototype,"compactionStatus",2);y([b()],m.prototype,"chatAvatarUrl",2);y([b()],m.prototype,"chatThinkingLevel",2);y([b()],m.prototype,"chatQueue",2);y([b()],m.prototype,"chatAttachments",2);y([b()],m.prototype,"sidebarOpen",2);y([b()],m.prototype,"sidebarContent",2);y([b()],m.prototype,"sidebarError",2);y([b()],m.prototype,"splitRatio",2);y([b()],m.prototype,"nodesLoading",2);y([b()],m.prototype,"nodes",2);y([b()],m.prototype,"devicesLoading",2);y([b()],m.prototype,"devicesError",2);y([b()],m.prototype,"devicesList",2);y([b()],m.prototype,"execApprovalsLoading",2);y([b()],m.prototype,"execApprovalsSaving",2);y([b()],m.prototype,"execApprovalsDirty",2);y([b()],m.prototype,"execApprovalsSnapshot",2);y([b()],m.prototype,"execApprovalsForm",2);y([b()],m.prototype,"execApprovalsSelectedAgent",2);y([b()],m.prototype,"execApprovalsTarget",2);y([b()],m.prototype,"execApprovalsTargetNodeId",2);y([b()],m.prototype,"execApprovalQueue",2);y([b()],m.prototype,"execApprovalBusy",2);y([b()],m.prototype,"execApprovalError",2);y([b()],m.prototype,"configLoading",2);y([b()],m.prototype,"configRaw",2);y([b()],m.prototype,"configRawOriginal",2);y([b()],m.prototype,"configValid",2);y([b()],m.prototype,"configIssues",2);y([b()],m.prototype,"configSaving",2);y([b()],m.prototype,"configApplying",2);y([b()],m.prototype,"updateRunning",2);y([b()],m.prototype,"applySessionKey",2);y([b()],m.prototype,"configSnapshot",2);y([b()],m.prototype,"configSchema",2);y([b()],m.prototype,"configSchemaVersion",2);y([b()],m.prototype,"configSchemaLoading",2);y([b()],m.prototype,"configUiHints",2);y([b()],m.prototype,"configForm",2);y([b()],m.prototype,"configFormOriginal",2);y([b()],m.prototype,"configFormDirty",2);y([b()],m.prototype,"configFormMode",2);y([b()],m.prototype,"configSearchQuery",2);y([b()],m.prototype,"configActiveSection",2);y([b()],m.prototype,"configActiveSubsection",2);y([b()],m.prototype,"channelsLoading",2);y([b()],m.prototype,"channelsSnapshot",2);y([b()],m.prototype,"channelsError",2);y([b()],m.prototype,"channelsLastSuccess",2);y([b()],m.prototype,"whatsappLoginMessage",2);y([b()],m.prototype,"whatsappLoginQrDataUrl",2);y([b()],m.prototype,"whatsappLoginConnected",2);y([b()],m.prototype,"whatsappBusy",2);y([b()],m.prototype,"nostrProfileFormState",2);y([b()],m.prototype,"nostrProfileAccountId",2);y([b()],m.prototype,"presenceLoading",2);y([b()],m.prototype,"presenceEntries",2);y([b()],m.prototype,"presenceError",2);y([b()],m.prototype,"presenceStatus",2);y([b()],m.prototype,"agentsLoading",2);y([b()],m.prototype,"agentsList",2);y([b()],m.prototype,"agentsError",2);y([b()],m.prototype,"sessionsLoading",2);y([b()],m.prototype,"sessionsResult",2);y([b()],m.prototype,"sessionsError",2);y([b()],m.prototype,"sessionsFilterActive",2);y([b()],m.prototype,"sessionsFilterLimit",2);y([b()],m.prototype,"sessionsIncludeGlobal",2);y([b()],m.prototype,"sessionsIncludeUnknown",2);y([b()],m.prototype,"cronLoading",2);y([b()],m.prototype,"cronJobs",2);y([b()],m.prototype,"cronStatus",2);y([b()],m.prototype,"cronError",2);y([b()],m.prototype,"cronForm",2);y([b()],m.prototype,"cronRunsJobId",2);y([b()],m.prototype,"cronRuns",2);y([b()],m.prototype,"cronBusy",2);y([b()],m.prototype,"skillsLoading",2);y([b()],m.prototype,"skillsReport",2);y([b()],m.prototype,"skillsError",2);y([b()],m.prototype,"skillsFilter",2);y([b()],m.prototype,"skillEdits",2);y([b()],m.prototype,"skillsBusyKey",2);y([b()],m.prototype,"skillMessages",2);y([b()],m.prototype,"debugLoading",2);y([b()],m.prototype,"debugStatus",2);y([b()],m.prototype,"debugHealth",2);y([b()],m.prototype,"debugModels",2);y([b()],m.prototype,"debugHeartbeat",2);y([b()],m.prototype,"debugCallMethod",2);y([b()],m.prototype,"debugCallParams",2);y([b()],m.prototype,"debugCallResult",2);y([b()],m.prototype,"debugCallError",2);y([b()],m.prototype,"logsLoading",2);y([b()],m.prototype,"logsError",2);y([b()],m.prototype,"logsFile",2);y([b()],m.prototype,"logsEntries",2);y([b()],m.prototype,"logsFilterText",2);y([b()],m.prototype,"logsLevelFilters",2);y([b()],m.prototype,"logsAutoFollow",2);y([b()],m.prototype,"logsTruncated",2);y([b()],m.prototype,"logsCursor",2);y([b()],m.prototype,"logsLastFetchAt",2);y([b()],m.prototype,"logsLimit",2);y([b()],m.prototype,"logsMaxBytes",2);y([b()],m.prototype,"logsAtBottom",2);m=y([no("poolbot-app")],m);
3194
- //# sourceMappingURL=index-HRr1grwl.js.map
3195
+ //# sourceMappingURL=index-Dvkl4Xlx.js.map