@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.
- package/CHANGELOG.md +29 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/onboard-helpers.js +4 -4
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/compat/legacy-names.js +2 -2
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
- package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
- package/dist/control-ui/index.html +1 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- 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
|
-
//
|
|
20
|
-
const
|
|
21
|
-
|
|
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
|
-
?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
+
log.info(`Session context saved to ${relPath}`);
|
|
135
257
|
}
|
|
136
258
|
catch (err) {
|
|
137
|
-
|
|
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
|
+
}
|