@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
@@ -13,6 +13,7 @@ export function createTelegramDraftStream(params) {
13
13
  : threadParams;
14
14
  let streamMessageId;
15
15
  let lastSentText = "";
16
+ let lastSentParseMode;
16
17
  let stopped = false;
17
18
  let isFinal = false;
18
19
  const sendOrEditStreamMessage = async (text) => {
@@ -24,29 +25,49 @@ export function createTelegramDraftStream(params) {
24
25
  if (!trimmed) {
25
26
  return false;
26
27
  }
27
- if (trimmed.length > maxChars) {
28
+ const rendered = params.renderText?.(trimmed) ?? { text: trimmed };
29
+ const renderedText = rendered.text.trimEnd();
30
+ const renderedParseMode = rendered.parseMode;
31
+ if (!renderedText) {
32
+ return false;
33
+ }
34
+ if (renderedText.length > maxChars) {
28
35
  // Telegram text messages/edits cap at 4096 chars.
29
36
  // Stop streaming once we exceed the cap to avoid repeated API failures.
30
37
  stopped = true;
31
- params.warn?.(`telegram stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
38
+ params.warn?.(`telegram stream preview stopped (text length ${renderedText.length} > ${maxChars})`);
32
39
  return false;
33
40
  }
34
- if (trimmed === lastSentText) {
41
+ if (renderedText === lastSentText && renderedParseMode === lastSentParseMode) {
35
42
  return true;
36
43
  }
37
44
  // Debounce first preview send for better push notification quality.
38
45
  if (typeof streamMessageId !== "number" && minInitialChars != null && !isFinal) {
39
- if (trimmed.length < minInitialChars) {
46
+ if (renderedText.length < minInitialChars) {
40
47
  return false;
41
48
  }
42
49
  }
43
- lastSentText = trimmed;
50
+ lastSentText = renderedText;
51
+ lastSentParseMode = renderedParseMode;
44
52
  try {
45
53
  if (typeof streamMessageId === "number") {
46
- await params.api.editMessageText(chatId, streamMessageId, trimmed);
54
+ if (renderedParseMode) {
55
+ await params.api.editMessageText(chatId, streamMessageId, renderedText, {
56
+ parse_mode: renderedParseMode,
57
+ });
58
+ }
59
+ else {
60
+ await params.api.editMessageText(chatId, streamMessageId, renderedText);
61
+ }
47
62
  return true;
48
63
  }
49
- const sent = await params.api.sendMessage(chatId, trimmed, replyParams);
64
+ const sendParams = renderedParseMode
65
+ ? {
66
+ ...replyParams,
67
+ parse_mode: renderedParseMode,
68
+ }
69
+ : replyParams;
70
+ const sent = await params.api.sendMessage(chatId, renderedText, sendParams);
50
71
  const sentMessageId = sent?.message_id;
51
72
  if (typeof sentMessageId !== "number" || !Number.isFinite(sentMessageId)) {
52
73
  stopped = true;
@@ -88,6 +109,7 @@ export function createTelegramDraftStream(params) {
88
109
  }
89
110
  try {
90
111
  await params.api.deleteMessage(chatId, messageId);
112
+ params.log?.(`telegram stream preview deleted (chat=${chatId}, message=${messageId})`);
91
113
  }
92
114
  catch (err) {
93
115
  params.warn?.(`telegram stream preview cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -96,6 +118,7 @@ export function createTelegramDraftStream(params) {
96
118
  const forceNewMessage = () => {
97
119
  streamMessageId = undefined;
98
120
  lastSentText = "";
121
+ lastSentParseMode = undefined;
99
122
  loop.resetPending();
100
123
  };
101
124
  params.log?.(`telegram stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
@@ -0,0 +1,128 @@
1
+ import { formatReasoningMessage } from "../agents/pi-embedded-utils.js";
2
+ import { stripReasoningTagsFromText } from "../shared/text/reasoning-tags.js";
3
+ const REASONING_MESSAGE_PREFIX = "Reasoning:\n";
4
+ const REASONING_TAG_PREFIXES = [
5
+ "<think",
6
+ "<thinking",
7
+ "<thought",
8
+ "<antthinking",
9
+ "</think",
10
+ "</thinking",
11
+ "</thought",
12
+ "</antthinking",
13
+ ];
14
+ const THINKING_TAG_RE = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>/gi;
15
+ function findCodeRegions(text) {
16
+ const regions = [];
17
+ const fencedRe = /(^|\n)(```|~~~)[^\n]*\n[\s\S]*?(?:\n\2(?:\n|$)|$)/g;
18
+ for (const match of text.matchAll(fencedRe)) {
19
+ const start = (match.index ?? 0) + match[1].length;
20
+ regions.push({ start, end: start + match[0].length - match[1].length });
21
+ }
22
+ const inlineRe = /`+[^`]+`+/g;
23
+ for (const match of text.matchAll(inlineRe)) {
24
+ const start = match.index ?? 0;
25
+ const end = start + match[0].length;
26
+ const insideFenced = regions.some((r) => start >= r.start && end <= r.end);
27
+ if (!insideFenced) {
28
+ regions.push({ start, end });
29
+ }
30
+ }
31
+ regions.sort((a, b) => a.start - b.start);
32
+ return regions;
33
+ }
34
+ function isInsideCode(pos, regions) {
35
+ return regions.some((r) => pos >= r.start && pos < r.end);
36
+ }
37
+ function extractThinkingFromTaggedStreamOutsideCode(text) {
38
+ if (!text) {
39
+ return "";
40
+ }
41
+ const codeRegions = findCodeRegions(text);
42
+ let result = "";
43
+ let lastIndex = 0;
44
+ let inThinking = false;
45
+ THINKING_TAG_RE.lastIndex = 0;
46
+ for (const match of text.matchAll(THINKING_TAG_RE)) {
47
+ const idx = match.index ?? 0;
48
+ if (isInsideCode(idx, codeRegions)) {
49
+ continue;
50
+ }
51
+ if (inThinking) {
52
+ result += text.slice(lastIndex, idx);
53
+ }
54
+ const isClose = match[1] === "/";
55
+ inThinking = !isClose;
56
+ lastIndex = idx + match[0].length;
57
+ }
58
+ if (inThinking) {
59
+ result += text.slice(lastIndex);
60
+ }
61
+ return result.trim();
62
+ }
63
+ function isPartialReasoningTagPrefix(text) {
64
+ const trimmed = text.trimStart().toLowerCase();
65
+ if (!trimmed.startsWith("<")) {
66
+ return false;
67
+ }
68
+ if (trimmed.includes(">")) {
69
+ return false;
70
+ }
71
+ return REASONING_TAG_PREFIXES.some((prefix) => prefix.startsWith(trimmed));
72
+ }
73
+ export function splitTelegramReasoningText(text) {
74
+ if (typeof text !== "string") {
75
+ return {};
76
+ }
77
+ const trimmed = text.trim();
78
+ if (isPartialReasoningTagPrefix(trimmed)) {
79
+ return {};
80
+ }
81
+ if (trimmed.startsWith(REASONING_MESSAGE_PREFIX) &&
82
+ trimmed.length > REASONING_MESSAGE_PREFIX.length) {
83
+ return { reasoningText: trimmed };
84
+ }
85
+ const taggedReasoning = extractThinkingFromTaggedStreamOutsideCode(text);
86
+ const strippedAnswer = stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
87
+ if (!taggedReasoning && strippedAnswer === text) {
88
+ return { answerText: text };
89
+ }
90
+ const reasoningText = taggedReasoning ? formatReasoningMessage(taggedReasoning) : undefined;
91
+ const answerText = strippedAnswer || undefined;
92
+ return { reasoningText, answerText };
93
+ }
94
+ export function createTelegramReasoningStepState() {
95
+ let reasoningStatus = "none";
96
+ let bufferedFinalAnswer;
97
+ const noteReasoningHint = () => {
98
+ if (reasoningStatus === "none") {
99
+ reasoningStatus = "hinted";
100
+ }
101
+ };
102
+ const noteReasoningDelivered = () => {
103
+ reasoningStatus = "delivered";
104
+ };
105
+ const shouldBufferFinalAnswer = () => {
106
+ return reasoningStatus === "hinted" && !bufferedFinalAnswer;
107
+ };
108
+ const bufferFinalAnswer = (value) => {
109
+ bufferedFinalAnswer = value;
110
+ };
111
+ const takeBufferedFinalAnswer = () => {
112
+ const value = bufferedFinalAnswer;
113
+ bufferedFinalAnswer = undefined;
114
+ return value;
115
+ };
116
+ const resetForNextStep = () => {
117
+ reasoningStatus = "none";
118
+ bufferedFinalAnswer = undefined;
119
+ };
120
+ return {
121
+ noteReasoningHint,
122
+ noteReasoningDelivered,
123
+ shouldBufferFinalAnswer,
124
+ bufferFinalAnswer,
125
+ takeBufferedFinalAnswer,
126
+ resetForNextStep,
127
+ };
128
+ }
@@ -0,0 +1,9 @@
1
+ import { select } from "@clack/prompts";
2
+ import { stylePromptHint, stylePromptMessage } from "./prompt-style.js";
3
+ export function selectStyled(params) {
4
+ return select({
5
+ ...params,
6
+ message: stylePromptMessage(params.message),
7
+ options: params.options.map((opt) => opt.hint === undefined ? opt : { ...opt, hint: stylePromptHint(opt.hint) }),
8
+ });
9
+ }
@@ -0,0 +1,6 @@
1
+ import { Command } from "commander";
2
+ export async function runRegisteredCli(params) {
3
+ const program = new Command();
4
+ params.register(program);
5
+ await program.parseAsync(params.argv, { from: "user" });
6
+ }
@@ -0,0 +1,10 @@
1
+ export function createInternalHookEventPayload(type, action, sessionKey, context) {
2
+ return {
3
+ type,
4
+ action,
5
+ sessionKey,
6
+ context,
7
+ timestamp: new Date(),
8
+ messages: [],
9
+ };
10
+ }
@@ -0,0 +1,12 @@
1
+ import { vi } from "vitest";
2
+ export function createModelAuthMockModule() {
3
+ return {
4
+ resolveApiKeyForProvider: vi.fn(),
5
+ requireApiKey: (auth, provider) => {
6
+ if (auth?.apiKey) {
7
+ return auth.apiKey;
8
+ }
9
+ throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth?.mode}).`);
10
+ },
11
+ };
12
+ }
@@ -0,0 +1,14 @@
1
+ import { vi } from "vitest";
2
+ import { withFetchPreconnect } from "./fetch-mock.js";
3
+ export function makeResponse(status, body) {
4
+ const payload = typeof body === "string" ? body : JSON.stringify(body);
5
+ const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" };
6
+ return new Response(payload, { status, headers });
7
+ }
8
+ export function toRequestUrl(input) {
9
+ return typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
10
+ }
11
+ export function createProviderUsageFetch(handler) {
12
+ const mockFetch = vi.fn(async (input, init) => handler(toRequestUrl(input), init));
13
+ return withFetchPreconnect(mockFetch);
14
+ }
@@ -0,0 +1,33 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { captureEnv } from "./env.js";
5
+ const HOME_ENV_KEYS = [
6
+ "HOME",
7
+ "USERPROFILE",
8
+ "HOMEDRIVE",
9
+ "HOMEPATH",
10
+ "CLAWDBOT_STATE_DIR",
11
+ ];
12
+ export async function createTempHomeEnv(prefix) {
13
+ const home = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
14
+ await fs.mkdir(path.join(home, ".poolbot"), { recursive: true });
15
+ const snapshot = captureEnv([...HOME_ENV_KEYS]);
16
+ process.env.HOME = home;
17
+ process.env.USERPROFILE = home;
18
+ process.env.CLAWDBOT_STATE_DIR = path.join(home, ".poolbot");
19
+ if (process.platform === "win32") {
20
+ const match = home.match(/^([A-Za-z]:)(.*)$/);
21
+ if (match) {
22
+ process.env.HOMEDRIVE = match[1];
23
+ process.env.HOMEPATH = match[2] || "\\";
24
+ }
25
+ }
26
+ return {
27
+ home,
28
+ restore: async () => {
29
+ snapshot.restore();
30
+ await fs.rm(home, { recursive: true, force: true });
31
+ },
32
+ };
33
+ }
@@ -47,6 +47,15 @@ export class ChatLog extends Container {
47
47
  }
48
48
  this.addChild(new AssistantMessageComponent(text));
49
49
  }
50
+ dropAssistant(runId) {
51
+ const effectiveRunId = this.resolveRunId(runId);
52
+ const existing = this.streamingRuns.get(effectiveRunId);
53
+ if (!existing) {
54
+ return;
55
+ }
56
+ this.removeChild(existing);
57
+ this.streamingRuns.delete(effectiveRunId);
58
+ }
50
59
  startTool(toolCallId, toolName, args) {
51
60
  const existing = this.toolById.get(toolCallId);
52
61
  if (existing) {
@@ -1,20 +1,15 @@
1
- import { truncateToWidth } from "@mariozechner/pi-tui";
2
1
  import { formatThinkingLevels, normalizeUsageDisplay, resolveResponseUsageMode, } from "../auto-reply/thinking.js";
2
+ import { formatRelativeTimestamp } from "../infra/format-time/format-relative.js";
3
3
  import { normalizeAgentId } from "../routing/session-key.js";
4
- import { formatRelativeTime } from "../utils/time-format.js";
5
4
  import { helpText, parseCommand } from "./commands.js";
6
5
  import { createFilterableSelectList, createSearchableSelectList, createSettingsList, } from "./components/selectors.js";
7
6
  import { formatStatusSummary } from "./tui-status-summary.js";
8
7
  export function createCommandHandlers(context) {
9
- const { client, chatLog, tui, opts, state, deliverDefault, openOverlay, closeOverlay, refreshSessionInfo, loadHistory, setSession, refreshAgents, abortActive, setActivityStatus, formatSessionKey, } = context;
8
+ const { client, chatLog, tui, opts, state, deliverDefault, openOverlay, closeOverlay, refreshSessionInfo, loadHistory, setSession, refreshAgents, abortActive, setActivityStatus, formatSessionKey, applySessionInfoFromPatch, noteLocalRunId, forgetLocalRunId, } = context;
10
9
  const setAgent = async (id) => {
11
10
  state.currentAgentId = normalizeAgentId(id);
12
11
  await setSession("");
13
12
  };
14
- // Maximum visible width for overlay item strings. The overlay has its own
15
- // padding/chrome (~4 chars). We subtract a generous margin so that even
16
- // after ANSI formatting, lines stay within terminal bounds.
17
- const overlayItemWidth = () => Math.max(30, (tui.terminal.columns ?? 80) - 6);
18
13
  const openModelSelector = async () => {
19
14
  try {
20
15
  const models = await client.listModels();
@@ -23,21 +18,21 @@ export function createCommandHandlers(context) {
23
18
  tui.requestRender();
24
19
  return;
25
20
  }
26
- const maxW = overlayItemWidth();
27
21
  const items = models.map((model) => ({
28
22
  value: `${model.provider}/${model.id}`,
29
- label: truncateToWidth(`${model.provider}/${model.id}`, maxW),
30
- description: model.name && model.name !== model.id ? truncateToWidth(model.name, maxW) : "",
23
+ label: `${model.provider}/${model.id}`,
24
+ description: model.name && model.name !== model.id ? model.name : "",
31
25
  }));
32
26
  const selector = createSearchableSelectList(items, 9);
33
27
  selector.onSelect = (item) => {
34
28
  void (async () => {
35
29
  try {
36
- await client.patchSession({
30
+ const result = await client.patchSession({
37
31
  key: state.currentSessionKey,
38
32
  model: item.value,
39
33
  });
40
34
  chatLog.addSystem(`model set to ${item.value}`);
35
+ applySessionInfoFromPatch(result);
41
36
  await refreshSessionInfo();
42
37
  }
43
38
  catch (err) {
@@ -66,10 +61,9 @@ export function createCommandHandlers(context) {
66
61
  tui.requestRender();
67
62
  return;
68
63
  }
69
- const maxW = overlayItemWidth();
70
64
  const items = state.agents.map((agent) => ({
71
65
  value: agent.id,
72
- label: truncateToWidth(agent.name ? `${agent.id} (${agent.name})` : agent.id, maxW),
66
+ label: agent.name ? `${agent.id} (${agent.name})` : agent.id,
73
67
  description: agent.id === state.agentDefaultId ? "default" : "",
74
68
  }));
75
69
  const selector = createSearchableSelectList(items, 9);
@@ -96,20 +90,21 @@ export function createCommandHandlers(context) {
96
90
  includeLastMessage: true,
97
91
  agentId: state.currentAgentId,
98
92
  });
99
- const maxW = overlayItemWidth();
100
93
  const items = result.sessions.map((session) => {
101
94
  const title = session.derivedTitle ?? session.displayName;
102
95
  const formattedKey = formatSessionKey(session.key);
103
96
  // Avoid redundant "title (key)" when title matches key
104
97
  const label = title && title !== formattedKey ? `${title} (${formattedKey})` : formattedKey;
105
98
  // Build description: time + message preview
106
- const timePart = session.updatedAt ? formatRelativeTime(session.updatedAt) : "";
99
+ const timePart = session.updatedAt
100
+ ? formatRelativeTimestamp(session.updatedAt, { dateFallback: true, fallback: "" })
101
+ : "";
107
102
  const preview = session.lastMessagePreview?.replace(/\s+/g, " ").trim();
108
103
  const description = timePart && preview ? `${timePart} · ${preview}` : (preview ?? timePart);
109
104
  return {
110
105
  value: session.key,
111
- label: truncateToWidth(label, maxW),
112
- description: truncateToWidth(description, maxW),
106
+ label,
107
+ description,
113
108
  searchText: [
114
109
  session.displayName,
115
110
  session.label,
@@ -176,8 +171,9 @@ export function createCommandHandlers(context) {
176
171
  };
177
172
  const handleCommand = async (raw) => {
178
173
  const { name, args } = parseCommand(raw);
179
- if (!name)
174
+ if (!name) {
180
175
  return;
176
+ }
181
177
  switch (name) {
182
178
  case "help":
183
179
  chatLog.addSystem(helpText({
@@ -194,8 +190,9 @@ export function createCommandHandlers(context) {
194
190
  }
195
191
  if (status && typeof status === "object") {
196
192
  const lines = formatStatusSummary(status);
197
- for (const line of lines)
193
+ for (const line of lines) {
198
194
  chatLog.addSystem(line);
195
+ }
199
196
  break;
200
197
  }
201
198
  chatLog.addSystem("status: unknown response");
@@ -232,11 +229,12 @@ export function createCommandHandlers(context) {
232
229
  }
233
230
  else {
234
231
  try {
235
- await client.patchSession({
232
+ const result = await client.patchSession({
236
233
  key: state.currentSessionKey,
237
234
  model: args,
238
235
  });
239
236
  chatLog.addSystem(`model set to ${args}`);
237
+ applySessionInfoFromPatch(result);
240
238
  await refreshSessionInfo();
241
239
  }
242
240
  catch (err) {
@@ -254,11 +252,12 @@ export function createCommandHandlers(context) {
254
252
  break;
255
253
  }
256
254
  try {
257
- await client.patchSession({
255
+ const result = await client.patchSession({
258
256
  key: state.currentSessionKey,
259
257
  thinkingLevel: args,
260
258
  });
261
259
  chatLog.addSystem(`thinking set to ${args}`);
260
+ applySessionInfoFromPatch(result);
262
261
  await refreshSessionInfo();
263
262
  }
264
263
  catch (err) {
@@ -271,12 +270,13 @@ export function createCommandHandlers(context) {
271
270
  break;
272
271
  }
273
272
  try {
274
- await client.patchSession({
273
+ const result = await client.patchSession({
275
274
  key: state.currentSessionKey,
276
275
  verboseLevel: args,
277
276
  });
278
277
  chatLog.addSystem(`verbose set to ${args}`);
279
- await refreshSessionInfo();
278
+ applySessionInfoFromPatch(result);
279
+ await loadHistory();
280
280
  }
281
281
  catch (err) {
282
282
  chatLog.addSystem(`verbose failed: ${String(err)}`);
@@ -288,11 +288,12 @@ export function createCommandHandlers(context) {
288
288
  break;
289
289
  }
290
290
  try {
291
- await client.patchSession({
291
+ const result = await client.patchSession({
292
292
  key: state.currentSessionKey,
293
293
  reasoningLevel: args,
294
294
  });
295
295
  chatLog.addSystem(`reasoning set to ${args}`);
296
+ applySessionInfoFromPatch(result);
296
297
  await refreshSessionInfo();
297
298
  }
298
299
  catch (err) {
@@ -309,11 +310,12 @@ export function createCommandHandlers(context) {
309
310
  const current = resolveResponseUsageMode(currentRaw);
310
311
  const next = normalized ?? (current === "off" ? "tokens" : current === "tokens" ? "full" : "off");
311
312
  try {
312
- await client.patchSession({
313
+ const result = await client.patchSession({
313
314
  key: state.currentSessionKey,
314
315
  responseUsage: next === "off" ? null : next,
315
316
  });
316
317
  chatLog.addSystem(`usage footer: ${next}`);
318
+ applySessionInfoFromPatch(result);
317
319
  await refreshSessionInfo();
318
320
  }
319
321
  catch (err) {
@@ -331,11 +333,12 @@ export function createCommandHandlers(context) {
331
333
  break;
332
334
  }
333
335
  try {
334
- await client.patchSession({
336
+ const result = await client.patchSession({
335
337
  key: state.currentSessionKey,
336
338
  elevatedLevel: args,
337
339
  });
338
340
  chatLog.addSystem(`elevated set to ${args}`);
341
+ applySessionInfoFromPatch(result);
339
342
  await refreshSessionInfo();
340
343
  }
341
344
  catch (err) {
@@ -348,11 +351,12 @@ export function createCommandHandlers(context) {
348
351
  break;
349
352
  }
350
353
  try {
351
- await client.patchSession({
354
+ const result = await client.patchSession({
352
355
  key: state.currentSessionKey,
353
356
  groupActivation: args === "always" ? "always" : "mention",
354
357
  });
355
358
  chatLog.addSystem(`activation set to ${args}`);
359
+ applySessionInfoFromPatch(result);
356
360
  await refreshSessionInfo();
357
361
  }
358
362
  catch (err) {
@@ -405,10 +409,15 @@ export function createCommandHandlers(context) {
405
409
  deliver: deliverDefault,
406
410
  timeoutMs: opts.timeoutMs,
407
411
  });
412
+ noteLocalRunId(runId);
408
413
  state.activeChatRunId = runId;
409
414
  setActivityStatus("waiting");
410
415
  }
411
416
  catch (err) {
417
+ if (state.activeChatRunId) {
418
+ forgetLocalRunId?.(state.activeChatRunId);
419
+ }
420
+ state.activeChatRunId = null;
412
421
  chatLog.addSystem(`send failed: ${String(err)}`);
413
422
  setActivityStatus("error");
414
423
  }