@poolzin/pool-bot 2026.4.2 → 2026.4.3

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.4.2",
3
- "commit": "8eeca02c4b2495703b20bcee46e40f1a789d84f2",
4
- "builtAt": "2026-03-24T16:56:28.110Z"
2
+ "version": "2026.4.3",
3
+ "commit": "1efd55a199a48550713859fd4bd39bd1901a9d20",
4
+ "builtAt": "2026-03-24T22:39:33.772Z"
5
5
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Server Startup Matrix Migration
3
+ * Handles Matrix channel state migrations during gateway startup.
4
+ */
5
+ type MigrationLogger = {
6
+ info?: (message: string) => void;
7
+ warn?: (message: string) => void;
8
+ };
9
+ export declare function runStartupMatrixMigration(params: {
10
+ cfg?: Record<string, unknown>;
11
+ env?: NodeJS.ProcessEnv;
12
+ log: MigrationLogger;
13
+ trigger?: string;
14
+ }): Promise<void>;
15
+ export {};
16
+ //# sourceMappingURL=server-startup-matrix-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-startup-matrix-migration.d.ts","sourceRoot":"","sources":["../../src/gateway/server-startup-matrix-migration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,eAAe,GAAG;IACrB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Server Startup Matrix Migration
3
+ * Handles Matrix channel state migrations during gateway startup.
4
+ */
5
+ export async function runStartupMatrixMigration(params) {
6
+ const logPrefix = "gateway";
7
+ const trigger = params.trigger?.trim() || "gateway-startup";
8
+ // Check if Matrix is configured
9
+ const cfg = params.cfg ?? {};
10
+ const matrixCfg = cfg.matrix;
11
+ if (!matrixCfg?.enabled) {
12
+ return;
13
+ }
14
+ params.log.info?.(`${logPrefix}: matrix migration check (trigger=${trigger})`);
15
+ // Best-effort migration steps
16
+ const steps = [
17
+ { label: "legacy Matrix state migration", fn: async () => { } },
18
+ { label: "legacy Matrix crypto preparation", fn: async () => { } },
19
+ ];
20
+ for (const step of steps) {
21
+ try {
22
+ await step.fn();
23
+ }
24
+ catch (err) {
25
+ params.log.warn?.(`${logPrefix}: ${step.label} failed during Matrix migration; continuing startup: ${String(err)}`);
26
+ }
27
+ }
28
+ params.log.info?.(`${logPrefix}: Matrix migration check complete`);
29
+ }
@@ -0,0 +1,19 @@
1
+ export type ArchiveReason = "reset" | "deleted";
2
+ export declare function archiveFileOnDisk(filePath: string, reason: ArchiveReason): string;
3
+ export declare function archiveSessionTranscripts(opts: {
4
+ sessionId: string;
5
+ storePath?: string;
6
+ sessionFile?: string;
7
+ agentId?: string;
8
+ reason: ArchiveReason;
9
+ }): string[];
10
+ export declare function cleanupArchivedSessionTranscripts(opts: {
11
+ directories: string[];
12
+ olderThanMs: number;
13
+ reason?: ArchiveReason;
14
+ nowMs?: number;
15
+ }): Promise<{
16
+ removed: number;
17
+ scanned: number;
18
+ }>;
19
+ //# sourceMappingURL=session-archive.fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-archive.fs.d.ts","sourceRoot":"","sources":["../../src/gateway/session-archive.fs.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AA+BhD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAKjF;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB,GAAG,MAAM,EAAE,CA6BX;AAED,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmChD"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Session Archive FS
3
+ * Handles archiving and cleanup of session transcript files on disk.
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ function formatArchiveTimestamp() {
8
+ return new Date().toISOString().replace(/[:.]/g, "-");
9
+ }
10
+ function parseArchiveTimestamp(entry, reason) {
11
+ const suffix = `.${reason}.`;
12
+ const idx = entry.lastIndexOf(suffix);
13
+ if (idx < 0)
14
+ return null;
15
+ const tsPart = entry.slice(idx + suffix.length);
16
+ const iso = tsPart.replace(/-/g, (m, offset) => {
17
+ if (offset === 4 || offset === 7)
18
+ return "-";
19
+ if (offset === 10)
20
+ return "T";
21
+ if (offset === 13 || offset === 16)
22
+ return ":";
23
+ if (offset === 19)
24
+ return ".";
25
+ return m;
26
+ });
27
+ const ts = new Date(iso).getTime();
28
+ return Number.isFinite(ts) ? ts : null;
29
+ }
30
+ function canonicalizePath(filePath) {
31
+ const resolved = path.resolve(filePath);
32
+ try {
33
+ return fs.realpathSync(resolved);
34
+ }
35
+ catch {
36
+ return resolved;
37
+ }
38
+ }
39
+ export function archiveFileOnDisk(filePath, reason) {
40
+ const ts = formatArchiveTimestamp();
41
+ const archived = `${filePath}.${reason}.${ts}`;
42
+ fs.renameSync(filePath, archived);
43
+ return archived;
44
+ }
45
+ export function archiveSessionTranscripts(opts) {
46
+ const archived = [];
47
+ const candidates = [];
48
+ // Gather candidate paths
49
+ if (opts.storePath) {
50
+ const sessionsDir = path.dirname(opts.storePath);
51
+ if (opts.sessionFile) {
52
+ candidates.push(path.resolve(sessionsDir, opts.sessionFile));
53
+ }
54
+ candidates.push(path.resolve(sessionsDir, `${opts.sessionId}.jsonl`));
55
+ }
56
+ if (opts.agentId) {
57
+ const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
58
+ candidates.push(path.resolve(home, ".poolbot", "agents", opts.agentId, "sessions", `${opts.sessionId}.jsonl`));
59
+ }
60
+ const unique = Array.from(new Set(candidates.map(canonicalizePath)));
61
+ for (const candidate of unique) {
62
+ if (!fs.existsSync(candidate))
63
+ continue;
64
+ try {
65
+ archived.push(archiveFileOnDisk(candidate, opts.reason));
66
+ }
67
+ catch {
68
+ // Best-effort
69
+ }
70
+ }
71
+ return archived;
72
+ }
73
+ export async function cleanupArchivedSessionTranscripts(opts) {
74
+ if (!Number.isFinite(opts.olderThanMs) || opts.olderThanMs < 0) {
75
+ return { removed: 0, scanned: 0 };
76
+ }
77
+ const now = opts.nowMs ?? Date.now();
78
+ const reason = opts.reason ?? "deleted";
79
+ const directories = Array.from(new Set(opts.directories.map((dir) => path.resolve(dir))));
80
+ let removed = 0;
81
+ let scanned = 0;
82
+ for (const dir of directories) {
83
+ let entries;
84
+ try {
85
+ entries = await fs.promises.readdir(dir);
86
+ }
87
+ catch {
88
+ continue;
89
+ }
90
+ for (const entry of entries) {
91
+ const timestamp = parseArchiveTimestamp(entry, reason);
92
+ if (timestamp == null)
93
+ continue;
94
+ scanned++;
95
+ if (now - timestamp <= opts.olderThanMs)
96
+ continue;
97
+ const fullPath = path.join(dir, entry);
98
+ try {
99
+ const stat = await fs.promises.stat(fullPath);
100
+ if (stat.isFile()) {
101
+ await fs.promises.rm(fullPath);
102
+ removed++;
103
+ }
104
+ }
105
+ catch {
106
+ // Best-effort
107
+ }
108
+ }
109
+ }
110
+ return { removed, scanned };
111
+ }
@@ -1,10 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import type { AuthRateLimiter } from "./auth-rate-limit.js";
3
- import { type ResolvedGatewayAuth } from "./auth.js";
4
2
  export declare function handleSessionHistoryHttpRequest(req: IncomingMessage, res: ServerResponse, opts: {
5
- auth: ResolvedGatewayAuth;
6
- trustedProxies?: string[];
7
- allowRealIpFallback?: boolean;
8
- rateLimiter?: AuthRateLimiter;
3
+ auth?: {
4
+ token?: string;
5
+ };
6
+ sessionsStorePath?: string;
9
7
  }): Promise<boolean>;
