@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
@@ -1,38 +1,122 @@
1
- import { listAgentIds, resolveAgentDir, resolveAgentModelFallbacksOverride, resolveAgentModelPrimary, resolveAgentWorkspaceDir, } from "../agents/agent-scope.js";
1
+ import { listAgentIds, resolveAgentDir, resolveEffectiveModelFallbacks, resolveAgentModelPrimary, resolveAgentSkillsFilter, resolveAgentWorkspaceDir, } from "../agents/agent-scope.js";
2
2
  import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
3
+ import { clearSessionAuthProfileOverride } from "../agents/auth-profiles/session-override.js";
3
4
  import { runCliAgent } from "../agents/cli-runner.js";
4
5
  import { getCliSessionId } from "../agents/cli-session.js";
5
6
  import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
7
+ import { AGENT_LANE_SUBAGENT } from "../agents/lanes.js";
6
8
  import { loadModelCatalog } from "../agents/model-catalog.js";
7
9
  import { runWithModelFallback } from "../agents/model-fallback.js";
8
- import { buildAllowedModelSet, isCliProvider, modelKey, resolveConfiguredModelRef, resolveThinkingDefault, } from "../agents/model-selection.js";
10
+ import { buildAllowedModelSet, isCliProvider, modelKey, normalizeModelRef, resolveConfiguredModelRef, resolveThinkingDefault, } from "../agents/model-selection.js";
9
11
  import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
10
12
  import { buildWorkspaceSkillSnapshot } from "../agents/skills.js";
11
13
  import { getSkillsSnapshotVersion } from "../agents/skills/refresh.js";
12
14
  import { resolveAgentTimeoutMs } from "../agents/timeout.js";
13
15
  import { ensureAgentWorkspace } from "../agents/workspace.js";
14
16
  import { formatThinkingLevels, formatXHighModelHint, normalizeThinkLevel, normalizeVerboseLevel, supportsXHighThinking, } from "../auto-reply/thinking.js";
17
+ import { formatCliCommand } from "../cli/command-format.js";
15
18
  import { createDefaultDeps } from "../cli/deps.js";
16
19
  import { loadConfig } from "../config/config.js";
17
20
  import { resolveAgentIdFromSessionKey, resolveSessionFilePath, updateSessionStore, } from "../config/sessions.js";
18
21
  import { clearAgentRunContext, emitAgentEvent, registerAgentRunContext, } from "../infra/agent-events.js";
19
22
  import { getRemoteSkillEligibility } from "../infra/skills-remote.js";
23
+ import { normalizeAgentId } from "../routing/session-key.js";
20
24
  import { defaultRuntime } from "../runtime.js";
21
- import { formatCliCommand } from "../cli/command-format.js";
22
25
  import { applyVerboseOverride } from "../sessions/level-overrides.js";
23
- import { resolveSendPolicy } from "../sessions/send-policy.js";
24
26
  import { applyModelOverrideToSessionEntry } from "../sessions/model-overrides.js";
25
- import { clearSessionAuthProfileOverride } from "../agents/auth-profiles/session-override.js";
27
+ import { resolveSendPolicy } from "../sessions/send-policy.js";
26
28
  import { resolveMessageChannel } from "../utils/message-channel.js";
27
29
  import { deliverAgentCommandResult } from "./agent/delivery.js";
28
30
  import { resolveAgentRunContext } from "./agent/run-context.js";
29
- import { resolveSession } from "./agent/session.js";
30
31
  import { updateSessionStoreAfterAgentRun } from "./agent/session-store.js";
