@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
@@ -5,22 +5,26 @@
5
5
  * Creates a new dated memory file with LLM-generated slug
6
6
  */
7
7
  import fs from "node:fs/promises";
8
- import path from "node:path";
9
8
  import os from "node:os";
9
+ import path from "node:path";
10
10
  import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
11
+ import { resolveStateDir } from "../../../config/paths.js";
12
+ import { createSubsystemLogger } from "../../../logging/subsystem.js";
11
13
  import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js";
14
+ import { hasInterSessionUserProvenance } from "../../../sessions/input-provenance.js";
15
+ import { resolveHookConfig } from "../../config.js";
16
+ import { generateSlugViaLLM } from "../../llm-slug-generator.js";
17
+ const log = createSubsystemLogger("hooks/session-memory");
12
18
  /**
13
19
  * Read recent messages from session file for slug generation
14
20
  */
15
- async function getRecentSessionContent(sessionFilePath) {
21
+ async function getRecentSessionContent(sessionFilePath, messageCount = 15) {
16
22
  try {
17
23
  const content = await fs.readFile(sessionFilePath, "utf-8");
18
24
  const lines = content.trim().split("\n");
19
- // Get last 15 lines (recent conversation)
20
- const recentLines = lines.slice(-15);
21
- // Parse JSONL and extract messages
22
- const messages = [];
23
- for (const line of recentLines) {
25
+ // Parse JSONL and extract user/assistant messages first
26
+ const allMessages = [];
27
+ for (const line of lines) {
24
28
  try {
25
29
  const entry = JSON.parse(line);
26
30
  // Session files have entries with type="message" containing a nested message object
@@ -28,12 +32,16 @@ async function getRecentSessionContent(sessionFilePath) {
28
32
  const msg = entry.message;
29
33
  const role = msg.role;
30
34
  if ((role === "user" || role === "assistant") && msg.content) {
35
+ if (role === "user" && hasInterSessionUserProvenance(msg)) {
36
+ continue;
37
+ }
31
38
  // Extract text content
32
39
  const text = Array.isArray(msg.content)
33
- ? msg.content.find((c) => c.type === "text")?.text
40
+ ? // biome-ignore lint/suspicious/noExplicitAny: required
41
+ msg.content.find((c) => c.type === "text")?.text
34
42
  : msg.content;
35
43
  if (text && !text.startsWith("/")) {
36
- messages.push(`${role}: ${text}`);
44
+ allMessages.push(`${role}: ${text}`);
37
45
  }
38
46
  }
39
47
  }
@@ -42,12 +50,92 @@ async function getRecentSessionContent(sessionFilePath) {
42
50
  // Skip invalid JSON lines
43
51
  }
44
52
  }
45
- return messages.join("\n");
53
+ // Then slice to get exactly messageCount messages
54
+ const recentMessages = allMessages.slice(-messageCount);
55
+ return recentMessages.join("\n");
46
56
  }
47
57
  catch {
48
58
  return null;
49
59
  }
50
60
  }
61
+ /**
62
+ * Try the active transcript first; if /new already rotated it,
63
+ * fallback to the latest .jsonl.reset.* sibling.
64
+ */
65
+ async function getRecentSessionContentWithResetFallback(sessionFilePath, messageCount = 15) {
66
+ const primary = await getRecentSessionContent(sessionFilePath, messageCount);
67
+ if (primary) {
68
+ return primary;
69
+ }
70
+ try {
71
+ const dir = path.dirname(sessionFilePath);
72
+ const base = path.basename(sessionFilePath);
73
+ const resetPrefix = `${base}.reset.`;
74
+ const files = await fs.readdir(dir);
75
+ const resetCandidates = files.filter((name) => name.startsWith(resetPrefix)).toSorted();
76
+ if (resetCandidates.length === 0) {
77
+ return primary;
78
+ }
79
+ const latestResetPath = path.join(dir, resetCandidates[resetCandidates.length - 1]);
80
+ const fallback = await getRecentSessionContent(latestResetPath, messageCount);
81
+ if (fallback) {
82
+ log.debug("Loaded session content from reset fallback", {
83
+ sessionFilePath,
84
+ latestResetPath,
85
+ });
86
+ }
87
+ return fallback || primary;
88
+ }
89
+ catch {
90
+ return primary;
91
+ }
92
+ }
93
+ function stripResetSuffix(fileName) {
94
+ const resetIndex = fileName.indexOf(".reset.");
95
+ return resetIndex === -1 ? fileName : fileName.slice(0, resetIndex);
96
+ }
97
+ async function findPreviousSessionFile(params) {
98
+ try {
99
+ const files = await fs.readdir(params.sessionsDir);
100
+ const fileSet = new Set(files);
101
+ const baseFromReset = params.currentSessionFile
102
+ ? stripResetSuffix(path.basename(params.currentSessionFile))
103
+ : undefined;
104
+ if (baseFromReset && fileSet.has(baseFromReset)) {
105
+ return path.join(params.sessionsDir, baseFromReset);
106
+ }
107
+ const trimmedSessionId = params.sessionId?.trim();
108
+ if (trimmedSessionId) {
109
+ const canonicalFile = `${trimmedSessionId}.jsonl`;
110
+ if (fileSet.has(canonicalFile)) {
111
+ return path.join(params.sessionsDir, canonicalFile);
112
+ }
113
+ const topicVariants = files
114
+ .filter((name) => name.startsWith(`${trimmedSessionId}-topic-`) &&
115
+ name.endsWith(".jsonl") &&
116
+ !name.includes(".reset."))
117
+ .toSorted()
118
+ .toReversed();
119
+ if (topicVariants.length > 0) {
120
+ return path.join(params.sessionsDir, topicVariants[0]);
121
+ }
122
+ }
123
+ if (!params.currentSessionFile) {
124
+ return undefined;
125
+ }
126
+ const nonResetJsonl = files
127
+ .filter((name) => name.endsWith(".jsonl") && !name.includes(".reset."))
128
+ .toSorted()
129
+ .toReversed();
130
+ if (nonResetJsonl.length > 0) {
131
+ return path.join(params.sessionsDir, nonResetJsonl[0]);
132
+ }
133
+ }
134
+ catch {
135
+ // Ignore directory read errors.
136
+ }
137
+ return undefined;
138
+ }
51
139
  /**
52
140
  * Save session context to memory when /new command is triggered
53
141
  */
@@ -57,56 +145,90 @@ const saveSessionToMemory = async (event) => {
57
145
  return;
58
146
  }
59
147
  try {
60
- console.log("[session-memory] Hook triggered for /new command");
148
+ log.debug("Hook triggered for /new command");
61
149
  const context = event.context || {};
62
150
  const cfg = context.cfg;
63
151
  const agentId = resolveAgentIdFromSessionKey(event.sessionKey);
64
152
  const workspaceDir = cfg
65
153
  ? resolveAgentWorkspaceDir(cfg, agentId)
66
- : path.join(os.homedir(), "clawd");
154
+ : path.join(resolveStateDir(process.env, os.homedir), "workspace");
67
155
  const memoryDir = path.join(workspaceDir, "memory");
68
156
  await fs.mkdir(memoryDir, { recursive: true });
69
157
  // Get today's date for filename
70
158
  const now = new Date(event.timestamp);
71
159
  const dateStr = now.toISOString().split("T")[0]; // YYYY-MM-DD
72
160
  // Generate descriptive slug from session using LLM
161
+ // Prefer previousSessionEntry (old session before /new) over current (which may be empty)
73
162
  const sessionEntry = (context.previousSessionEntry || context.sessionEntry || {});
74
163
  const currentSessionId = sessionEntry.sessionId;
75
- const currentSessionFile = sessionEntry.sessionFile;
76
- console.log("[session-memory] Current sessionId:", currentSessionId);
77
- console.log("[session-memory] Current sessionFile:", currentSessionFile);
78
- console.log("[session-memory] cfg present:", !!cfg);
164
+ let currentSessionFile = sessionEntry.sessionFile || undefined;
165
+ // If sessionFile is empty or looks like a new/reset file, try to find the previous session file.
166
+ if (!currentSessionFile || currentSessionFile.includes(".reset.")) {
167
+ const sessionsDirs = new Set();
168
+ if (currentSessionFile) {
169
+ sessionsDirs.add(path.dirname(currentSessionFile));
170
+ }
171
+ sessionsDirs.add(path.join(workspaceDir, "sessions"));
172
+ for (const sessionsDir of sessionsDirs) {
173
+ const recoveredSessionFile = await findPreviousSessionFile({
174
+ sessionsDir,
175
+ currentSessionFile,
176
+ sessionId: currentSessionId,
177
+ });
178
+ if (!recoveredSessionFile) {
179
+ continue;
180
+ }
181
+ currentSessionFile = recoveredSessionFile;
182
+ log.debug("Found previous session file", { file: currentSessionFile });
183
+ break;
184
+ }
185
+ }
186
+ log.debug("Session context resolved", {
187
+ sessionId: currentSessionId,
188
+ sessionFile: currentSessionFile,
189
+ hasCfg: Boolean(cfg),
190
+ });
79
191
  const sessionFile = currentSessionFile || undefined;
192
+ // Read message count from hook config (default: 15)
193
+ const hookConfig = resolveHookConfig(cfg, "session-memory");
194
+ const messageCount = typeof hookConfig?.messages === "number" && hookConfig.messages > 0
195
+ ? hookConfig.messages
196
+ : 15;
80
197
  let slug = null;
81
198
  let sessionContent = null;
82
199
  if (sessionFile) {
83
- // Get recent conversation content
84
- sessionContent = await getRecentSessionContent(sessionFile);
85
- console.log("[session-memory] sessionContent length:", sessionContent?.length || 0);
86
- if (sessionContent && cfg) {
87
- console.log("[session-memory] Calling generateSlugViaLLM...");
88
- // Dynamically import the LLM slug generator (avoids module caching issues)
89
- // When compiled, handler is at dist/hooks/bundled/session-memory/handler.js
90
- // Going up ../.. puts us at dist/hooks/, so just add llm-slug-generator.js
91
- const poolbotRoot = path.resolve(path.dirname(import.meta.url.replace("file://", "")), "../..");
92
- const slugGenPath = path.join(poolbotRoot, "llm-slug-generator.js");
93
- const { generateSlugViaLLM } = await import(slugGenPath);
200
+ // Get recent conversation content, with fallback to rotated reset transcript.
201
+ sessionContent = await getRecentSessionContentWithResetFallback(sessionFile, messageCount);
202
+ log.debug("Session content loaded", {
203
+ length: sessionContent?.length ?? 0,
204
+ messageCount,
205
+ });
206
+ // Avoid calling the model provider in unit tests; keep hooks fast and deterministic.
207
+ const isTestEnv = process.env.POOLBOT_TEST_FAST === "1" ||
208
+ process.env.VITEST === "true" ||
209
+ process.env.VITEST === "1" ||
210
+ process.env.NODE_ENV === "test";
211
+ const allowLlmSlug = !isTestEnv && hookConfig?.llmSlug !== false;
212
+ if (sessionContent && cfg && allowLlmSlug) {
213
+ log.debug("Calling generateSlugViaLLM...");
94
214
  // Use LLM to generate a descriptive slug
95
215
  slug = await generateSlugViaLLM({ sessionContent, cfg });
96
- console.log("[session-memory] Generated slug:", slug);
216
+ log.debug("Generated slug", { slug });
97
217
  }
98
218
  }
99
219
  // If no slug, use timestamp
100
220
  if (!slug) {
101
221
  const timeSlug = now.toISOString().split("T")[1].split(".")[0].replace(/:/g, "");
102
222
  slug = timeSlug.slice(0, 4); // HHMM
103
- console.log("[session-memory] Using fallback timestamp slug:", slug);
223
+ log.debug("Using fallback timestamp slug", { slug });
104
224
  }
105
225
  // Create filename with date and slug
106
226
  const filename = `${dateStr}-${slug}.md`;
107
227
  const memoryFilePath = path.join(memoryDir, filename);
108
- console.log("[session-memory] Generated filename:", filename);
109
- console.log("[session-memory] Full path:", memoryFilePath);
228
+ log.debug("Memory file path resolved", {
229
+ filename,
230
+ path: memoryFilePath.replace(os.homedir(), "~"),
231
+ });
110
232
  // Format time as HH:MM:SS UTC
111
233
  const timeStr = now.toISOString().split("T")[1].split(".")[0];
112
234
  // Extract context details
@@ -128,13 +250,22 @@ const saveSessionToMemory = async (event) => {
128
250
  const entry = entryParts.join("\n");
129
251
  // Write to new memory file
130
252
  await fs.writeFile(memoryFilePath, entry, "utf-8");
131
- console.log("[session-memory] Memory file written successfully");
253
+ log.debug("Memory file written successfully");
132
254
  // Log completion (but don't send user-visible confirmation - it's internal housekeeping)
133
255
  const relPath = memoryFilePath.replace(os.homedir(), "~");
134
- console.log(`[session-memory] Session context saved to ${relPath}`);
256
+ log.info(`Session context saved to ${relPath}`);
135
257
  }
136
258
  catch (err) {
137
- console.error("[session-memory] Failed to save session memory:", err instanceof Error ? err.message : String(err));
259
+ if (err instanceof Error) {
260
+ log.error("Failed to save session memory", {
261
+ errorName: err.name,
262
+ errorMessage: err.message,
263
+ stack: err.stack,
264
+ });
265
+ }
266
+ else {
267
+ log.error("Failed to save session memory", { error: String(err) });
268
+ }
138
269
  }
139
270
  };
140
271
  export default saveSessionToMemory;
@@ -0,0 +1,23 @@
1
+ import { isTruthyEnvValue } from "../infra/env.js";
2
+ import { startGmailWatcher } from "./gmail-watcher.js";
3
+ export async function startGmailWatcherWithLogs(params) {
4
+ if (isTruthyEnvValue(process.env.CLAWDBOT_SKIP_GMAIL_WATCHER)) {
5
+ params.onSkipped?.();
6
+ return;
7
+ }
8
+ try {
9
+ const gmailResult = await startGmailWatcher(params.cfg);
10
+ if (gmailResult.started) {
11
+ params.log.info("gmail watcher started");
12
+ return;
13
+ }
14
+ if (gmailResult.reason &&
15
+ gmailResult.reason !== "hooks not enabled" &&
16
+ gmailResult.reason !== "no gmail account configured") {
17
+ params.log.warn(`gmail watcher not started: ${gmailResult.reason}`);
18
+ }
19
+ }
20
+ catch (err) {
21
+ params.log.error(`gmail watcher failed to start: ${String(err)}`);
22
+ }
23
+ }
@@ -0,0 +1,49 @@
1
+ import path from "node:path";
2
+ import { resolveSafeBaseDir } from "./path-safety.js";
3
+ export function isWindowsDrivePath(value) {
4
+ return /^[a-zA-Z]:[\\/]/.test(value);
5
+ }
6
+ export function normalizeArchiveEntryPath(raw) {
7
+ return raw.replaceAll("\\", "/");
8
+ }
9
+ export function validateArchiveEntryPath(entryPath, params) {
10
+ if (!entryPath || entryPath === "." || entryPath === "./") {
11
+ return;
12
+ }
13
+ if (isWindowsDrivePath(entryPath)) {
14
+ throw new Error(`archive entry uses a drive path: ${entryPath}`);
15
+ }
16
+ const normalized = path.posix.normalize(normalizeArchiveEntryPath(entryPath));
17
+ const escapeLabel = params?.escapeLabel ?? "destination";
18
+ if (normalized === ".." || normalized.startsWith("../")) {
19
+ throw new Error(`archive entry escapes ${escapeLabel}: ${entryPath}`);
20
+ }
21
+ if (path.posix.isAbsolute(normalized) || normalized.startsWith("//")) {
22
+ throw new Error(`archive entry is absolute: ${entryPath}`);
23
+ }
24
+ }
25
+ export function stripArchivePath(entryPath, stripComponents) {
26
+ const raw = normalizeArchiveEntryPath(entryPath);
27
+ if (!raw || raw === "." || raw === "./") {
28
+ return null;
29
+ }
30
+ // Mimic tar --strip-components semantics (raw segments before normalization)
31
+ // so strip-induced escapes like "a/../b" are visible to validators.
32
+ const parts = raw.split("/").filter((part) => part.length > 0 && part !== ".");
33
+ const strip = Math.max(0, Math.floor(stripComponents));
34
+ const stripped = strip === 0 ? parts.join("/") : parts.slice(strip).join("/");
35
+ const result = path.posix.normalize(stripped);
36
+ if (!result || result === "." || result === "./") {
37
+ return null;
38
+ }
39
+ return result;
40
+ }
41
+ export function resolveArchiveOutputPath(params) {
42
+ const safeBase = resolveSafeBaseDir(params.rootDir);
43
+ const outPath = path.resolve(params.rootDir, params.relPath);
44
+ const escapeLabel = params.escapeLabel ?? "destination";
45
+ if (!outPath.startsWith(safeBase)) {
46
+ throw new Error(`archive entry escapes ${escapeLabel}: ${params.originalPath}`);
47
+ }
48
+ return outPath;
49
+ }