@poolzin/pool-bot 2026.3.25 → 2026.3.27

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 (271) hide show
  1. package/dist/agents/model-fallback.js +5 -4
  2. package/dist/agents/tools/common.js +16 -201
  3. package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
  4. package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
  5. package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
  6. package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
  7. package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
  8. package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
  9. package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
  10. package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
  11. package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
  12. package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
  13. package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
  14. package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
  15. package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
  16. package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
  17. package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
  18. package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
  19. package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
  20. package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
  21. package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
  22. package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
  23. package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
  24. package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
  25. package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
  26. package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
  27. package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
  28. package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
  29. package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
  30. package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
  31. package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
  32. package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
  33. package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
  34. package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
  35. package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
  36. package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
  37. package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
  38. package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
  39. package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
  40. package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
  41. package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
  42. package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
  43. package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
  44. package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
  45. package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
  46. package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
  47. package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
  48. package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
  49. package/dist/build-info.json +3 -3
  50. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  51. package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
  52. package/dist/canvas-host/a2ui/index.html +1 -307
  53. package/dist/channels/channels/directory-config.js +185 -0
  54. package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
  55. package/dist/channels/channels/discord/handle-action.js +165 -0
  56. package/dist/channels/channels/discord.js +413 -0
  57. package/dist/channels/channels/dock.js +436 -0
  58. package/dist/channels/channels/index.js +51 -0
  59. package/dist/channels/channels/plugins/outbound/discord.js +101 -0
  60. package/dist/channels/channels/whatsapp.js +17 -0
  61. package/dist/channels/plugins/types.js +1 -1
  62. package/dist/channels/run-state-machine.js +7 -0
  63. package/dist/commands/models/auth.js +47 -1
  64. package/dist/commands-subagents/action-agents.js +44 -0
  65. package/dist/commands-subagents/action-focus.js +64 -0
  66. package/dist/commands-subagents/action-help.js +4 -0
  67. package/dist/commands-subagents/action-info.js +45 -0
  68. package/dist/commands-subagents/action-kill.js +60 -0
  69. package/dist/commands-subagents/action-list.js +44 -0
  70. package/dist/commands-subagents/action-log.js +29 -0
  71. package/dist/commands-subagents/action-send.js +119 -0
  72. package/dist/commands-subagents/action-spawn.js +52 -0
  73. package/dist/commands-subagents/action-unfocus.js +30 -0
  74. package/dist/commands-subagents/shared.js +303 -0
  75. package/dist/config/config.js +1 -8
  76. package/dist/config/types.secrets.js +61 -0
  77. package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
  78. package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
  79. package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
  80. package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
  81. package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
  82. package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
  83. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
  84. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
  85. package/dist/control-ui/index.html +1 -1
  86. package/dist/gateway/method-scopes.js +9 -1
  87. package/dist/gateway/node-pending-work.js +142 -0
  88. package/dist/gateway/protocol/index.js +5 -1
  89. package/dist/gateway/protocol/schema/nodes.js +18 -0
  90. package/dist/gateway/server-methods/nodes-pending.js +96 -0
  91. package/dist/gateway/server-methods-list.js +4 -0
  92. package/dist/gateway/server-methods.js +2 -0
  93. package/dist/imessage/channel.js +253 -0
  94. package/dist/imessage/monitor/echo-cache.js +70 -0
  95. package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
  96. package/dist/imessage/monitor/reflection-guard.js +50 -0
  97. package/dist/imessage/monitor/sanitize-outbound.js +25 -0
  98. package/dist/imessage/monitor/self-chat-cache.js +75 -0
  99. package/dist/imessage/runtime.js +3 -0
  100. package/dist/infra/exec-approval-reply.js +7 -0
  101. package/dist/infra/tmp-openclaw-dir.js +84 -0
  102. package/dist/pairing/pairing-challenge.js +15 -0
  103. package/dist/plugin-sdk/account-id.d.ts +1 -0
  104. package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
  105. package/dist/plugin-sdk/allow-from.d.ts +27 -0
  106. package/dist/plugin-sdk/command-auth.d.ts +25 -0
  107. package/dist/plugin-sdk/command-auth.js +3 -1
  108. package/dist/plugin-sdk/config-paths.d.ts +6 -0
  109. package/dist/plugin-sdk/file-lock.d.ts +16 -0
  110. package/dist/plugin-sdk/index.d.ts +428 -0
  111. package/dist/plugin-sdk/index.js +237 -103
  112. package/dist/plugin-sdk/json-store.d.ts +5 -0
  113. package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
  114. package/dist/plugin-sdk/onboarding.d.ts +11 -0
  115. package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
  116. package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
  117. package/dist/plugin-sdk/status-helpers.d.ts +25 -0
  118. package/dist/plugin-sdk/temp-path.d.ts +12 -0
  119. package/dist/plugin-sdk/text-chunking.d.ts +1 -0
  120. package/dist/plugin-sdk/tool-send.d.ts +4 -0
  121. package/dist/plugin-sdk/webhook-path.d.ts +6 -0
  122. package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
  123. package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
  124. package/dist/plugin-sdk-internal/accounts.js +6 -0
  125. package/dist/plugin-sdk-internal/discord.js +23 -0
  126. package/dist/plugin-sdk-internal/imessage.js +13 -0
  127. package/dist/plugin-sdk-internal/setup.js +9 -0
  128. package/dist/plugin-sdk-internal/signal.js +13 -0
  129. package/dist/plugin-sdk-internal/slack.js +22 -0
  130. package/dist/plugin-sdk-internal/telegram.js +32 -0
  131. package/dist/plugin-sdk-internal/whatsapp.js +29 -0
  132. package/dist/routing/session-key.js +4 -185
  133. package/dist/shared/pid-alive.js +2 -61
  134. package/dist/shared/process-scoped-map.js +5 -7
  135. package/dist/signal/channel.js +264 -0
  136. package/dist/signal/monitor/access-policy.js +60 -0
  137. package/dist/signal/runtime.js +3 -0
  138. package/dist/slack/account-inspect.js +135 -0
  139. package/dist/slack/blocks-input.js +7 -38
  140. package/dist/slack/channel.js +394 -0
  141. package/dist/slack/interactive-replies.js +28 -0
  142. package/dist/slack/monitor/channel-type.js +31 -0
  143. package/dist/slack/monitor/dm-auth.js +49 -0
  144. package/dist/slack/monitor/events/interactions.modal.js +137 -0
  145. package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
  146. package/dist/slack/monitor/events/system-event-context.js +29 -0
  147. package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
  148. package/dist/slack/monitor/external-arg-menu-store.js +46 -0
  149. package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
  150. package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
  151. package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
  152. package/dist/slack/monitor/reconnect-policy.js +78 -0
  153. package/dist/slack/monitor/slash-commands.runtime.js +1 -0
  154. package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
  155. package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
  156. package/dist/slack/resolve-allowlist-common.js +36 -0
  157. package/dist/slack/runtime.js +3 -0
  158. package/dist/slack/sent-thread-cache.js +61 -0
  159. package/dist/slack/truncate.js +10 -0
  160. package/dist/telegram/account-inspect.js +175 -0
  161. package/dist/telegram/allow-from.js +10 -0
  162. package/dist/telegram/api-fetch.js +18 -0
  163. package/dist/telegram/approval-buttons.js +30 -0
  164. package/dist/telegram/audit-membership-runtime.js +61 -0
  165. package/dist/telegram/bot/delivery.replies.js +508 -0
  166. package/dist/telegram/bot/delivery.resolve-media.js +227 -0
  167. package/dist/telegram/bot/delivery.send.js +132 -0
  168. package/dist/telegram/bot/reply-threading.js +46 -0
  169. package/dist/telegram/bot-message-context.body.js +186 -0
  170. package/dist/telegram/bot-message-context.session.js +207 -0
  171. package/dist/telegram/bot-message-context.types.js +1 -0
  172. package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
  173. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  174. package/dist/telegram/bot.media.test-utils.js +81 -0
  175. package/dist/telegram/channel-actions.js +225 -0
  176. package/dist/telegram/channel.js +515 -0
  177. package/dist/telegram/conversation-route.js +107 -0
  178. package/dist/telegram/delivery.js +2 -0
  179. package/dist/telegram/delivery.replies.js +508 -0
  180. package/dist/telegram/dm-access.js +86 -0
  181. package/dist/telegram/draft-stream.test-helpers.js +62 -0
  182. package/dist/telegram/exec-approvals-handler.js +281 -0
  183. package/dist/telegram/exec-approvals.js +62 -0
  184. package/dist/telegram/forum-service-message.js +22 -0
  185. package/dist/telegram/group-config-helpers.js +10 -0
  186. package/dist/telegram/lane-delivery-state.js +19 -0
  187. package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
  188. package/dist/telegram/lane-delivery.js +2 -0
  189. package/dist/telegram/normalize.js +37 -0
  190. package/dist/telegram/onboarding.js +192 -0
  191. package/dist/telegram/outbound-adapter.js +100 -0
  192. package/dist/telegram/polling-session.js +275 -0
  193. package/dist/telegram/runtime.js +3 -0
  194. package/dist/telegram/sendchataction-401-backoff.js +71 -0
  195. package/dist/telegram/sequential-key.js +46 -0
  196. package/dist/telegram/status-issues.js +105 -0
  197. package/dist/telegram/target-writeback.js +165 -0
  198. package/dist/telegram/thread-bindings.js +560 -0
  199. package/dist/utils.js +32 -257
  200. package/dist/wizard/prompts.js +5 -5
  201. package/extensions/feishu/src/policy.ts +1 -1
  202. package/extensions/firecrawl/index.test.ts +82 -0
  203. package/extensions/firecrawl/index.ts +20 -0
  204. package/extensions/firecrawl/openclaw.plugin.json +8 -0
  205. package/extensions/firecrawl/package.json +12 -0
  206. package/extensions/firecrawl/src/config.ts +159 -0
  207. package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
  208. package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
  209. package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
  210. package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
  211. package/package.json +1 -1
  212. package/dist/.buildstamp +0 -1
  213. package/dist/acp/bindings-store.js +0 -209
  214. package/dist/acp/control-plane/runtime-cache.js +0 -54
  215. package/dist/acp/control-plane/runtime-options.js +0 -215
  216. package/dist/acp/control-plane/session-actor-queue.js +0 -36
  217. package/dist/acp/index.js +0 -2
  218. package/dist/acp/runtime/errors.js +0 -47
  219. package/dist/acp/runtime/registry.js +0 -86
  220. package/dist/acp/secret-file.js +0 -22
  221. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
  222. package/dist/agents/bash-process-registry.test-helpers.js +0 -29
  223. package/dist/agents/bash-tools.exec-approval-request.js +0 -20
  224. package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
  225. package/dist/agents/bash-tools.exec-host-node.js +0 -235
  226. package/dist/agents/checkpoint-manager.js +0 -290
  227. package/dist/agents/claude-cli-runner.js +0 -3
  228. package/dist/agents/error-classifier.js +0 -251
  229. package/dist/agents/live-model-filter.js +0 -84
  230. package/dist/agents/nvidia-models.js +0 -228
  231. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
  232. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
  233. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
  234. package/dist/agents/provider/config-loader.js +0 -76
  235. package/dist/agents/provider/index.js +0 -15
  236. package/dist/agents/provider/models-dev.js +0 -129
  237. package/dist/agents/provider/session-binding.js +0 -376
  238. package/dist/agents/queued-file-writer.js +0 -22
  239. package/dist/agents/skills/bundled-context.js +0 -23
  240. package/dist/agents/skills/security.js +0 -211
  241. package/dist/agents/skills/tools-dir.js +0 -9
  242. package/dist/agents/skills-install-download.js +0 -290
  243. package/dist/agents/skills-install-output.js +0 -30
  244. package/dist/agents/skills-install.download-test-utils.js +0 -36
  245. package/dist/agents/skills.test-helpers.js +0 -13
  246. package/dist/agents/subagent-announce-reliability.js +0 -160
  247. package/dist/agents/subagent-registry.mocks.shared.js +0 -12
  248. package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
  249. package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
  250. package/dist/agents/test-helpers/fast-core-tools.js +0 -8
  251. package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
  252. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
  253. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
  254. package/dist/agents/tool-display-common.js +0 -915
  255. package/dist/agents/tool-policy-shared.js +0 -108
  256. package/dist/agents/tool-policy.conformance.js +0 -14
  257. package/dist/agents/tool-result-truncation.js +0 -299
  258. package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
  259. package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
  260. package/dist/agents/tools/discord-actions-presence.js +0 -78
  261. package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
  262. package/dist/discord/discord-improvements.js +0 -167
  263. package/dist/discord/index.js +0 -2
  264. package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
  265. package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
  266. package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
  267. package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
  268. package/dist/whatsapp/normalize.js +0 -66
  269. package/dist/whatsapp/resolve-outbound-target.js +0 -42
  270. /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
  271. /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