31
- import { normalizeAgentId } from "../routing/session-key.js";
32
+ import { resolveSession } from "./agent/session.js";
33
+ async function persistSessionEntry(params) {
34
+ params.sessionStore[params.sessionKey] = params.entry;
35
+ await updateSessionStore(params.storePath, (store) => {
36
+ store[params.sessionKey] = params.entry;
37
+ });
38
+ }
39
+ function resolveFallbackRetryPrompt(params) {
40
+ if (!params.isFallbackRetry) {
41
+ return params.body;
42
+ }
43
+ return "Continue where you left off. The previous model attempt failed or timed out.";
44
+ }
45
+ function runAgentAttempt(params) {
46
+ const effectivePrompt = resolveFallbackRetryPrompt({
47
+ body: params.body,
48
+ isFallbackRetry: params.isFallbackRetry,
49
+ });
50
+ if (isCliProvider(params.providerOverride, params.cfg)) {
51
+ const cliSessionId = getCliSessionId(params.sessionEntry, params.providerOverride);
52
+ return runCliAgent({
53
+ sessionId: params.sessionId,
54
+ sessionKey: params.sessionKey,
55
+ agentId: params.sessionAgentId,
56
+ sessionFile: params.sessionFile,
57
+ workspaceDir: params.workspaceDir,
58
+ config: params.cfg,
59
+ prompt: effectivePrompt,
60
+ provider: params.providerOverride,
61
+ model: params.modelOverride,
62
+ thinkLevel: params.resolvedThinkLevel,
63
+ timeoutMs: params.timeoutMs,
64
+ runId: params.runId,
65
+ extraSystemPrompt: params.opts.extraSystemPrompt,
66
+ cliSessionId,
67
+ images: params.isFallbackRetry ? undefined : params.opts.images,
68
+ streamParams: params.opts.streamParams,
69
+ });
70
+ }
71
+ const authProfileId = params.providerOverride === params.primaryProvider
72
+ ? params.sessionEntry?.authProfileOverride
73
+ : undefined;
74
+ return runEmbeddedPiAgent({
75
+ sessionId: params.sessionId,
76
+ sessionKey: params.sessionKey,
77
+ agentId: params.sessionAgentId,
78
+ messageChannel: params.messageChannel,
79
+ agentAccountId: params.runContext.accountId,
80
+ messageTo: params.opts.replyTo ?? params.opts.to,
81
+ messageThreadId: params.opts.threadId,
82
+ groupId: params.runContext.groupId,
83
+ groupChannel: params.runContext.groupChannel,
84
+ groupSpace: params.runContext.groupSpace,
85
+ spawnedBy: params.spawnedBy,
86
+ currentChannelId: params.runContext.currentChannelId,
87
+ currentThreadTs: params.runContext.currentThreadTs,
88
+ replyToMode: params.runContext.replyToMode,
89
+ hasRepliedRef: params.runContext.hasRepliedRef,
90
+ senderIsOwner: true,
91
+ sessionFile: params.sessionFile,
92
+ workspaceDir: params.workspaceDir,
93
+ config: params.cfg,
94
+ skillsSnapshot: params.skillsSnapshot,
95
+ prompt: effectivePrompt,
96
+ images: params.isFallbackRetry ? undefined : params.opts.images,
97
+ clientTools: params.opts.clientTools,
98
+ provider: params.providerOverride,
99
+ model: params.modelOverride,
100
+ authProfileId,
101
+ authProfileIdSource: authProfileId ? params.sessionEntry?.authProfileOverrideSource : undefined,
102
+ thinkLevel: params.resolvedThinkLevel,
103
+ verboseLevel: params.resolvedVerboseLevel,
104
+ timeoutMs: params.timeoutMs,
105
+ runId: params.runId,
106
+ lane: params.opts.lane,
107
+ abortSignal: params.opts.abortSignal,
108
+ extraSystemPrompt: params.opts.extraSystemPrompt,
109
+ inputProvenance: params.opts.inputProvenance,
110
+ streamParams: params.opts.streamParams,
111
+ agentDir: params.agentDir,
112
+ onAgentEvent: params.onAgentEvent,
113
+ });
114
+ }
32
115
  export async function agentCommand(opts, runtime = defaultRuntime, deps = createDefaultDeps()) {
33
116
  const body = (opts.message ?? "").trim();
34
- if (!body)
117
+ if (!body) {
35
118
  throw new Error("Message (--message) is required");
119
+ }
36
120
  if (!opts.to && !opts.sessionId && !opts.sessionKey && !opts.agentId) {
37
121
  throw new Error("Pass --to <E.164>, --session-id, or --agent to choose a session");
38
122
  }
@@ -78,10 +162,16 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
78
162
  if (opts.verbose && !verboseOverride) {
79
163
  throw new Error('Invalid verbose level. Use "on", "full", or "off".');
80
164
  }
81
- const timeoutSecondsRaw = opts.timeout !== undefined ? Number.parseInt(String(opts.timeout), 10) : undefined;
165
+ const laneRaw = typeof opts.lane === "string" ? opts.lane.trim() : "";
166
+ const isSubagentLane = laneRaw === String(AGENT_LANE_SUBAGENT);
167
+ const timeoutSecondsRaw = opts.timeout !== undefined
168
+ ? Number.parseInt(String(opts.timeout), 10)
169
+ : isSubagentLane
170
+ ? 0
171
+ : undefined;
82
172
  if (timeoutSecondsRaw !== undefined &&
83
- (Number.isNaN(timeoutSecondsRaw) || timeoutSecondsRaw <= 0)) {
84
- throw new Error("--timeout must be a positive integer (seconds)");
173
+ (Number.isNaN(timeoutSecondsRaw) || timeoutSecondsRaw < 0)) {
174
+ throw new Error("--timeout must be a non-negative integer (seconds; 0 means no timeout)");
85
175
  }
86
176
  const timeoutMs = resolveAgentTimeoutMs({
87
177
  cfg,
@@ -123,11 +213,13 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
123
213
  }
124
214
  const needsSkillsSnapshot = isNewSession || !sessionEntry?.skillsSnapshot;
125
215
  const skillsSnapshotVersion = getSkillsSnapshotVersion(workspaceDir);
216
+ const skillFilter = resolveAgentSkillsFilter(cfg, sessionAgentId);
126
217
  const skillsSnapshot = needsSkillsSnapshot
127
218
  ? buildWorkspaceSkillSnapshot(workspaceDir, {
128
219
  config: cfg,
129
220
  eligibility: { remote: getRemoteSkillEligibility() },
130
221
  snapshotVersion: skillsSnapshotVersion,
222
+ skillFilter,
131
223
  })
132
224
  : sessionEntry?.skillsSnapshot;
133
225
  if (skillsSnapshot && sessionStore && sessionKey && needsSkillsSnapshot) {
@@ -141,9 +233,11 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
141
233
  updatedAt: Date.now(),
142
234
  skillsSnapshot,
143
235
  };
144
- sessionStore[sessionKey] = next;
145
- await updateSessionStore(storePath, (store) => {
146
- store[sessionKey] = next;
236
+ await persistSessionEntry({
237
+ sessionStore,
238
+ sessionKey,
239
+ storePath,
240
+ entry: next,
147
241
  });
148
242
  sessionEntry = next;
149
243
  }
@@ -153,15 +247,14 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
153
247
  sessionEntry ?? { sessionId, updatedAt: Date.now() };
154
248
  const next = { ...entry, sessionId, updatedAt: Date.now() };
155
249
  if (thinkOverride) {
156
- if (thinkOverride === "off")
157
- delete next.thinkingLevel;
158
- else
159
- next.thinkingLevel = thinkOverride;
250
+ next.thinkingLevel = thinkOverride;
160
251
  }
161
252
  applyVerboseOverride(next, verboseOverride);
162
- sessionStore[sessionKey] = next;
163
- await updateSessionStore(storePath, (store) => {
164
- store[sessionKey] = next;
253
+ await persistSessionEntry({
254
+ sessionStore,
255
+ sessionKey,
256
+ storePath,
257
+ entry: next,
165
258
  });
166
259
  }
167
260
  const agentModelPrimary = resolveAgentModelPrimary(cfg, sessionAgentId);
@@ -182,11 +275,12 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
182
275
  },
183
276
  }
184
277
  : cfg;
185
- const { provider: defaultProvider, model: defaultModel } = resolveConfiguredModelRef({
278
+ const configuredDefaultRef = resolveConfiguredModelRef({
186
279
  cfg: cfgForModelSelection,
187
280
  defaultProvider: DEFAULT_PROVIDER,
188
281
  defaultModel: DEFAULT_MODEL,
189
282
  });
283
+ const { provider: defaultProvider, model: defaultModel } = normalizeModelRef(configuredDefaultRef.provider, configuredDefaultRef.model);
190
284
  let provider = defaultProvider;
191
285
  let model = defaultModel;
192
286
  const hasAllowlist = agentCfg?.models && Object.keys(agentCfg.models).length > 0;
@@ -211,8 +305,9 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
211
305
  const overrideProvider = sessionEntry.providerOverride?.trim() || defaultProvider;
212
306
  const overrideModel = sessionEntry.modelOverride?.trim();
213
307
  if (overrideModel) {
214
- const key = modelKey(overrideProvider, overrideModel);
215
- if (!isCliProvider(overrideProvider, cfg) &&
308
+ const normalizedOverride = normalizeModelRef(overrideProvider, overrideModel);
309
+ const key = modelKey(normalizedOverride.provider, normalizedOverride.model);
310
+ if (!isCliProvider(normalizedOverride.provider, cfg) &&
216
311
  allowedModelKeys.size > 0 &&
217
312
  !allowedModelKeys.has(key)) {
218
313
  const { updated } = applyModelOverrideToSessionEntry({
@@ -220,9 +315,11 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
220
315
  selection: { provider: defaultProvider, model: defaultModel, isDefault: true },
221
316
  });
222
317
  if (updated) {
223
- sessionStore[sessionKey] = entry;
224
- await updateSessionStore(storePath, (store) => {
225
- store[sessionKey] = entry;
318
+ await persistSessionEntry({
319
+ sessionStore,
320
+ sessionKey,
321
+ storePath,
322
+ entry,
226
323
  });
227
324
  }
228
325
  }
@@ -232,12 +329,13 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
232
329
  const storedModelOverride = sessionEntry?.modelOverride?.trim();
233
330
  if (storedModelOverride) {
234
331
  const candidateProvider = storedProviderOverride || defaultProvider;
235
- const key = modelKey(candidateProvider, storedModelOverride);
236
- if (isCliProvider(candidateProvider, cfg) ||
332
+ const normalizedStored = normalizeModelRef(candidateProvider, storedModelOverride);
333
+ const key = modelKey(normalizedStored.provider, normalizedStored.model);
334
+ if (isCliProvider(normalizedStored.provider, cfg) ||
237
335
  allowedModelKeys.size === 0 ||
238
336
  allowedModelKeys.has(key)) {
239
- provider = candidateProvider;
240
- model = storedModelOverride;
337
+ provider = normalizedStored.provider;
338
+ model = normalizedStored.model;
241
339
  }
242
340
  }
243
341
  if (sessionEntry) {
@@ -281,9 +379,11 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
281
379
  const entry = sessionEntry;
282
380
  entry.thinkingLevel = "high";
283
381
  entry.updatedAt = Date.now();
284
- sessionStore[sessionKey] = entry;
285
- await updateSessionStore(storePath, (store) => {
286
- store[sessionKey] = entry;
382
+ await persistSessionEntry({
383
+ sessionStore,
384
+ sessionKey,
385
+ storePath,
386
+ entry,
287
387
  });
288
388
  }
289
389
  }
@@ -299,72 +399,48 @@ export async function agentCommand(opts, runtime = defaultRuntime, deps = create
299
399
  const runContext = resolveAgentRunContext(opts);
300
400
  const messageChannel = resolveMessageChannel(runContext.messageChannel, opts.replyChannel ?? opts.channel);
301
401
  const spawnedBy = opts.spawnedBy ?? sessionEntry?.spawnedBy;
402
+ // Keep fallback candidate resolution centralized so session model overrides,
403
+ // per-agent overrides, and default fallbacks stay consistent across callers.
404
+ const effectiveFallbacksOverride = resolveEffectiveModelFallbacks({
405
+ cfg,
406
+ agentId: sessionAgentId,
407
+ hasSessionModelOverride: Boolean(storedModelOverride),
408
+ });
409
+ // Track model fallback attempts so retries on an existing session don't
410
+ // re-inject the original prompt as a duplicate user message.
411
+ let fallbackAttemptIndex = 0;
302
412
  const fallbackResult = await runWithModelFallback({
303
413
  cfg,
304
414
  provider,
305
415
  model,
306
416
  agentDir,
307
- fallbacksOverride: resolveAgentModelFallbacksOverride(cfg, sessionAgentId),
417
+ fallbacksOverride: effectiveFallbacksOverride,
308
418
  run: (providerOverride, modelOverride) => {
309
- if (isCliProvider(providerOverride, cfg)) {
310
- const cliSessionId = getCliSessionId(sessionEntry, providerOverride);
311
- return runCliAgent({
312
- sessionId,
313
- sessionKey,
314
- sessionFile,
315
- workspaceDir,
316
- config: cfg,
317
- prompt: body,
318
- provider: providerOverride,
319
- model: modelOverride,
320
- thinkLevel: resolvedThinkLevel,
321
- timeoutMs,
322
- runId,
323
- extraSystemPrompt: opts.extraSystemPrompt,
324
- cliSessionId,
325
- images: opts.images,
326
- streamParams: opts.streamParams,
327
- });
328
- }
329
- const authProfileId = providerOverride === provider ? sessionEntry?.authProfileOverride : undefined;
330
- return runEmbeddedPiAgent({
419
+ const isFallbackRetry = fallbackAttemptIndex > 0;
420
+ fallbackAttemptIndex += 1;
421
+ return runAgentAttempt({
422
+ providerOverride,
423
+ modelOverride,
424
+ cfg,
425
+ sessionEntry,
331
426
  sessionId,
332
427
  sessionKey,
333
- messageChannel,
334
- agentAccountId: runContext.accountId,
335
- messageTo: opts.replyTo ?? opts.to,
336
- messageThreadId: opts.threadId,
337
- groupId: runContext.groupId,
338
- groupChannel: runContext.groupChannel,
339
- groupSpace: runContext.groupSpace,
340
- spawnedBy,
341
- currentChannelId: runContext.currentChannelId,
342
- currentThreadTs: runContext.currentThreadTs,
343
- replyToMode: runContext.replyToMode,
344
- hasRepliedRef: runContext.hasRepliedRef,
345
- senderIsOwner: true,
428
+ sessionAgentId,
346
429
  sessionFile,
347
430
  workspaceDir,
348
- config: cfg,
349
- skillsSnapshot,
350
- prompt: body,
351
- images: opts.images,
352
- clientTools: opts.clientTools,
353
- provider: providerOverride,
354
- model: modelOverride,
355
- authProfileId,
356
- authProfileIdSource: authProfileId
357
- ? sessionEntry?.authProfileOverrideSource
358
- : undefined,
359
- thinkLevel: resolvedThinkLevel,
360
- verboseLevel: resolvedVerboseLevel,
431
+ body,
432
+ isFallbackRetry,
433
+ resolvedThinkLevel,
361
434
  timeoutMs,
362
435
  runId,
363
- lane: opts.lane,
364
- abortSignal: opts.abortSignal,
365
- extraSystemPrompt: opts.extraSystemPrompt,
366
- streamParams: opts.streamParams,
436
+ opts,
437
+ runContext,
438
+ spawnedBy,
439
+ messageChannel,
440
+ skillsSnapshot,
441
+ resolvedVerboseLevel,
367
442
  agentDir,
443
+ primaryProvider: provider,
368
444
  onAgentEvent: (evt) => {
369
445
  // Track lifecycle end for fallback emission below.
370
446
  if (evt.stream === "lifecycle" &&
@@ -0,0 +1,23 @@
1
+ import { vi } from "vitest";
2
+ export const configMocks = {
3
+ readConfigFileSnapshot: vi.fn(),
4
+ writeConfigFile: vi.fn().mockResolvedValue(undefined),
5
+ };
6
+ export const offsetMocks = {
7
+ deleteTelegramUpdateOffset: vi.fn().mockResolvedValue(undefined),
8
+ };
9
+ vi.mock("../config/config.js", async (importOriginal) => {
10
+ const actual = await importOriginal();
11
+ return {
12
+ ...actual,
13
+ readConfigFileSnapshot: configMocks.readConfigFileSnapshot,
14
+ writeConfigFile: configMocks.writeConfigFile,
15
+ };
16
+ });
17
+ vi.mock("../telegram/update-offset-store.js", async (importOriginal) => {
18
+ const actual = await importOriginal();
19
+ return {
20
+ ...actual,
21
+ deleteTelegramUpdateOffset: offsetMocks.deleteTelegramUpdateOffset,
22
+ };
23
+ });
@@ -0,0 +1,11 @@
1
+ import { renderSystemNodeWarning, resolveSystemNodeInfo } from "../daemon/runtime-paths.js";
2
+ export async function emitNodeRuntimeWarning(params) {
3
+ if (params.runtime !== "node") {
4
+ return;
5
+ }
6
+ const systemNode = await resolveSystemNodeInfo({ env: params.env });
7
+ const warning = renderSystemNodeWarning(systemNode, params.nodeProgram);
8
+ if (warning) {
9
+ params.warn?.(warning, params.title);
10
+ }
11
+ }
@@ -85,11 +85,11 @@ export function validateGatewayPasswordInput(value) {
85
85
  export function printWizardHeader(runtime) {
86
86
  const header = [
87
87
  "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
88
- "██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░██",
89
- "██░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░██",
90
- "██░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██",
88
+ "██░▄▄░███░▄▄▄░██░▄▄▄░██░██████░▄▄▀░██░▄▄▄░██▀▀░▀▀███",
89
+ "██░▀▀░███░███░██░███░██░██████░██▄░██░███░████░█████",
90
+ "██░██████░▀▀▀░██░▀▀▀░██░▀▀▀░██░▀▀▀░██░▀▀▀░████░█████",
91
91
  "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
92
- " 🦞 POOL BOT 🦞 ",
92
+ " 🎱 POOL BOT 🎱 ",
93
93
  " ",
94
94
  ].join("\n");
95
95
  runtime.log(header);
@@ -0,0 +1,61 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { vi } from "vitest";
5
+ export function mockSessionsConfig() {
6
+ vi.mock("../config/config.js", async (importOriginal) => {
7
+ const actual = await importOriginal();
8
+ return {
9
+ ...actual,
10
+ loadConfig: () => ({
11
+ agents: {
12
+ defaults: {
13
+ model: { primary: "pi:opus" },
14
+ models: { "pi:opus": {} },
15
+ contextTokens: 32000,
16
+ },
17
+ },
18
+ }),
19
+ };
20
+ });
21
+ }
22
+ export function makeRuntime(params) {
23
+ const logs = [];
24
+ const errors = [];
25
+ const throwOnError = params?.throwOnError ?? false;
26
+ return {
27
+ runtime: {
28
+ log: (msg) => logs.push(String(msg)),
29
+ error: (msg) => {
30
+ errors.push(String(msg));
31
+ if (throwOnError) {
32
+ throw new Error(String(msg));
33
+ }
34
+ },
35
+ exit: (code) => {
36
+ throw new Error(`exit ${code}`);
37
+ },
38
+ },
39
+ logs,
40
+ errors,
41
+ };
42
+ }
43
+ export function writeStore(data, prefix = "sessions") {
44
+ const file = path.join(os.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
45
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
46
+ return file;
47
+ }
48
+ export async function runSessionsJson(run, store, options) {
49
+ const { runtime, logs } = makeRuntime();
50
+ try {
51
+ await run({
52
+ store,
53
+ json: true,
54
+ active: options?.active,
55
+ }, runtime);
56
+ }
57
+ finally {
58
+ fs.rmSync(store, { force: true });
59
+ }
60
+ return JSON.parse(logs[0] ?? "{}");
61
+ }
@@ -1,6 +1,6 @@
1
- export const LEGACY_PROJECT_NAME = "poolbot";
1
+ export const LEGACY_PROJECT_NAME = "clawdbot";
2
2
  export const LEGACY_MANIFEST_KEY = LEGACY_PROJECT_NAME;
3
- export const MANIFEST_KEY = LEGACY_PROJECT_NAME;
3
+ export const MANIFEST_KEY = "poolbot";
4
4
  export const LEGACY_MANIFEST_KEYS = [LEGACY_MANIFEST_KEY];
5
5
  export const LEGACY_PLUGIN_MANIFEST_FILENAME = `${LEGACY_PROJECT_NAME}.plugin.json`;
6
6
  export const LEGACY_CANVAS_HANDLER_NAME = `${LEGACY_PROJECT_NAME}CanvasA2UIAction`;
@@ -36,3 +36,6 @@ export function isNativeCommandsExplicitlyDisabled(params) {
36
36
  return globalSetting === false;
37
37
  return false;
38
38
  }
39
+ export function isRestartEnabled(config) {
40
+ return config?.commands?.restart !== false;
41
+ }
@@ -1,4 +1,4 @@
1
- export { createConfigIO, loadConfig, parseConfigJson5, readConfigFileSnapshot, resolveConfigSnapshotHash, writeConfigFile, } from "./io.js";
1
+ export { createConfigIO, loadConfig, parseConfigJson5, readConfigFileSnapshot, readConfigFileSnapshotForWrite, resolveConfigSnapshotHash, writeConfigFile, } from "./io.js";
2
2
  export { migrateLegacyConfig } from "./legacy-migrate.js";
3
3
  export * from "./paths.js";
4
4
  export * from "./runtime-overrides.js";
@@ -21,6 +21,7 @@
21
21
  */
22
22
  // Pattern for valid uppercase env var names: starts with letter or underscore,
23
23
  // followed by letters, numbers, or underscores (all uppercase)
24
+ import { isPlainObject } from "../utils.js";
24
25
  const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
25
26
  export class MissingEnvVarError extends Error {
26
27
  varName;
@@ -32,11 +33,35 @@ export class MissingEnvVarError extends Error {
32
33
  this.name = "MissingEnvVarError";
33
34
  }
34
35
  }
35
- function isPlainObject(value) {
36
- return (typeof value === "object" &&
37
- value !== null &&
38
- !Array.isArray(value) &&
39
- Object.prototype.toString.call(value) === "[object Object]");
36
+ function parseEnvTokenAt(value, index) {
37
+ if (value[index] !== "$") {
38
+ return null;
39
+ }
40
+ const next = value[index + 1];
41
+ const afterNext = value[index + 2];
42
+ // Escaped: $${VAR} -> ${VAR}
43
+ if (next === "$" && afterNext === "{") {
44
+ const start = index + 3;
45
+ const end = value.indexOf("}", start);
46
+ if (end !== -1) {
47
+ const name = value.slice(start, end);
48
+ if (ENV_VAR_NAME_PATTERN.test(name)) {
49
+ return { kind: "escaped", name, end };
50
+ }
51
+ }
52
+ }
53
+ // Substitution: ${VAR} -> value
54
+ if (next === "{") {
55
+ const start = index + 2;
56
+ const end = value.indexOf("}", start);
57
+ if (end !== -1) {
58
+ const name = value.slice(start, end);
59
+ if (ENV_VAR_NAME_PATTERN.test(name)) {
60
+ return { kind: "substitution", name, end };
61
+ }
62
+ }
63
+ }
64
+ return null;
40
65
  }
41
66
  function substituteString(value, env, configPath) {
42
67
  if (!value.includes("$")) {
@@ -49,43 +74,46 @@ function substituteString(value, env, configPath) {
49
74
  chunks.push(char);
50
75
  continue;
51
76
  }
52
- const next = value[i + 1];
53
- const afterNext = value[i + 2];
54
- // Escaped: $${VAR} -> ${VAR}
55
- if (next === "$" && afterNext === "{") {
56
- const start = i + 3;
57
- const end = value.indexOf("}", start);
58
- if (end !== -1) {
59
- const name = value.slice(start, end);
60
- if (ENV_VAR_NAME_PATTERN.test(name)) {
61
- chunks.push(`\${${name}}`);
62
- i = end;
63
- continue;
64
- }
65
- }
77
+ const token = parseEnvTokenAt(value, i);
78
+ if (token?.kind === "escaped") {
79
+ chunks.push(`\${${token.name}}`);
80
+ i = token.end;
81
+ continue;
66
82
  }
67
- // Substitution: ${VAR} -> value
68
- if (next === "{") {
69
- const start = i + 2;
70
- const end = value.indexOf("}", start);
71
- if (end !== -1) {
72
- const name = value.slice(start, end);
73
- if (ENV_VAR_NAME_PATTERN.test(name)) {
74
- const envValue = env[name];
75
- if (envValue === undefined || envValue === "") {
76
- throw new MissingEnvVarError(name, configPath);
77
- }
78
- chunks.push(envValue);
79
- i = end;
80
- continue;
81
- }
83
+ if (token?.kind === "substitution") {
84
+ const envValue = env[token.name];
85
+ if (envValue === undefined || envValue === "") {
86
+ throw new MissingEnvVarError(token.name, configPath);
82
87
  }
88
+ chunks.push(envValue);
89
+ i = token.end;
90
+ continue;
83
91
  }
84
92
  // Leave untouched if not a recognized pattern
85
93
  chunks.push(char);
86
94
  }
87
95
  return chunks.join("");
88
96
  }
97
+ export function containsEnvVarReference(value) {
98
+ if (!value.includes("$")) {
99
+ return false;
100
+ }
101
+ for (let i = 0; i < value.length; i += 1) {
102
+ const char = value[i];
103
+ if (char !== "$") {
104
+ continue;
105
+ }
106
+ const token = parseEnvTokenAt(value, i);
107
+ if (token?.kind === "escaped") {
108
+ i = token.end;
109
+ continue;
110
+ }
111
+ if (token?.kind === "substitution") {
112
+ return true;
113
+ }
114
+ }
115
+ return false;
116
+ }
89
117
  function substituteAny(value, env, path) {
90
118
  if (typeof value === "string") {
91
119
  return substituteString(value, env, path);
@@ -19,3 +19,12 @@ export function collectConfigEnvVars(cfg) {
19
19
  }
20
20
  return entries;
21
21
  }
22
+ export function applyConfigEnvVars(cfg, env = process.env) {
23
+ const entries = collectConfigEnvVars(cfg);
24
+ for (const [key, value] of Object.entries(entries)) {
25
+ if (env[key]?.trim()) {
26
+ continue;
27
+ }
28
+ env[key] = value;
29
+ }
30
+ }