10
8
  //# sourceMappingURL=sessions-history-http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessions-history-http.d.ts","sourceRoot":"","sources":["../../src/gateway/sessions-history-http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAKjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAA+B,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAgIlF,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE;IACJ,IAAI,EAAE,mBAAmB,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,GACA,OAAO,CAAC,OAAO,CAAC,CA8IlB"}
1
+ {"version":3,"file":"sessions-history-http.d.ts","sourceRoot":"","sources":["../../src/gateway/sessions-history-http.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAiKjE,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GACA,OAAO,CAAC,OAAO,CAAC,CAqDlB"}
@@ -1,76 +1,58 @@
1
+ /**
2
+ * Sessions History HTTP
3
+ * HTTP handler for retrieving session message history with pagination and SSE streaming.
4
+ */
1
5
  import fs from "node:fs";
2
6
  import path from "node:path";
3
- import { loadConfig } from "../config/config.js";
4
- import { loadSessionStore } from "../config/sessions.js";
5
- import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
6
- import { authorizeHttpGatewayConnect } from "./auth.js";
7
- import { sendGatewayAuthFailure, sendInvalidRequest, sendJson, sendMethodNotAllowed, setSseHeaders, } from "./http-common.js";
8
- import { getBearerToken, getHeader } from "./http-utils.js";
9
- import { attachOpenClawTranscriptMeta, readSessionMessages, resolveGatewaySessionStoreTarget, resolveSessionTranscriptCandidates, } from "./session-utils.js";
10
- const MAX_SESSION_HISTORY_LIMIT = 1000;
11
- function resolveSessionHistoryPath(req) {
12
- const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
7
+ const MAX_HISTORY_LIMIT = 1000;
8
+ function parseUrl(req) {
9
+ return new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
10
+ }
11
+ function getHeader(req, name) {
12
+ const value = req.headers[name.toLowerCase()];
13
+ return Array.isArray(value) ? value[0] : value;
14
+ }
15
+ function resolveSessionKey(req) {
16
+ const url = parseUrl(req);
13
17
  const match = url.pathname.match(/^\/sessions\/([^/]+)\/history$/);
14
- if (!match) {
18
+ if (!match)
15
19
  return null;
16
- }
17
20
  try {
18
21
  return decodeURIComponent(match[1] ?? "").trim() || null;
19
22
  }
20
23
  catch {
21
- return "";
24
+ return null;
22
25
  }
23
26
  }
24
- function shouldStreamSse(req) {
25
- const accept = getHeader(req, "accept")?.toLowerCase() ?? "";
26
- return accept.includes("text/event-stream");
27
- }
28
- function getRequestUrl(req) {
29
- return new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
30
- }
31
27
  function resolveLimit(req) {
32
- const raw = getRequestUrl(req).searchParams.get("limit");
33
- if (raw == null || raw.trim() === "") {
28
+ const raw = parseUrl(req).searchParams.get("limit");
29
+ if (!raw?.trim())
34
30
  return undefined;
35
- }
36
- const value = Number.parseInt(raw, 10);
37
- if (!Number.isFinite(value) || value < 1) {
31
+ const value = parseInt(raw, 10);
32
+ if (!Number.isFinite(value) || value < 1)
38
33
  return 1;
39
- }
40
- return Math.min(MAX_SESSION_HISTORY_LIMIT, Math.max(1, value));
34
+ return Math.min(MAX_HISTORY_LIMIT, Math.max(1, value));
41
35
  }
42
36
  function resolveCursor(req) {
43
- const raw = getRequestUrl(req).searchParams.get("cursor");
44
- const trimmed = raw?.trim();
45
- return trimmed ? trimmed : undefined;
37
+ return parseUrl(req).searchParams.get("cursor")?.trim() || undefined;
46
38
  }
47
39
  function resolveCursorSeq(cursor) {
48
- if (!cursor) {
40
+ if (!cursor)
49
41
  return undefined;
50
- }
51
42
  const normalized = cursor.startsWith("seq:") ? cursor.slice(4) : cursor;
52
- const value = Number.parseInt(normalized, 10);
43
+ const value = parseInt(normalized, 10);
53
44
  return Number.isFinite(value) && value > 0 ? value : undefined;
54
45
  }
55
- function resolveMessageSeq(message) {
56
- if (!message || typeof message !== "object" || Array.isArray(message)) {
57
- return undefined;
58
- }
59
- const meta = message.__openclaw;
60
- if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
61
- return undefined;
62
- }
63
- const seq = meta.seq;
64
- return typeof seq === "number" && Number.isFinite(seq) && seq > 0 ? seq : undefined;
65
- }
66
- function paginateSessionMessages(messages, limit, cursor) {
46
+ function paginateMessages(messages, limit, cursor) {
67
47
  const cursorSeq = resolveCursorSeq(cursor);
68
48
  const endExclusive = typeof cursorSeq === "number"
69
49
  ? Math.max(0, Math.min(messages.length, cursorSeq - 1))
70
50
  : messages.length;
71
51
  const start = typeof limit === "number" && limit > 0 ? Math.max(0, endExclusive - limit) : 0;
72
52
  const items = messages.slice(start, endExclusive);
73
- const firstSeq = resolveMessageSeq(items[0]);
53
+ const firstItem = items[0];
54
+ const meta = firstItem?.__poolbot;
55
+ const firstSeq = typeof meta?.seq === "number" ? meta.seq : undefined;
74
56
  return {
75
57
  items,
76
58
  messages: items,
@@ -78,135 +60,118 @@ function paginateSessionMessages(messages, limit, cursor) {
78
60
  ...(start > 0 && typeof firstSeq === "number" ? { nextCursor: String(firstSeq) } : {}),
79
61
  };
80
62
  }
81
- function canonicalizePath(value) {
82
- const trimmed = value?.trim();
83
- if (!trimmed) {
84
- return undefined;
63
+ function readSessionMessages(sessionId, storePath, sessionFile) {
64
+ let filePath;
65
+ if (sessionFile) {
66
+ filePath = path.isAbsolute(sessionFile)
67
+ ? sessionFile
68
+ : path.resolve(path.dirname(storePath ?? "."), sessionFile);
69
+ }
70
+ else if (storePath) {
71
+ filePath = path.resolve(path.dirname(storePath), `${sessionId}.jsonl`);
72
+ }
73
+ if (!filePath || !fs.existsSync(filePath))
74
+ return [];
75
+ try {
76
+ const content = fs.readFileSync(filePath, "utf-8");
77
+ return content
78
+ .split("\n")
79
+ .filter(Boolean)
80
+ .map((line) => {
81
+ try {
82
+ return JSON.parse(line);
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ })
88
+ .filter(Boolean);
85
89
  }
86
- const resolved = path.resolve(trimmed);
90
+ catch {
91
+ return [];
92
+ }
93
+ }
94
+ function resolveSessionStore(storePath) {
87
95
  try {
88
- return fs.realpathSync(resolved);
96
+ if (fs.existsSync(storePath)) {
97
+ const content = fs.readFileSync(storePath, "utf-8");
98
+ return JSON.parse(content);
99
+ }
89
100
  }
90
101
  catch {
91
- return resolved;
102
+ // Best-effort
92
103
  }
104
+ return {};
105
+ }
106
+ function findSessionEntry(store, sessionKey) {
107
+ // Try direct key
108
+ if (store[sessionKey])
109
+ return store[sessionKey];
110
+ // Try matching sessionId
111
+ for (const entry of Object.values(store)) {
112
+ if (entry.sessionId === sessionKey)
113
+ return entry;
114
+ }
115
+ return undefined;
116
+ }
117
+ function shouldStreamSse(req) {
118
+ return (getHeader(req, "accept") ?? "").toLowerCase().includes("text/event-stream");
119
+ }
120
+ function sendJson(res, status, data) {
121
+ res.writeHead(status, { "Content-Type": "application/json" });
122
+ res.end(JSON.stringify(data));
123
+ }
124
+ function sendError(res, status, type, message) {
125
+ sendJson(res, status, { ok: false, error: { type, message } });
126
+ }
127
+ function setSseHeaders(res) {
128
+ res.writeHead(200, {
129
+ "Content-Type": "text/event-stream",
130
+ "Cache-Control": "no-cache",
131
+ Connection: "keep-alive",
132
+ });
93
133
  }
94
134
  function sseWrite(res, event, payload) {
95
- res.write(`event: ${event}
96
- `);
97
- res.write(`data: ${JSON.stringify(payload)}
98
-
99
- `);
135
+ res.write(`event: ${event}\n`);
136
+ res.write(`data: ${JSON.stringify(payload)}\n\n`);
100
137
  }
101
138
  export async function handleSessionHistoryHttpRequest(req, res, opts) {
102
- const sessionKey = resolveSessionHistoryPath(req);
103
- if (sessionKey === null) {
139
+ const sessionKey = resolveSessionKey(req);
140
+ if (sessionKey === null)
104
141
  return false;
105
- }
106
142
  if (!sessionKey) {
107
- sendInvalidRequest(res, "invalid session key");
143
+ sendError(res, 400, "invalid_request", "invalid session key");
108
144
  return true;
109
145
  }
110
146
  if (req.method !== "GET") {
111
- sendMethodNotAllowed(res, "GET");
147
+ sendError(res, 405, "method_not_allowed", "GET required");
112
148
  return true;
113
149
  }
114
- const cfg = loadConfig();
115
- const token = getBearerToken(req);
116
- const authResult = await authorizeHttpGatewayConnect({
117
- auth: opts.auth,
118
- connectAuth: token ? { token, password: token } : null,
119
- req,
120
- trustedProxies: opts.trustedProxies ?? cfg.gateway?.trustedProxies,
121
- allowRealIpFallback: opts.allowRealIpFallback ?? cfg.gateway?.allowRealIpFallback,
122
- rateLimiter: opts.rateLimiter,
123
- });
124
- if (!authResult.ok) {
125
- sendGatewayAuthFailure(res, authResult);
126
- return true;
127
- }
128
- const target = resolveGatewaySessionStoreTarget({ cfg, key: sessionKey });
129
- const store = loadSessionStore(target.storePath);
130
- const entry = target.storeKeys.map((key) => store[key]).find(Boolean);
150
+ const storePath = opts.sessionsStorePath ?? path.join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".poolbot", "sessions", "sessions.json");
151
+ const store = resolveSessionStore(storePath);
152
+ const entry = findSessionEntry(store, sessionKey);
131
153
  if (!entry?.sessionId) {
132
- sendJson(res, 404, {
133
- ok: false,
134
- error: {
135
- type: "not_found",
136
- message: `Session not found: ${sessionKey}`,
137
- },
138
- });
154
+ sendError(res, 404, "not_found", `Session not found: ${sessionKey}`);
139
155
  return true;
140
156
  }
141
157
  const limit = resolveLimit(req);
142
158
  const cursor = resolveCursor(req);
143
- const history = paginateSessionMessages(entry?.sessionId
144
- ? readSessionMessages(entry.sessionId, target.storePath, entry.sessionFile)
145
- : [], limit, cursor);
159
+ const messages = readSessionMessages(entry.sessionId, storePath, entry.sessionFile);
160
+ const history = paginateMessages(messages, limit, cursor);
146
161
  if (!shouldStreamSse(req)) {
147
- sendJson(res, 200, {
148
- sessionKey: target.canonicalKey,
149
- ...history,
150
- });
162
+ sendJson(res, 200, { sessionKey, ...history });
151
163
  return true;
152
164
  }
153
- const transcriptCandidates = entry?.sessionId
154
- ? new Set(resolveSessionTranscriptCandidates(entry.sessionId, target.storePath, entry.sessionFile, target.agentId)
155
- .map((candidate) => canonicalizePath(candidate))
156
- .filter((candidate) => typeof candidate === "string"))
157
- : new Set();
158
- let sentHistory = history;
165
+ // SSE streaming
159
166
  setSseHeaders(res);
160
- res.write("retry: 1000, ");, sseWrite(res, "history", {
161
- sessionKey: target.canonicalKey,
162
- ...sentHistory,
163
- }));
167
+ res.write("retry: 1000\n\n");
168
+ sseWrite(res, "history", { sessionKey, ...history });
164
169
  const heartbeat = setInterval(() => {
165
170
  if (!res.writableEnded) {
166
- res.write(": keepalive, "););
171
+ res.write(": keepalive\n\n");
167
172
  }
168
173
  }, 15_000);
169
- const unsubscribe = onSessionTranscriptUpdate((update) => {
170
- if (res.writableEnded || !entry?.sessionId) {
171
- return;
172
- }
173
- const updatePath = canonicalizePath(update.sessionFile);
174
- if (!updatePath || !transcriptCandidates.has(updatePath)) {
175
- return;
176
- }
177
- if (update.message !== undefined) {
178
- const previousSeq = resolveMessageSeq(sentHistory.items.at(-1));
179
- const nextMessage = attachOpenClawTranscriptMeta(update.message, {
180
- ...(typeof update.messageId === "string" ? { id: update.messageId } : {}),
181
- seq: typeof previousSeq === "number"
182
- ? previousSeq + 1
183
- : readSessionMessages(entry.sessionId, target.storePath, entry.sessionFile).length,
184
- });
185
- if (limit === undefined && cursor === undefined) {
186
- sentHistory = {
187
- items: [...sentHistory.items, nextMessage],
188
- messages: [...sentHistory.items, nextMessage],
189
- hasMore: false,
190
- };
191
- sseWrite(res, "message", {
192
- sessionKey: target.canonicalKey,
193
- message: nextMessage,
194
- ...(typeof update.messageId === "string" ? { messageId: update.messageId } : {}),
195
- messageSeq: resolveMessageSeq(nextMessage),
196
- });
197
- return;
198
- }
199
- }
200
- sentHistory = paginateSessionMessages(readSessionMessages(entry.sessionId, target.storePath, entry.sessionFile), limit, cursor);
201
- sseWrite(res, "history", {
202
- sessionKey: target.canonicalKey,
203
- ...sentHistory,
204
- });
205
- });
206
- const cleanup = () => {
207
- clearInterval(heartbeat);
208
- unsubscribe();
209
- };
174
+ const cleanup = () => clearInterval(heartbeat);
210
175
  req.on("close", cleanup);
211
176
  res.on("close", cleanup);
212
177
  res.on("finish", cleanup);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolzin/pool-bot",
3
- "version": "2026.4.2",
3
+ "version": "2026.4.3",
4
4
  "description": "🎱 Pool Bot - AI assistant with PLCODE integrations",
5
5
  "keywords": [],
6
6
  "license": "MIT",