@poolzin/pool-bot 2026.3.25 → 2026.3.26

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 +10 -276
  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,295 @@
1
+ import { resolveSessionAgentIds } from "../../agents/agent-scope.js";
2
+ import { resolveBootstrapMaxChars } from "../../agents/pi-embedded-helpers.js";
3
+ import { createPoolbotCodingTools } from "../../agents/pi-tools.js";
4
+ import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
5
+ import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
6
+ import { getSkillsSnapshotVersion } from "../../agents/skills/refresh.js";
7
+ import { buildAgentSystemPrompt } from "../../agents/system-prompt.js";
8
+ import { buildSystemPromptReport } from "../../agents/system-prompt-report.js";
9
+ import { buildSystemPromptParams } from "../../agents/system-prompt-params.js";
10
+ import { resolveDefaultModelForAgent } from "../../agents/model-selection.js";
11
+ import { buildToolSummaryMap } from "../../agents/tool-summaries.js";
12
+ import { resolveBootstrapContextForRun } from "../../agents/bootstrap-files.js";
13
+ import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
14
+ import { buildTtsSystemPromptHint } from "../../tts/tts.js";
15
+ function estimateTokensFromChars(chars) {
16
+ return Math.ceil(Math.max(0, chars) / 4);
17
+ }
18
+ function formatInt(n) {
19
+ return new Intl.NumberFormat("en-US").format(n);
20
+ }
21
+ function formatCharsAndTokens(chars) {
22
+ return `${formatInt(chars)} chars (~${formatInt(estimateTokensFromChars(chars))} tok)`;
23
+ }
24
+ function parseContextArgs(commandBodyNormalized) {
25
+ if (commandBodyNormalized === "/context")
26
+ return "";
27
+ if (commandBodyNormalized.startsWith("/context "))
28
+ return commandBodyNormalized.slice(8).trim();
29
+ return "";
30
+ }
31
+ function formatListTop(entries, cap) {
32
+ const sorted = [...entries].sort((a, b) => b.value - a.value);
33
+ const top = sorted.slice(0, cap);
34
+ const omitted = Math.max(0, sorted.length - top.length);
35
+ const lines = top.map((e) => `- ${e.name}: ${formatCharsAndTokens(e.value)}`);
36
+ return { lines, omitted };
37
+ }
38
+ async function resolveContextReport(params) {
39
+ const existing = params.sessionEntry?.systemPromptReport;
40
+ if (existing && existing.source === "run")
41
+ return existing;
42
+ const workspaceDir = params.workspaceDir;
43
+ const bootstrapMaxChars = resolveBootstrapMaxChars(params.cfg);
44
+ const { bootstrapFiles, contextFiles: injectedFiles } = await resolveBootstrapContextForRun({
45
+ workspaceDir,
46
+ config: params.cfg,
47
+ sessionKey: params.sessionKey,
48
+ sessionId: params.sessionEntry?.sessionId,
49
+ });
50
+ const skillsSnapshot = (() => {
51
+ try {
52
+ return buildWorkspaceSkillSnapshot(workspaceDir, {
53
+ config: params.cfg,
54
+ eligibility: { remote: getRemoteSkillEligibility() },
55
+ snapshotVersion: getSkillsSnapshotVersion(workspaceDir),
56
+ });
57
+ }
58
+ catch {
59
+ return { prompt: "", skills: [], resolvedSkills: [] };
60
+ }
61
+ })();
62
+ const skillsPrompt = skillsSnapshot.prompt ?? "";
63
+ const sandboxRuntime = resolveSandboxRuntimeStatus({
64
+ cfg: params.cfg,
65
+ sessionKey: params.ctx.SessionKey ?? params.sessionKey,
66
+ });
67
+ const tools = (() => {
68
+ try {
69
+ return createPoolbotCodingTools({
70
+ config: params.cfg,
71
+ workspaceDir,
72
+ sessionKey: params.sessionKey,
73
+ messageProvider: params.command.channel,
74
+ groupId: params.sessionEntry?.groupId ?? undefined,
75
+ groupChannel: params.sessionEntry?.groupChannel ?? undefined,
76
+ groupSpace: params.sessionEntry?.space ?? undefined,
77
+ spawnedBy: params.sessionEntry?.spawnedBy ?? undefined,
78
+ senderIsOwner: params.command.senderIsOwner,
79
+ modelProvider: params.provider,
80
+ modelId: params.model,
81
+ });
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ })();
87
+ const toolSummaries = buildToolSummaryMap(tools);
88
+ const toolNames = tools.map((t) => t.name);
89
+ const { sessionAgentId } = resolveSessionAgentIds({
90
+ sessionKey: params.sessionKey,
91
+ config: params.cfg,
92
+ });
93
+ const defaultModelRef = resolveDefaultModelForAgent({
94
+ cfg: params.cfg,
95
+ agentId: sessionAgentId,
96
+ });
97
+ const defaultModelLabel = `${defaultModelRef.provider}/${defaultModelRef.model}`;
98
+ const { runtimeInfo, userTimezone, userTime, userTimeFormat } = buildSystemPromptParams({
99
+ config: params.cfg,
100
+ agentId: sessionAgentId,
101
+ workspaceDir,
102
+ cwd: process.cwd(),
103
+ runtime: {
104
+ host: "unknown",
105
+ os: "unknown",
106
+ arch: "unknown",
107
+ node: process.version,
108
+ model: `${params.provider}/${params.model}`,
109
+ defaultModel: defaultModelLabel,
110
+ },
111
+ });
112
+ const sandboxInfo = sandboxRuntime.sandboxed
113
+ ? {
114
+ enabled: true,
115
+ workspaceDir,
116
+ workspaceAccess: "rw",
117
+ elevated: {
118
+ allowed: params.elevated.allowed,
119
+ defaultLevel: (params.resolvedElevatedLevel ?? "off"),
120
+ },
121
+ }
122
+ : { enabled: false };
123
+ const ttsHint = params.cfg ? buildTtsSystemPromptHint(params.cfg) : undefined;
124
+ const systemPrompt = buildAgentSystemPrompt({
125
+ workspaceDir,
126
+ defaultThinkLevel: params.resolvedThinkLevel,
127
+ reasoningLevel: params.resolvedReasoningLevel,
128
+ extraSystemPrompt: undefined,
129
+ ownerNumbers: undefined,
130
+ reasoningTagHint: false,
131
+ toolNames,
132
+ toolSummaries,
133
+ modelAliasLines: [],
134
+ userTimezone,
135
+ userTime,
136
+ userTimeFormat,
137
+ contextFiles: injectedFiles,
138
+ skillsPrompt,
139
+ heartbeatPrompt: undefined,
140
+ ttsHint,
141
+ runtimeInfo,
142
+ sandboxInfo,
143
+ });
144
+ return buildSystemPromptReport({
145
+ source: "estimate",
146
+ generatedAt: Date.now(),
147
+ sessionId: params.sessionEntry?.sessionId,
148
+ sessionKey: params.sessionKey,
149
+ provider: params.provider,
150
+ model: params.model,
151
+ workspaceDir,
152
+ bootstrapMaxChars,
153
+ sandbox: { mode: sandboxRuntime.mode, sandboxed: sandboxRuntime.sandboxed },
154
+ systemPrompt,
155
+ bootstrapFiles,
156
+ injectedFiles,
157
+ skillsPrompt,
158
+ tools,
159
+ });
160
+ }
161
+ export async function buildContextReply(params) {
162
+ const args = parseContextArgs(params.command.commandBodyNormalized);
163
+ const sub = args.split(/\s+/).filter(Boolean)[0]?.toLowerCase() ?? "";
164
+ if (!sub || sub === "help") {
165
+ return {
166
+ text: [
167
+ "🧠 /context",
168
+ "",
169
+ "What counts as context (high-level), plus a breakdown mode.",
170
+ "",
171
+ "Try:",
172
+ "- /context list (short breakdown)",
173
+ "- /context detail (per-file + per-tool + per-skill + system prompt size)",
174
+ "- /context json (same, machine-readable)",
175
+ "",
176
+ "Inline shortcut = a command token inside a normal message (e.g. “hey /status”). It runs immediately (allowlisted senders only) and is stripped before the model sees the remaining text.",
177
+ ].join("\n"),
178
+ };
179
+ }
180
+ const report = await resolveContextReport(params);
181
+ const session = {
182
+ totalTokens: params.sessionEntry?.totalTokens ?? null,
183
+ inputTokens: params.sessionEntry?.inputTokens ?? null,
184
+ outputTokens: params.sessionEntry?.outputTokens ?? null,
185
+ contextTokens: params.contextTokens ?? null,
186
+ };
187
+ if (sub === "json") {
188
+ return { text: JSON.stringify({ report, session }, null, 2) };
189
+ }
190
+ if (sub !== "list" && sub !== "show" && sub !== "detail" && sub !== "deep") {
191
+ return {
192
+ text: [
193
+ "Unknown /context mode.",
194
+ "Use: /context, /context list, /context detail, or /context json",
195
+ ].join("\n"),
196
+ };
197
+ }
198
+ const fileLines = report.injectedWorkspaceFiles.map((f) => {
199
+ const status = f.missing ? "MISSING" : f.truncated ? "TRUNCATED" : "OK";
200
+ const raw = f.missing ? "0" : formatCharsAndTokens(f.rawChars);
201
+ const injected = f.missing ? "0" : formatCharsAndTokens(f.injectedChars);
202
+ return `- ${f.name}: ${status} | raw ${raw} | injected ${injected}`;
203
+ });
204
+ const sandboxLine = `Sandbox: mode=${report.sandbox?.mode ?? "unknown"} sandboxed=${report.sandbox?.sandboxed ?? false}`;
205
+ const toolSchemaLine = `Tool schemas (JSON): ${formatCharsAndTokens(report.tools.schemaChars)} (counts toward context; not shown as text)`;
206
+ const toolListLine = `Tool list (system prompt text): ${formatCharsAndTokens(report.tools.listChars)}`;
207
+ const skillNameSet = new Set(report.skills.entries.map((s) => s.name));
208
+ const skillNames = Array.from(skillNameSet);
209
+ const toolNames = report.tools.entries.map((t) => t.name);
210
+ const formatNameList = (names, cap) => names.length <= cap
211
+ ? names.join(", ")
212
+ : `${names.slice(0, cap).join(", ")}, … (+${names.length - cap} more)`;
213
+ const skillsLine = `Skills list (system prompt text): ${formatCharsAndTokens(report.skills.promptChars)} (${skillNameSet.size} skills)`;
214
+ const skillsNamesLine = skillNameSet.size
215
+ ? `Skills: ${formatNameList(skillNames, 20)}`
216
+ : "Skills: (none)";
217
+ const toolsNamesLine = toolNames.length
218
+ ? `Tools: ${formatNameList(toolNames, 30)}`
219
+ : "Tools: (none)";
220
+ const systemPromptLine = `System prompt (${report.source}): ${formatCharsAndTokens(report.systemPrompt.chars)} (Project Context ${formatCharsAndTokens(report.systemPrompt.projectContextChars)})`;
221
+ const workspaceLabel = report.workspaceDir ?? params.workspaceDir;
222
+ const bootstrapMaxLabel = typeof report.bootstrapMaxChars === "number"
223
+ ? `${formatInt(report.bootstrapMaxChars)} chars`
224
+ : "? chars";
225
+ const totalsLine = session.totalTokens != null
226
+ ? `Session tokens (cached): ${formatInt(session.totalTokens)} total / ctx=${session.contextTokens ?? "?"}`
227
+ : `Session tokens (cached): unknown / ctx=${session.contextTokens ?? "?"}`;
228
+ if (sub === "detail" || sub === "deep") {
229
+ const perSkill = formatListTop(report.skills.entries.map((s) => ({ name: s.name, value: s.blockChars })), 30);
230
+ const perToolSchema = formatListTop(report.tools.entries.map((t) => ({ name: t.name, value: t.schemaChars })), 30);
231
+ const perToolSummary = formatListTop(report.tools.entries.map((t) => ({ name: t.name, value: t.summaryChars })), 30);
232
+ const toolPropsLines = report.tools.entries
233
+ .filter((t) => t.propertiesCount != null)
234
+ .sort((a, b) => (b.propertiesCount ?? 0) - (a.propertiesCount ?? 0))
235
+ .slice(0, 30)
236
+ .map((t) => `- ${t.name}: ${t.propertiesCount} params`);
237
+ return {
238
+ text: [
239
+ "🧠 Context breakdown (detailed)",
240
+ `Workspace: ${workspaceLabel}`,
241
+ `Bootstrap max/file: ${bootstrapMaxLabel}`,
242
+ sandboxLine,
243
+ systemPromptLine,
244
+ "",
245
+ "Injected workspace files:",
246
+ ...fileLines,
247
+ "",
248
+ skillsLine,
249
+ skillsNamesLine,
250
+ ...(perSkill.lines.length ? ["Top skills (prompt entry size):", ...perSkill.lines] : []),
251
+ ...(perSkill.omitted ? [`… (+${perSkill.omitted} more skills)`] : []),
252
+ "",
253
+ toolListLine,
254
+ toolSchemaLine,
255
+ toolsNamesLine,
256
+ "Top tools (schema size):",
257
+ ...perToolSchema.lines,
258
+ ...(perToolSchema.omitted ? [`… (+${perToolSchema.omitted} more tools)`] : []),
259
+ "",
260
+ "Top tools (summary text size):",
261
+ ...perToolSummary.lines,
262
+ ...(perToolSummary.omitted ? [`… (+${perToolSummary.omitted} more tools)`] : []),
263
+ ...(toolPropsLines.length ? ["", "Tools (param count):", ...toolPropsLines] : []),
264
+ "",
265
+ totalsLine,
266
+ "",
267
+ "Inline shortcut: a command token inside normal text (e.g. “hey /status”) that runs immediately (allowlisted senders only) and is stripped before the model sees the remaining message.",
268
+ ]
269
+ .filter(Boolean)
270
+ .join("\n"),
271
+ };
272
+ }
273
+ return {
274
+ text: [
275
+ "🧠 Context breakdown",
276
+ `Workspace: ${workspaceLabel}`,
277
+ `Bootstrap max/file: ${bootstrapMaxLabel}`,
278
+ sandboxLine,
279
+ systemPromptLine,
280
+ "",
281
+ "Injected workspace files:",
282
+ ...fileLines,
283
+ "",
284
+ skillsLine,
285
+ skillsNamesLine,
286
+ toolListLine,
287
+ toolSchemaLine,
288
+ toolsNamesLine,
289
+ "",
290
+ totalsLine,
291
+ "",
292
+ "Inline shortcut: a command token inside normal text (e.g. “hey /status”) that runs immediately (allowlisted senders only) and is stripped before the model sees the remaining message.",
293
+ ].join("\n"),
294
+ };
295
+ }
@@ -0,0 +1,30 @@
1
+ import { resolveCommandAuthorization } from "../command-auth.js";
2
+ import { normalizeCommandBody } from "../commands-registry.js";
3
+ import { stripMentions } from "./mentions.js";
4
+ export function buildCommandContext(params) {
5
+ const { ctx, cfg, agentId, sessionKey, isGroup, triggerBodyNormalized } = params;
6
+ const auth = resolveCommandAuthorization({
7
+ ctx,
8
+ cfg,
9
+ commandAuthorized: params.commandAuthorized,
10
+ });
11
+ const surface = (ctx.Surface ?? ctx.Provider ?? "").trim().toLowerCase();
12
+ const channel = (ctx.Provider ?? surface).trim().toLowerCase();
13
+ const abortKey = sessionKey ?? (auth.from || undefined) ?? (auth.to || undefined);
14
+ const rawBodyNormalized = triggerBodyNormalized;
15
+ const commandBodyNormalized = normalizeCommandBody(isGroup ? stripMentions(rawBodyNormalized, ctx, cfg, agentId) : rawBodyNormalized);
16
+ return {
17
+ surface,
18
+ channel,
19
+ channelId: auth.providerId,
20
+ ownerList: auth.ownerList,
21
+ senderIsOwner: auth.senderIsOwner,
22
+ isAuthorizedSender: auth.isAuthorizedSender,
23
+ senderId: auth.senderId,
24
+ abortKey,
25
+ rawBodyNormalized,
26
+ commandBodyNormalized,
27
+ from: auth.from,
28
+ to: auth.to,
29
+ };
30
+ }
@@ -0,0 +1,151 @@
1
+ import fs from "node:fs/promises";
2
+ import { logVerbose } from "../../globals.js";
3
+ import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
4
+ import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
5
+ import { resolveSendPolicy } from "../../sessions/send-policy.js";
6
+ import { shouldHandleTextCommands } from "../commands-registry.js";
7
+ import { handleAllowlistCommand } from "./commands-allowlist.js";
8
+ import { handleApproveCommand } from "./commands-approve.js";
9
+ import { handleBashCommand } from "./commands-bash.js";
10
+ import { handleCompactCommand } from "./commands-compact.js";
11
+ import { handleConfigCommand, handleDebugCommand } from "./commands-config.js";
12
+ import { handleCommandsListCommand, handleContextCommand, handleExportSessionCommand, handleHelpCommand, handleStatusCommand, handleWhoamiCommand, } from "./commands-info.js";
13
+ import { handleModelsCommand } from "./commands-models.js";
14
+ import { handlePluginCommand } from "./commands-plugin.js";
15
+ import { handleAbortTrigger, handleActivationCommand, handleRestartCommand, handleSessionCommand, handleSendPolicyCommand, handleStopCommand, handleUsageCommand, } from "./commands-session.js";
16
+ import { handleSubagentsCommand } from "./commands-subagents.js";
17
+ import { handleTtsCommands } from "./commands-tts.js";
18
+ import { routeReply } from "./route-reply.js";
19
+ let HANDLERS = null;
20
+ export async function handleCommands(params) {
21
+ if (HANDLERS === null) {
22
+ HANDLERS = [
23
+ // Plugin commands are processed first, before built-in commands
24
+ handlePluginCommand,
25
+ handleBashCommand,
26
+ handleActivationCommand,
27
+ handleSendPolicyCommand,
28
+ handleUsageCommand,
29
+ handleSessionCommand,
30
+ handleRestartCommand,
31
+ handleTtsCommands,
32
+ handleHelpCommand,
33
+ handleCommandsListCommand,
34
+ handleStatusCommand,
35
+ handleAllowlistCommand,
36
+ handleApproveCommand,
37
+ handleContextCommand,
38
+ handleExportSessionCommand,
39
+ handleWhoamiCommand,
40
+ handleSubagentsCommand,
41
+ handleConfigCommand,
42
+ handleDebugCommand,
43
+ handleModelsCommand,
44
+ handleStopCommand,
45
+ handleCompactCommand,
46
+ handleAbortTrigger,
47
+ ];
48
+ }
49
+ const resetMatch = params.command.commandBodyNormalized.match(/^\/(new|reset)(?:\s|$)/);
50
+ const resetRequested = Boolean(resetMatch);
51
+ if (resetRequested && !params.command.isAuthorizedSender) {
52
+ logVerbose(`Ignoring /reset from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
53
+ return { shouldContinue: false };
54
+ }
55
+ // Trigger internal hook for reset/new commands
56
+ if (resetRequested && params.command.isAuthorizedSender) {
57
+ const commandAction = resetMatch?.[1] ?? "new";
58
+ const hookEvent = createInternalHookEvent("command", commandAction, params.sessionKey ?? "", {
59
+ sessionEntry: params.sessionEntry,
60
+ previousSessionEntry: params.previousSessionEntry,
61
+ commandSource: params.command.surface,
62
+ senderId: params.command.senderId,
63
+ cfg: params.cfg, // Pass config for LLM slug generation
64
+ });
65
+ await triggerInternalHook(hookEvent);
66
+ // Send hook messages immediately if present
67
+ if (hookEvent.messages.length > 0) {
68
+ // Use OriginatingChannel/To if available, otherwise fall back to command channel/from
69
+ // oxlint-disable-next-line typescript/no-explicit-any
70
+ const channel = params.ctx.OriginatingChannel || params.command.channel;
71
+ // For replies, use 'from' (the sender) not 'to' (which might be the bot itself)
72
+ const to = params.ctx.OriginatingTo || params.command.from || params.command.to;
73
+ if (channel && to) {
74
+ const hookReply = { text: hookEvent.messages.join("\n\n") };
75
+ await routeReply({
76
+ payload: hookReply,
77
+ channel: channel,
78
+ to: to,
79
+ sessionKey: params.sessionKey,
80
+ accountId: params.ctx.AccountId,
81
+ threadId: params.ctx.MessageThreadId,
82
+ cfg: params.cfg,
83
+ });
84
+ }
85
+ }
86
+ // Fire before_reset plugin hook — extract memories before session history is lost
87
+ const hookRunner = getGlobalHookRunner();
88
+ if (hookRunner?.hasHooks("before_reset")) {
89
+ const prevEntry = params.previousSessionEntry;
90
+ const sessionFile = prevEntry?.sessionFile;
91
+ // Fire-and-forget: read old session messages and run hook
92
+ void (async () => {
93
+ try {
94
+ const messages = [];
95
+ if (sessionFile) {
96
+ const content = await fs.readFile(sessionFile, "utf-8");
97
+ for (const line of content.split("\n")) {
98
+ if (!line.trim()) {
99
+ continue;
100
+ }
101
+ try {
102
+ const entry = JSON.parse(line);
103
+ if (entry.type === "message" && entry.message) {
104
+ messages.push(entry.message);
105
+ }
106
+ }
107
+ catch {
108
+ // skip malformed lines
109
+ }
110
+ }
111
+ }
112
+ else {
113
+ logVerbose("before_reset: no session file available, firing hook with empty messages");
114
+ }
115
+ await hookRunner.runBeforeReset({ sessionFile, messages, reason: commandAction }, {
116
+ agentId: params.sessionKey?.split(":")[0] ?? "main",
117
+ sessionKey: params.sessionKey,
118
+ sessionId: prevEntry?.sessionId,
119
+ workspaceDir: params.workspaceDir,
120
+ });
121
+ }
122
+ catch (err) {
123
+ logVerbose(`before_reset hook failed: ${String(err)}`);
124
+ }
125
+ })();
126
+ }
127
+ }
128
+ const allowTextCommands = shouldHandleTextCommands({
129
+ cfg: params.cfg,
130
+ surface: params.command.surface,
131
+ commandSource: params.ctx.CommandSource,
132
+ });
133
+ for (const handler of HANDLERS) {
134
+ const result = await handler(params, allowTextCommands);
135
+ if (result) {
136
+ return result;
137
+ }
138
+ }
139
+ const sendPolicy = resolveSendPolicy({
140
+ cfg: params.cfg,
141
+ entry: params.sessionEntry,
142
+ sessionKey: params.sessionKey,
143
+ channel: params.sessionEntry?.channel ?? params.command.channel,
144
+ chatType: params.sessionEntry?.chatType,
145
+ });
146
+ if (sendPolicy === "deny") {
147
+ logVerbose(`Send blocked by policy for session ${params.sessionKey ?? "unknown"}`);
148
+ return { shouldContinue: false };
149
+ }
150
+ return { shouldContinue: true };
151
+ }
@@ -0,0 +1,163 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { SessionManager } from "@mariozechner/pi-coding-agent";
5
+ import { resolveDefaultSessionStorePath, resolveSessionFilePath, } from "../../config/sessions/paths.js";
6
+ import { loadSessionStore } from "../../config/sessions/store.js";
7
+ import { resolveCommandsSystemPromptBundle } from "./commands-system-prompt.js";
8
+ // Export HTML templates are bundled with this module
9
+ const EXPORT_HTML_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "export-html");
10
+ function loadTemplate(fileName) {
11
+ return fs.readFileSync(path.join(EXPORT_HTML_DIR, fileName), "utf-8");
12
+ }
13
+ function generateHtml(sessionData) {
14
+ const template = loadTemplate("template.html");
15
+ const templateCss = loadTemplate("template.css");
16
+ const templateJs = loadTemplate("template.js");
17
+ const markedJs = loadTemplate(path.join("vendor", "marked.min.js"));
18
+ const hljsJs = loadTemplate(path.join("vendor", "highlight.min.js"));
19
+ // Use pi-mono dark theme colors (matching their theme/dark.json)
20
+ const themeVars = `
21
+ --cyan: #00d7ff;
22
+ --blue: #5f87ff;
23
+ --green: #b5bd68;
24
+ --red: #cc6666;
25
+ --yellow: #ffff00;
26
+ --gray: #808080;
27
+ --dimGray: #666666;
28
+ --darkGray: #505050;
29
+ --accent: #8abeb7;
30
+ --selectedBg: #3a3a4a;
31
+ --userMsgBg: #343541;
32
+ --toolPendingBg: #282832;
33
+ --toolSuccessBg: #283228;
34
+ --toolErrorBg: #3c2828;
35
+ --customMsgBg: #2d2838;
36
+ --text: #e0e0e0;
37
+ --dim: #666666;
38
+ --muted: #808080;
39
+ --border: #5f87ff;
40
+ --borderAccent: #00d7ff;
41
+ --borderMuted: #505050;
42
+ --success: #b5bd68;
43
+ --error: #cc6666;
44
+ --warning: #ffff00;
45
+ --thinkingText: #808080;
46
+ --userMessageBg: #343541;
47
+ --userMessageText: #e0e0e0;
48
+ --customMessageBg: #2d2838;
49
+ --customMessageText: #e0e0e0;
50
+ --customMessageLabel: #9575cd;
51
+ --toolTitle: #e0e0e0;
52
+ --toolOutput: #808080;
53
+ --mdHeading: #f0c674;
54
+ --mdLink: #81a2be;
55
+ --mdLinkUrl: #666666;
56
+ --mdCode: #8abeb7;
57
+ --mdCodeBlock: #b5bd68;
58
+ `;
59
+ const bodyBg = "#1e1e28";
60
+ const containerBg = "#282832";
61
+ const infoBg = "#343541";
62
+ // Base64 encode session data
63
+ const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString("base64");
64
+ // Build CSS with theme variables
65
+ const css = templateCss
66
+ .replace("/* {{THEME_VARS}} */", themeVars.trim())
67
+ .replace("/* {{BODY_BG_DECL}} */", `--body-bg: ${bodyBg};`)
68
+ .replace("/* {{CONTAINER_BG_DECL}} */", `--container-bg: ${containerBg};`)
69
+ .replace("/* {{INFO_BG_DECL}} */", `--info-bg: ${infoBg};`);
70
+ return template
71
+ .replace("{{CSS}}", css)
72
+ .replace("{{JS}}", templateJs)
73
+ .replace("{{SESSION_DATA}}", sessionDataBase64)
74
+ .replace("{{MARKED_JS}}", markedJs)
75
+ .replace("{{HIGHLIGHT_JS}}", hljsJs);
76
+ }
77
+ function parseExportArgs(commandBodyNormalized) {
78
+ const normalized = commandBodyNormalized.trim();
79
+ if (normalized === "/export-session" || normalized === "/export") {
80
+ return {};
81
+ }
82
+ const args = normalized.replace(/^\/(export-session|export)\s*/, "").trim();
83
+ // First non-flag argument is the output path
84
+ const outputPath = args.split(/\s+/).find((part) => !part.startsWith("-"));
85
+ return { outputPath };
86
+ }
87
+ export async function buildExportSessionReply(params) {
88
+ const args = parseExportArgs(params.command.commandBodyNormalized);
89
+ // 1. Resolve session file
90
+ const sessionEntry = params.sessionEntry;
91
+ if (!sessionEntry?.sessionId) {
92
+ return { text: "❌ No active session found." };
93
+ }
94
+ const storePath = resolveDefaultSessionStorePath(params.agentId);
95
+ const store = loadSessionStore(storePath, { skipCache: true });
96
+ const entry = store[params.sessionKey];
97
+ if (!entry?.sessionId) {
98
+ return { text: `❌ Session not found: ${params.sessionKey}` };
99
+ }
100
+ let sessionFile;
101
+ try {
102
+ sessionFile = resolveSessionFilePath(entry.sessionId, entry, {
103
+ agentId: params.agentId,
104
+ sessionsDir: path.dirname(storePath),
105
+ });
106
+ }
107
+ catch (err) {
108
+ return {
109
+ text: `❌ Failed to resolve session file: ${err instanceof Error ? err.message : String(err)}`,
110
+ };
111
+ }
112
+ if (!fs.existsSync(sessionFile)) {
113
+ return { text: `❌ Session file not found: ${sessionFile}` };
114
+ }
115
+ // 2. Load session entries
116
+ const sessionManager = SessionManager.open(sessionFile);
117
+ const entries = sessionManager.getEntries();
118
+ const header = sessionManager.getHeader();
119
+ const leafId = sessionManager.getLeafId();
120
+ // 3. Build full system prompt
121
+ const { systemPrompt, tools } = await resolveCommandsSystemPromptBundle(params);
122
+ // 4. Prepare session data
123
+ const sessionData = {
124
+ header,
125
+ entries,
126
+ leafId,
127
+ systemPrompt,
128
+ tools: tools.map((t) => ({
129
+ name: t.name,
130
+ description: t.description,
131
+ parameters: t.parameters,
132
+ })),
133
+ };
134
+ // 5. Generate HTML
135
+ const html = generateHtml(sessionData);
136
+ // 6. Determine output path
137
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
138
+ const defaultFileName = `poolbot-session-${entry.sessionId.slice(0, 8)}-${timestamp}.html`;
139
+ const outputPath = args.outputPath
140
+ ? path.resolve(args.outputPath.startsWith("~")
141
+ ? args.outputPath.replace("~", process.env.HOME ?? "")
142
+ : args.outputPath)
143
+ : path.join(params.workspaceDir, defaultFileName);
144
+ // Ensure directory exists
145
+ const outputDir = path.dirname(outputPath);
146
+ if (!fs.existsSync(outputDir)) {
147
+ fs.mkdirSync(outputDir, { recursive: true });
148
+ }
149
+ // 7. Write file
150
+ fs.writeFileSync(outputPath, html, "utf-8");
151
+ const relativePath = path.relative(params.workspaceDir, outputPath);
152
+ const displayPath = relativePath.startsWith("..") ? outputPath : relativePath;
153
+ return {
154
+ text: [
155
+ "✅ Session exported!",
156
+ "",
157
+ `📄 File: ${displayPath}`,
158
+ `📊 Entries: ${entries.length}`,
159
+ `🧠 System prompt: ${systemPrompt.length.toLocaleString()} chars`,
160
+ `🔧 Tools: ${tools.length}`,
161
+ ].join("\n"),
162
+ };
163
+ }