@@ -0,0 +1,160 @@
1
+ import crypto from "node:crypto";
2
+ import { resolveAgentModelFallbacksOverride } from "../../agents/agent-scope.js";
3
+ import { runWithModelFallback } from "../../agents/model-fallback.js";
4
+ import { isCliProvider } from "../../agents/model-selection.js";
5
+ import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
6
+ import { resolveSandboxConfigForAgent, resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
7
+ import { resolveAgentIdFromSessionKey, updateSessionStoreEntry, } from "../../config/sessions.js";
8
+ import { logVerbose } from "../../globals.js";
9
+ import { registerAgentRunContext } from "../../infra/agent-events.js";
10
+ import { buildThreadingToolContext, resolveEnforceFinalTag } from "./agent-runner-utils.js";
11
+ import { resolveMemoryFlushContextWindowTokens, resolveMemoryFlushSettings, shouldRunMemoryFlush, } from "./memory-flush.js";
12
+ import { incrementCompactionCount } from "./session-updates.js";
13
+ export async function runMemoryFlushIfNeeded(params) {
14
+ const memoryFlushSettings = resolveMemoryFlushSettings(params.cfg);
15
+ if (!memoryFlushSettings)
16
+ return params.sessionEntry;
17
+ const memoryFlushWritable = (() => {
18
+ if (!params.sessionKey)
19
+ return true;
20
+ const runtime = resolveSandboxRuntimeStatus({
21
+ cfg: params.cfg,
22
+ sessionKey: params.sessionKey,
23
+ });
24
+ if (!runtime.sandboxed)
25
+ return true;
26
+ const sandboxCfg = resolveSandboxConfigForAgent(params.cfg, runtime.agentId);
27
+ return sandboxCfg.workspaceAccess === "rw";
28
+ })();
29
+ const shouldFlushMemory = memoryFlushSettings &&
30
+ memoryFlushWritable &&
31
+ !params.isHeartbeat &&
32
+ !isCliProvider(params.followupRun.run.provider, params.cfg) &&
33
+ shouldRunMemoryFlush({
34
+ entry: params.sessionEntry ??
35
+ (params.sessionKey ? params.sessionStore?.[params.sessionKey] : undefined),
36
+ contextWindowTokens: resolveMemoryFlushContextWindowTokens({
37
+ modelId: params.followupRun.run.model ?? params.defaultModel,
38
+ agentCfgContextTokens: params.agentCfgContextTokens,
39
+ }),
40
+ reserveTokensFloor: memoryFlushSettings.reserveTokensFloor,
41
+ softThresholdTokens: memoryFlushSettings.softThresholdTokens,
42
+ });
43
+ if (!shouldFlushMemory)
44
+ return params.sessionEntry;
45
+ let activeSessionEntry = params.sessionEntry;
46
+ const activeSessionStore = params.sessionStore;
47
+ const flushRunId = crypto.randomUUID();
48
+ if (params.sessionKey) {
49
+ registerAgentRunContext(flushRunId, {
50
+ sessionKey: params.sessionKey,
51
+ verboseLevel: params.resolvedVerboseLevel,
52
+ });
53
+ }
54
+ let memoryCompactionCompleted = false;
55
+ const flushSystemPrompt = [
56
+ params.followupRun.run.extraSystemPrompt,
57
+ memoryFlushSettings.systemPrompt,
58
+ ]
59
+ .filter(Boolean)
60
+ .join("\n\n");
61
+ try {
62
+ await runWithModelFallback({
63
+ cfg: params.followupRun.run.config,
64
+ provider: params.followupRun.run.provider,
65
+ model: params.followupRun.run.model,
66
+ agentDir: params.followupRun.run.agentDir,
67
+ fallbacksOverride: resolveAgentModelFallbacksOverride(params.followupRun.run.config, resolveAgentIdFromSessionKey(params.followupRun.run.sessionKey)),
68
+ run: (provider, model) => {
69
+ const authProfileId = provider === params.followupRun.run.provider
70
+ ? params.followupRun.run.authProfileId
71
+ : undefined;
72
+ return runEmbeddedPiAgent({
73
+ sessionId: params.followupRun.run.sessionId,
74
+ sessionKey: params.sessionKey,
75
+ messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
76
+ agentAccountId: params.sessionCtx.AccountId,
77
+ messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
78
+ messageThreadId: params.sessionCtx.MessageThreadId ?? undefined,
79
+ // Provider threading context for tool auto-injection
80
+ ...buildThreadingToolContext({
81
+ sessionCtx: params.sessionCtx,
82
+ config: params.followupRun.run.config,
83
+ hasRepliedRef: params.opts?.hasRepliedRef,
84
+ }),
85
+ senderId: params.sessionCtx.SenderId?.trim() || undefined,
86
+ senderName: params.sessionCtx.SenderName?.trim() || undefined,
87
+ senderUsername: params.sessionCtx.SenderUsername?.trim() || undefined,
88
+ senderE164: params.sessionCtx.SenderE164?.trim() || undefined,
89
+ sessionFile: params.followupRun.run.sessionFile,
90
+ workspaceDir: params.followupRun.run.workspaceDir,
91
+ agentDir: params.followupRun.run.agentDir,
92
+ config: params.followupRun.run.config,
93
+ skillsSnapshot: params.followupRun.run.skillsSnapshot,
94
+ prompt: memoryFlushSettings.prompt,
95
+ extraSystemPrompt: flushSystemPrompt,
96
+ ownerNumbers: params.followupRun.run.ownerNumbers,
97
+ enforceFinalTag: resolveEnforceFinalTag(params.followupRun.run, provider),
98
+ provider,
99
+ model,
100
+ authProfileId,
101
+ authProfileIdSource: authProfileId
102
+ ? params.followupRun.run.authProfileIdSource
103
+ : undefined,
104
+ thinkLevel: params.followupRun.run.thinkLevel,
105
+ verboseLevel: params.followupRun.run.verboseLevel,
106
+ reasoningLevel: params.followupRun.run.reasoningLevel,
107
+ execOverrides: params.followupRun.run.execOverrides,
108
+ bashElevated: params.followupRun.run.bashElevated,
109
+ timeoutMs: params.followupRun.run.timeoutMs,
110
+ runId: flushRunId,
111
+ onAgentEvent: (evt) => {
112
+ if (evt.stream === "compaction") {
113
+ const phase = typeof evt.data.phase === "string" ? evt.data.phase : "";
114
+ const willRetry = Boolean(evt.data.willRetry);
115
+ if (phase === "end" && !willRetry) {
116
+ memoryCompactionCompleted = true;
117
+ }
118
+ }
119
+ },
120
+ });
121
+ },
122
+ });
123
+ let memoryFlushCompactionCount = activeSessionEntry?.compactionCount ??
124
+ (params.sessionKey ? activeSessionStore?.[params.sessionKey]?.compactionCount : 0) ??
125
+ 0;
126
+ if (memoryCompactionCompleted) {
127
+ const nextCount = await incrementCompactionCount({
128
+ sessionEntry: activeSessionEntry,
129
+ sessionStore: activeSessionStore,
130
+ sessionKey: params.sessionKey,
131
+ storePath: params.storePath,
132
+ });
133
+ if (typeof nextCount === "number") {
134
+ memoryFlushCompactionCount = nextCount;
135
+ }
136
+ }
137
+ if (params.storePath && params.sessionKey) {
138
+ try {
139
+ const updatedEntry = await updateSessionStoreEntry({
140
+ storePath: params.storePath,
141
+ sessionKey: params.sessionKey,
142
+ update: async () => ({
143
+ memoryFlushAt: Date.now(),
144
+ memoryFlushCompactionCount,
145
+ }),
146
+ });
147
+ if (updatedEntry) {
148
+ activeSessionEntry = updatedEntry;
149
+ }
150
+ }
151
+ catch (err) {
152
+ logVerbose(`failed to persist memory flush metadata: ${String(err)}`);
153
+ }
154
+ }
155
+ }
156
+ catch (err) {
157
+ logVerbose(`memory flush run failed: ${String(err)}`);
158
+ }
159
+ return activeSessionEntry;
160
+ }
@@ -0,0 +1,85 @@
1
+ import { logVerbose } from "../../globals.js";
2
+ import { stripHeartbeatToken } from "../heartbeat.js";
3
+ import { SILENT_REPLY_TOKEN } from "../tokens.js";
4
+ import { formatBunFetchSocketError, isBunFetchSocketError } from "./agent-runner-utils.js";
5
+ import { createBlockReplyPayloadKey } from "./block-reply-pipeline.js";
6
+ import { parseReplyDirectives } from "./reply-directives.js";
7
+ import { applyReplyThreading, filterMessagingToolDuplicates, isRenderablePayload, shouldSuppressMessagingToolReplies, } from "./reply-payloads.js";
8
+ export function buildReplyPayloads(params) {
9
+ let didLogHeartbeatStrip = params.didLogHeartbeatStrip;
10
+ const sanitizedPayloads = params.isHeartbeat
11
+ ? params.payloads
12
+ : params.payloads.flatMap((payload) => {
13
+ let text = payload.text;
14
+ if (payload.isError && text && isBunFetchSocketError(text)) {
15
+ text = formatBunFetchSocketError(text);
16
+ }
17
+ if (!text || !text.includes("HEARTBEAT_OK")) {
18
+ return [{ ...payload, text }];
19
+ }
20
+ const stripped = stripHeartbeatToken(text, { mode: "message" });
21
+ if (stripped.didStrip && !didLogHeartbeatStrip) {
22
+ didLogHeartbeatStrip = true;
23
+ logVerbose("Stripped stray HEARTBEAT_OK token from reply");
24
+ }
25
+ const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
26
+ if (stripped.shouldSkip && !hasMedia)
27
+ return [];
28
+ return [{ ...payload, text: stripped.text }];
29
+ });
30
+ const replyTaggedPayloads = applyReplyThreading({
31
+ payloads: sanitizedPayloads,
32
+ replyToMode: params.replyToMode,
33
+ replyToChannel: params.replyToChannel,
34
+ currentMessageId: params.currentMessageId,
35
+ })
36
+ .map((payload) => {
37
+ const parsed = parseReplyDirectives(payload.text ?? "", {
38
+ currentMessageId: params.currentMessageId,
39
+ silentToken: SILENT_REPLY_TOKEN,
40
+ });
41
+ const mediaUrls = payload.mediaUrls ?? parsed.mediaUrls;
42
+ const mediaUrl = payload.mediaUrl ?? parsed.mediaUrl ?? mediaUrls?.[0];
43
+ return {
44
+ ...payload,
45
+ text: parsed.text ? parsed.text : undefined,
46
+ mediaUrls,
47
+ mediaUrl,
48
+ replyToId: payload.replyToId ?? parsed.replyToId,
49
+ replyToTag: payload.replyToTag || parsed.replyToTag,
50
+ replyToCurrent: payload.replyToCurrent || parsed.replyToCurrent,
51
+ audioAsVoice: Boolean(payload.audioAsVoice || parsed.audioAsVoice),
52
+ };
53
+ })
54
+ .filter(isRenderablePayload);
55
+ // Drop final payloads only when block streaming succeeded end-to-end.
56
+ // If streaming aborted (e.g., timeout), fall back to final payloads.
57
+ const shouldDropFinalPayloads = params.blockStreamingEnabled &&
58
+ Boolean(params.blockReplyPipeline?.didStream()) &&
59
+ !params.blockReplyPipeline?.isAborted();
60
+ const messagingToolSentTexts = params.messagingToolSentTexts ?? [];
61
+ const messagingToolSentTargets = params.messagingToolSentTargets ?? [];
62
+ const suppressMessagingToolReplies = shouldSuppressMessagingToolReplies({
63
+ messageProvider: params.messageProvider,
64
+ messagingToolSentTargets,
65
+ originatingTo: params.originatingTo,
66
+ accountId: params.accountId,
67
+ });
68
+ const dedupedPayloads = filterMessagingToolDuplicates({
69
+ payloads: replyTaggedPayloads,
70
+ sentTexts: messagingToolSentTexts,
71
+ });
72
+ // Filter out payloads already sent via pipeline or directly during tool flush.
73
+ const filteredPayloads = shouldDropFinalPayloads
74
+ ? []
75
+ : params.blockStreamingEnabled
76
+ ? dedupedPayloads.filter((payload) => !params.blockReplyPipeline?.hasSentPayload(payload))
77
+ : params.directlySentBlockKeys?.size
78
+ ? dedupedPayloads.filter((payload) => !params.directlySentBlockKeys.has(createBlockReplyPayloadKey(payload)))
79
+ : dedupedPayloads;
80
+ const replyPayloads = suppressMessagingToolReplies ? [] : filteredPayloads;
81
+ return {
82
+ replyPayloads,
83
+ didLogHeartbeatStrip,
84
+ };
85
+ }
@@ -0,0 +1,101 @@
1
+ import { getChannelDock } from "../../channels/dock.js";
2
+ import { normalizeAnyChannelId, normalizeChannelId } from "../../channels/registry.js";
3
+ import { isReasoningTagProvider } from "../../utils/provider-utils.js";
4
+ import { estimateUsageCost, formatTokenCount, formatUsd } from "../../utils/usage-format.js";
5
+ const BUN_FETCH_SOCKET_ERROR_RE = /socket connection was closed unexpectedly/i;
6
+ /**
7
+ * Build provider-specific threading context for tool auto-injection.
8
+ */
9
+ export function buildThreadingToolContext(params) {
10
+ const { sessionCtx, config, hasRepliedRef } = params;
11
+ if (!config)
12
+ return {};
13
+ const rawProvider = sessionCtx.Provider?.trim().toLowerCase();
14
+ if (!rawProvider)
15
+ return {};
16
+ const provider = normalizeChannelId(rawProvider) ?? normalizeAnyChannelId(rawProvider);
17
+ // Fallback for unrecognized/plugin channels (e.g., BlueBubbles before plugin registry init)
18
+ const dock = provider ? getChannelDock(provider) : undefined;
19
+ if (!dock?.threading?.buildToolContext) {
20
+ return {
21
+ currentChannelId: sessionCtx.To?.trim() || undefined,
22
+ currentChannelProvider: provider ?? rawProvider,
23
+ hasRepliedRef,
24
+ };
25
+ }
26
+ const context = dock.threading.buildToolContext({
27
+ cfg: config,
28
+ accountId: sessionCtx.AccountId,
29
+ context: {
30
+ Channel: sessionCtx.Provider,
31
+ From: sessionCtx.From,
32
+ To: sessionCtx.To,
33
+ ChatType: sessionCtx.ChatType,
34
+ ReplyToId: sessionCtx.ReplyToId,
35
+ ThreadLabel: sessionCtx.ThreadLabel,
36
+ MessageThreadId: sessionCtx.MessageThreadId,
37
+ },
38
+ hasRepliedRef,
39
+ }) ?? {};
40
+ return {
41
+ ...context,
42
+ currentChannelProvider: provider, // guaranteed non-null since dock exists
43
+ };
44
+ }
45
+ export const isBunFetchSocketError = (message) => Boolean(message && BUN_FETCH_SOCKET_ERROR_RE.test(message));
46
+ export const formatBunFetchSocketError = (message) => {
47
+ const trimmed = message.trim();
48
+ return [
49
+ "⚠️ LLM connection failed. This could be due to server issues, network problems, or context length exceeded (e.g., with local LLMs like LM Studio). Original error:",
50
+ "```",
51
+ trimmed || "Unknown error",
52
+ "```",
53
+ ].join("\n");
54
+ };
55
+ export const formatResponseUsageLine = (params) => {
56
+ const usage = params.usage;
57
+ if (!usage)
58
+ return null;
59
+ const input = usage.input;
60
+ const output = usage.output;
61
+ if (typeof input !== "number" && typeof output !== "number")
62
+ return null;
63
+ const inputLabel = typeof input === "number" ? formatTokenCount(input) : "?";
64
+ const outputLabel = typeof output === "number" ? formatTokenCount(output) : "?";
65
+ const cost = params.showCost && typeof input === "number" && typeof output === "number"
66
+ ? estimateUsageCost({
67
+ usage: {
68
+ input,
69
+ output,
70
+ cacheRead: usage.cacheRead,
71
+ cacheWrite: usage.cacheWrite,
72
+ },
73
+ cost: params.costConfig,
74
+ })
75
+ : undefined;
76
+ const costLabel = params.showCost ? formatUsd(cost) : undefined;
77
+ const suffix = costLabel ? ` · est ${costLabel}` : "";
78
+ return `Usage: ${inputLabel} in / ${outputLabel} out${suffix}`;
79
+ };
80
+ export const appendUsageLine = (payloads, line) => {
81
+ let index = -1;
82
+ for (let i = payloads.length - 1; i >= 0; i -= 1) {
83
+ if (payloads[i]?.text) {
84
+ index = i;
85
+ break;
86
+ }
87
+ }
88
+ if (index === -1)
89
+ return [...payloads, { text: line }];
90
+ const existing = payloads[index];
91
+ const existingText = existing.text ?? "";
92
+ const separator = existingText.endsWith("\n") ? "" : "\n";
93
+ const next = {
94
+ ...existing,
95
+ text: `${existingText}${separator}${line}`,
96
+ };
97
+ const updated = payloads.slice();
98
+ updated[index] = next;
99
+ return updated;
100
+ };
101
+ export const resolveEnforceFinalTag = (run, provider) => Boolean(run.enforceFinalTag || isReasoningTagProvider(provider));
@@ -0,0 +1,338 @@
1
+ import { resolveSessionAgentId } from "../../agents/agent-scope.js";
2
+ import { getFinishedSession, getSession, markExited } from "../../agents/bash-process-registry.js";
3
+ import { createExecTool } from "../../agents/bash-tools.js";
4
+ import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
5
+ import { killProcessTree } from "../../agents/shell-utils.js";
6
+ import { isCommandFlagEnabled } from "../../config/commands.js";
7
+ import { logVerbose } from "../../globals.js";
8
+ import { clampInt } from "../../utils.js";
9
+ import { buildDisabledCommandReply } from "./command-gates.js";
10
+ import { formatElevatedUnavailableMessage } from "./elevated-unavailable.js";
11
+ import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
12
+ const CHAT_BASH_SCOPE_KEY = "chat:bash";
13
+ const DEFAULT_FOREGROUND_MS = 2000;
14
+ const MAX_FOREGROUND_MS = 30_000;
15
+ let activeJob = null;
16
+ function resolveForegroundMs(cfg) {
17
+ const raw = cfg.commands?.bashForegroundMs;
18
+ if (typeof raw !== "number" || Number.isNaN(raw)) {
19
+ return DEFAULT_FOREGROUND_MS;
20
+ }
21
+ return clampInt(raw, 0, MAX_FOREGROUND_MS);
22
+ }
23
+ function formatSessionSnippet(sessionId) {
24
+ const trimmed = sessionId.trim();
25
+ if (trimmed.length <= 12) {
26
+ return trimmed;
27
+ }
28
+ return `${trimmed.slice(0, 8)}…`;
29
+ }
30
+ function formatOutputBlock(text) {
31
+ const trimmed = text.trim();
32
+ if (!trimmed) {
33
+ return "(no output)";
34
+ }
35
+ return `\`\`\`txt\n${trimmed}\n\`\`\``;
36
+ }
37
+ function parseBashRequest(raw) {
38
+ const trimmed = raw.trimStart();
39
+ let restSource = "";
40
+ if (trimmed.toLowerCase().startsWith("/bash")) {
41
+ const match = trimmed.match(/^\/bash(?:\s*:\s*|\s+|$)([\s\S]*)$/i);
42
+ if (!match) {
43
+ return null;
44
+ }
45
+ restSource = match[1] ?? "";
46
+ }
47
+ else if (trimmed.startsWith("!")) {
48
+ restSource = trimmed.slice(1);
49
+ if (restSource.trimStart().startsWith(":")) {
50
+ restSource = restSource.trimStart().slice(1);
51
+ }
52
+ }
53
+ else {
54
+ return null;
55
+ }
56
+ const rest = restSource.trimStart();
57
+ if (!rest) {
58
+ return { action: "help" };
59
+ }
60
+ const tokenMatch = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/);
61
+ const token = tokenMatch?.[1]?.trim() ?? "";
62
+ const remainder = tokenMatch?.[2]?.trim() ?? "";
63
+ const lowered = token.toLowerCase();
64
+ if (lowered === "poll") {
65
+ return { action: "poll", sessionId: remainder || undefined };
66
+ }
67
+ if (lowered === "stop") {
68
+ return { action: "stop", sessionId: remainder || undefined };
69
+ }
70
+ if (lowered === "help") {
71
+ return { action: "help" };
72
+ }
73
+ return { action: "run", command: rest };
74
+ }
75
+ function resolveRawCommandBody(params) {
76
+ const source = params.ctx.CommandBody ?? params.ctx.RawBody ?? params.ctx.Body ?? "";
77
+ const stripped = stripStructuralPrefixes(source);
78
+ return params.isGroup
79
+ ? stripMentions(stripped, params.ctx, params.cfg, params.agentId)
80
+ : stripped;
81
+ }
82
+ function getScopedSession(sessionId) {
83
+ const running = getSession(sessionId);
84
+ if (running && running.scopeKey === CHAT_BASH_SCOPE_KEY) {
85
+ return { running };
86
+ }
87
+ const finished = getFinishedSession(sessionId);
88
+ if (finished && finished.scopeKey === CHAT_BASH_SCOPE_KEY) {
89
+ return { finished };
90
+ }
91
+ return {};
92
+ }
93
+ function ensureActiveJobState() {
94
+ if (!activeJob) {
95
+ return null;
96
+ }
97
+ if (activeJob.state === "starting") {
98
+ return activeJob;
99
+ }
100
+ const { running, finished } = getScopedSession(activeJob.sessionId);
101
+ if (running) {
102
+ return activeJob;
103
+ }
104
+ if (finished) {
105
+ activeJob = null;
106
+ return null;
107
+ }
108
+ activeJob = null;
109
+ return null;
110
+ }
111
+ function attachActiveWatcher(sessionId) {
112
+ if (!activeJob || activeJob.state !== "running") {
113
+ return;
114
+ }
115
+ if (activeJob.sessionId !== sessionId) {
116
+ return;
117
+ }
118
+ if (activeJob.watcherAttached) {
119
+ return;
120
+ }
121
+ const { running } = getScopedSession(sessionId);
122
+ const child = running?.child;
123
+ if (!child) {
124
+ return;
125
+ }
126
+ activeJob.watcherAttached = true;
127
+ child.once("close", () => {
128
+ if (activeJob?.state === "running" && activeJob.sessionId === sessionId) {
129
+ activeJob = null;
130
+ }
131
+ });
132
+ }
133
+ function buildUsageReply() {
134
+ return {
135
+ text: [
136
+ "⚙️ Usage:",
137
+ "- ! <command>",
138
+ "- !poll | ! poll",
139
+ "- !stop | ! stop",
140
+ "- /bash ... (alias; same subcommands as !)",
141
+ ].join("\n"),
142
+ };
143
+ }
144
+ export async function handleBashChatCommand(params) {
145
+ if (!isCommandFlagEnabled(params.cfg, "bash")) {
146
+ return buildDisabledCommandReply({
147
+ label: "bash",
148
+ configKey: "bash",
149
+ docsUrl: "https://docs.molt.bot/tools/slash-commands#config",
150
+ });
151
+ }
152
+ const agentId = params.agentId ??
153
+ resolveSessionAgentId({
154
+ sessionKey: params.sessionKey,
155
+ config: params.cfg,
156
+ });
157
+ if (!params.elevated.enabled || !params.elevated.allowed) {
158
+ const runtimeSandboxed = resolveSandboxRuntimeStatus({
159
+ cfg: params.cfg,
160
+ sessionKey: params.ctx.SessionKey,
161
+ }).sandboxed;
162
+ return {
163
+ text: formatElevatedUnavailableMessage({
164
+ runtimeSandboxed,
165
+ failures: params.elevated.failures,
166
+ sessionKey: params.ctx.SessionKey,
167
+ }),
168
+ };
169
+ }
170
+ const rawBody = resolveRawCommandBody({
171
+ ctx: params.ctx,
172
+ cfg: params.cfg,
173
+ agentId,
174
+ isGroup: params.isGroup,
175
+ }).trim();
176
+ const request = parseBashRequest(rawBody);
177
+ if (!request) {
178
+ return { text: "⚠️ Unrecognized bash request." };
179
+ }
180
+ const liveJob = ensureActiveJobState();
181
+ if (request.action === "help") {
182
+ return buildUsageReply();
183
+ }
184
+ if (request.action === "poll") {
185
+ const sessionId = request.sessionId?.trim() || (liveJob?.state === "running" ? liveJob.sessionId : "");
186
+ if (!sessionId) {
187
+ return { text: "⚙️ No active bash job." };
188
+ }
189
+ const { running, finished } = getScopedSession(sessionId);
190
+ if (running) {
191
+ attachActiveWatcher(sessionId);
192
+ const runtimeSec = Math.max(0, Math.floor((Date.now() - running.startedAt) / 1000));
193
+ const tail = running.tail || "(no output yet)";
194
+ return {
195
+ text: [
196
+ `⚙️ bash still running (session ${formatSessionSnippet(sessionId)}, ${runtimeSec}s).`,
197
+ formatOutputBlock(tail),
198
+ "Hint: !stop (or /bash stop)",
199
+ ].join("\n"),
200
+ };
201
+ }
202
+ if (finished) {
203
+ if (activeJob?.state === "running" && activeJob.sessionId === sessionId) {
204
+ activeJob = null;
205
+ }
206
+ const exitLabel = finished.exitSignal
207
+ ? `signal ${String(finished.exitSignal)}`
208
+ : `code ${String(finished.exitCode ?? 0)}`;
209
+ const prefix = finished.status === "completed" ? "⚙️" : "⚠️";
210
+ return {
211
+ text: [
212
+ `${prefix} bash finished (session ${formatSessionSnippet(sessionId)}).`,
213
+ `Exit: ${exitLabel}`,
214
+ formatOutputBlock(finished.aggregated || finished.tail),
215
+ ].join("\n"),
216
+ };
217
+ }
218
+ if (activeJob?.state === "running" && activeJob.sessionId === sessionId) {
219
+ activeJob = null;
220
+ }
221
+ return {
222
+ text: `⚙️ No bash session found for ${formatSessionSnippet(sessionId)}.`,
223
+ };
224
+ }
225
+ if (request.action === "stop") {
226
+ const sessionId = request.sessionId?.trim() || (liveJob?.state === "running" ? liveJob.sessionId : "");
227
+ if (!sessionId) {
228
+ return { text: "⚙️ No active bash job." };
229
+ }
230
+ const { running } = getScopedSession(sessionId);
231
+ if (!running) {
232
+ if (activeJob?.state === "running" && activeJob.sessionId === sessionId) {
233
+ activeJob = null;
234
+ }
235
+ return {
236
+ text: `⚙️ No running bash job found for ${formatSessionSnippet(sessionId)}.`,
237
+ };
238
+ }
239
+ if (!running.backgrounded) {
240
+ return {
241
+ text: `⚠️ Session ${formatSessionSnippet(sessionId)} is not backgrounded.`,
242
+ };
243
+ }
244
+ const pid = running.pid ?? running.child?.pid;
245
+ if (pid) {
246
+ killProcessTree(pid);
247
+ }
248
+ markExited(running, null, "SIGKILL", "failed");
249
+ if (activeJob?.state === "running" && activeJob.sessionId === sessionId) {
250
+ activeJob = null;
251
+ }
252
+ return {
253
+ text: `⚙️ bash stopped (session ${formatSessionSnippet(sessionId)}).`,
254
+ };
255
+ }
256
+ // request.action === "run"
257
+ if (liveJob) {
258
+ const label = liveJob.state === "running" ? formatSessionSnippet(liveJob.sessionId) : "starting";
259
+ return {
260
+ text: `⚠️ A bash job is already running (${label}). Use !poll / !stop (or /bash poll / /bash stop).`,
261
+ };
262
+ }
263
+ const commandText = request.command.trim();
264
+ if (!commandText) {
265
+ return buildUsageReply();
266
+ }
267
+ activeJob = {
268
+ state: "starting",
269
+ startedAt: Date.now(),
270
+ command: commandText,
271
+ };
272
+ try {
273
+ const foregroundMs = resolveForegroundMs(params.cfg);
274
+ const shouldBackgroundImmediately = foregroundMs <= 0;
275
+ const timeoutSec = params.cfg.tools?.exec?.timeoutSec;
276
+ const notifyOnExit = params.cfg.tools?.exec?.notifyOnExit;
277
+ const notifyOnExitEmptySuccess = params.cfg.tools?.exec?.notifyOnExitEmptySuccess;
278
+ const execTool = createExecTool({
279
+ scopeKey: CHAT_BASH_SCOPE_KEY,
280
+ allowBackground: true,
281
+ timeoutSec,
282
+ sessionKey: params.sessionKey,
283
+ notifyOnExit,
284
+ notifyOnExitEmptySuccess,
285
+ elevated: {
286
+ enabled: params.elevated.enabled,
287
+ allowed: params.elevated.allowed,
288
+ defaultLevel: "on",
289
+ },
290
+ });
291
+ const result = await execTool.execute("chat-bash", {
292
+ command: commandText,
293
+ background: shouldBackgroundImmediately,
294
+ yieldMs: shouldBackgroundImmediately ? undefined : foregroundMs,
295
+ timeout: timeoutSec,
296
+ elevated: true,
297
+ });
298
+ if (result.details?.status === "running") {
299
+ const sessionId = result.details.sessionId;
300
+ activeJob = {
301
+ state: "running",
302
+ sessionId,
303
+ startedAt: result.details.startedAt,
304
+ command: commandText,
305
+ watcherAttached: false,
306
+ };
307
+ attachActiveWatcher(sessionId);
308
+ const snippet = formatSessionSnippet(sessionId);
309
+ logVerbose(`Started bash session ${snippet}: ${commandText}`);
310
+ return {
311
+ text: `⚙️ bash started (session ${sessionId}). Still running; use !poll / !stop (or /bash poll / /bash stop).`,
312
+ };
313
+ }
314
+ // Completed in foreground.
315
+ activeJob = null;
316
+ const exitCode = result.details?.status === "completed" ? result.details.exitCode : 0;
317
+ const output = result.details?.status === "completed"
318
+ ? result.details.aggregated
319
+ : result.content.map((chunk) => (chunk.type === "text" ? chunk.text : "")).join("\n");
320
+ return {
321
+ text: [
322
+ `⚙️ bash: ${commandText}`,
323
+ `Exit: ${exitCode}`,
324
+ formatOutputBlock(output || "(no output)"),
325
+ ].join("\n"),
326
+ };
327
+ }
328
+ catch (err) {
329
+ activeJob = null;
330
+ const message = err instanceof Error ? err.message : String(err);
331
+ return {
332
+ text: [`⚠️ bash failed: ${commandText}`, formatOutputBlock(message)].join("\n"),
333
+ };
334
+ }
335
+ }
336
+ export function resetBashChatCommandForTests() {
337
+ activeJob = null;
338
+ }