@rubytech/create-realagent 1.0.853 → 1.0.855

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 (42) hide show
  1. package/dist/index.js +9 -3
  2. package/package.json +1 -1
  3. package/payload/platform/lib/persistent-components/dist/index.d.ts +21 -0
  4. package/payload/platform/lib/persistent-components/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/persistent-components/dist/index.js +32 -0
  6. package/payload/platform/lib/persistent-components/dist/index.js.map +1 -0
  7. package/payload/platform/lib/persistent-components/src/index.ts +28 -0
  8. package/payload/platform/lib/persistent-components/tsconfig.json +8 -0
  9. package/payload/platform/package.json +2 -2
  10. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  11. package/payload/platform/plugins/admin/hooks/__tests__/playwright-file-guard.test.sh +278 -0
  12. package/payload/platform/plugins/admin/hooks/playwright-file-guard.sh +204 -20
  13. package/payload/platform/plugins/admin/mcp/dist/index.js +40 -1
  14. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/cloudflare/references/manual-setup.md +2 -0
  16. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +39 -10
  17. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +112 -20
  18. package/payload/platform/plugins/docs/references/cloudflare.md +1 -0
  19. package/payload/platform/plugins/docs/references/deployment.md +2 -0
  20. package/payload/platform/plugins/docs/references/getting-started.md +2 -0
  21. package/payload/platform/plugins/docs/references/platform.md +1 -1
  22. package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
  23. package/payload/platform/scripts/admin-persist-audit.ts +191 -0
  24. package/payload/platform/scripts/component-knowledgedoc-backfill.ts +214 -0
  25. package/payload/platform/scripts/installer-device-verify.sh +17 -4
  26. package/payload/platform/templates/specialists/agents/content-producer.md +2 -2
  27. package/payload/server/chunk-CFNSKDGA.js +667 -0
  28. package/payload/server/chunk-DC6DWYZJ.js +1603 -0
  29. package/payload/server/chunk-LTB5SSQW.js +10889 -0
  30. package/payload/server/chunk-MN2LGNUB.js +2143 -0
  31. package/payload/server/client-pool-AMT2W3II.js +34 -0
  32. package/payload/server/cloudflare-task-tracker-LJ4SMK2D.js +20 -0
  33. package/payload/server/maxy-edge.js +3 -3
  34. package/payload/server/public/assets/admin-Cpk5cT4I.js +352 -0
  35. package/payload/server/public/assets/public-DApUXgoq.js +5 -0
  36. package/payload/server/public/assets/useVoiceRecorder-CI8GpxfU.js +36 -0
  37. package/payload/server/public/index.html +2 -2
  38. package/payload/server/public/public.html +2 -2
  39. package/payload/server/server.js +543 -354
  40. package/payload/server/public/assets/admin-Dyl8uNxX.js +0 -352
  41. package/payload/server/public/assets/public-B_PNZUph.js +0 -5
  42. package/payload/server/public/assets/useVoiceRecorder-fD0IWzJj.js +0 -36
@@ -0,0 +1,1603 @@
1
+ import {
2
+ createNewAdminConversation,
3
+ ensureConversation,
4
+ isMessageUseful,
5
+ persistMessage,
6
+ setConversationAgentSessionId,
7
+ setSessionStoreRef
8
+ } from "./chunk-MN2LGNUB.js";
9
+
10
+ // app/lib/claude-agent/client-pool.ts
11
+ import { query } from "@anthropic-ai/claude-agent-sdk";
12
+ import { appendFileSync as appendFileSync2 } from "fs";
13
+ import { resolve as resolvePath } from "path";
14
+
15
+ // app/lib/claude-agent/logging.ts
16
+ import { spawnSync } from "child_process";
17
+ import { resolve } from "path";
18
+ import { platform as osPlatform } from "os";
19
+ import { readFileSync, readdirSync, mkdirSync, createWriteStream, statSync, unlinkSync, renameSync, appendFileSync, existsSync } from "fs";
20
+ import { lookup as dnsLookup } from "dns/promises";
21
+ import { createConnection as netConnect } from "net";
22
+ import { StringDecoder } from "string_decoder";
23
+ var LOG_RETENTION_DAYS = 7;
24
+ var isoTs = () => (/* @__PURE__ */ new Date()).toISOString();
25
+ var BROWSER_TOOL_PREFIXES = [
26
+ "mcp__plugin_playwright_playwright__",
27
+ "mcp__plugin_chrome-devtools-mcp_chrome-devtools__"
28
+ ];
29
+ function isBrowserTool(name) {
30
+ return BROWSER_TOOL_PREFIXES.some((p) => name.startsWith(p));
31
+ }
32
+ var DIAG_HARD_CAP_MS = 5e3;
33
+ var DIAG_DNS_TIMEOUT_MS = 2e3;
34
+ var DIAG_TCP_TIMEOUT_MS = 3e3;
35
+ var DIAG_HTTP_TIMEOUT_MS = 4e3;
36
+ function quoteDiag(value) {
37
+ return JSON.stringify(value);
38
+ }
39
+ function extractUrl(toolName, input) {
40
+ if (input === null || typeof input !== "object") return void 0;
41
+ const obj = input;
42
+ if (toolName === "WebFetch" && typeof obj.url === "string") return obj.url;
43
+ if (isBrowserTool(toolName) && typeof obj.url === "string") return obj.url;
44
+ if (typeof obj.url === "string" && /^https?:\/\//.test(obj.url)) return obj.url;
45
+ return void 0;
46
+ }
47
+ var FULL_REDACT_ENV_VARS = /* @__PURE__ */ new Set(["HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY"]);
48
+ function redactEnvField(name) {
49
+ const value = process.env[name];
50
+ if (!value) return `${name.toLowerCase()}=absent`;
51
+ if (FULL_REDACT_ENV_VARS.has(name)) {
52
+ return `${name.toLowerCase()}=present`;
53
+ }
54
+ const suffix = value.length > 40 ? value.slice(-40) : value;
55
+ return `${name.toLowerCase()}=present suffix=${quoteDiag(suffix)}`;
56
+ }
57
+ async function probeDns(host, family) {
58
+ const label = family === 4 ? "dns_a" : "dns_aaaa";
59
+ const start = Date.now();
60
+ let timer;
61
+ try {
62
+ const result = await Promise.race([
63
+ dnsLookup(host, { family, verbatim: true }),
64
+ new Promise((_, reject) => {
65
+ timer = setTimeout(() => reject(new Error("timeout")), DIAG_DNS_TIMEOUT_MS);
66
+ })
67
+ ]);
68
+ if (timer) clearTimeout(timer);
69
+ const ms = Date.now() - start;
70
+ return `${label}=${result.address} ${label}_ms=${ms}`;
71
+ } catch (err) {
72
+ if (timer) clearTimeout(timer);
73
+ const ms = Date.now() - start;
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ return `${label}=err ${label}_err=${quoteDiag(msg.slice(0, 60))} ${label}_ms=${ms}`;
76
+ }
77
+ }
78
+ async function probeTcp(host, port) {
79
+ const start = Date.now();
80
+ return new Promise((resolvePromise) => {
81
+ let settled = false;
82
+ const sock = netConnect({ host, port, family: 0 });
83
+ const finish = (result) => {
84
+ if (settled) return;
85
+ settled = true;
86
+ try {
87
+ sock.destroy();
88
+ } catch {
89
+ }
90
+ resolvePromise(result);
91
+ };
92
+ const timer = setTimeout(() => finish(`tcp=timeout tcp_ms=${Date.now() - start}`), DIAG_TCP_TIMEOUT_MS);
93
+ sock.once("connect", () => {
94
+ clearTimeout(timer);
95
+ finish(`tcp=ok tcp_ms=${Date.now() - start}`);
96
+ });
97
+ sock.once("error", (err) => {
98
+ clearTimeout(timer);
99
+ const msg = err instanceof Error ? err.message : String(err);
100
+ finish(`tcp=err tcp_err=${quoteDiag(msg.slice(0, 60))} tcp_ms=${Date.now() - start}`);
101
+ });
102
+ });
103
+ }
104
+ async function probeHttp(url) {
105
+ const start = Date.now();
106
+ const controller = new AbortController();
107
+ const timer = setTimeout(() => controller.abort(), DIAG_HTTP_TIMEOUT_MS);
108
+ try {
109
+ const res = await fetch(url, { method: "HEAD", redirect: "manual", signal: controller.signal });
110
+ clearTimeout(timer);
111
+ return `http_status=${res.status} http_ms=${Date.now() - start}`;
112
+ } catch (err) {
113
+ clearTimeout(timer);
114
+ const msg = err instanceof Error ? err.message : String(err);
115
+ return `http_status=err http_err=${quoteDiag(msg.slice(0, 60))} http_ms=${Date.now() - start}`;
116
+ }
117
+ }
118
+ async function runFailureDiagnostic(toolName, toolInput) {
119
+ const inputKeys = toolInput !== null && typeof toolInput === "object" ? Object.keys(toolInput).join(",") : "";
120
+ const envFields = [
121
+ redactEnvField("HTTPS_PROXY"),
122
+ redactEnvField("HTTP_PROXY"),
123
+ redactEnvField("NO_PROXY"),
124
+ redactEnvField("NODE_OPTIONS")
125
+ ].join(" ");
126
+ const url = extractUrl(toolName, toolInput);
127
+ if (!url) {
128
+ return `diag_url=none input_keys=[${inputKeys}] ${envFields}`;
129
+ }
130
+ let host;
131
+ let port;
132
+ try {
133
+ const parsed = new URL(url);
134
+ host = parsed.hostname;
135
+ port = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
136
+ } catch {
137
+ return `diag_url=unparseable input_keys=[${inputKeys}] ${envFields}`;
138
+ }
139
+ const probes = Promise.allSettled([
140
+ probeDns(host, 4),
141
+ probeDns(host, 6),
142
+ probeTcp(host, port),
143
+ probeHttp(url)
144
+ ]);
145
+ let capTimer;
146
+ const capped = await Promise.race([
147
+ probes,
148
+ new Promise((resolvePromise) => {
149
+ capTimer = setTimeout(() => resolvePromise("__diag_timeout__"), DIAG_HARD_CAP_MS);
150
+ })
151
+ ]);
152
+ if (capTimer) clearTimeout(capTimer);
153
+ if (capped === "__diag_timeout__") {
154
+ return `diag_host=${host} diag_port=${port} diag_timeout=true input_keys=[${inputKeys}] ${envFields}`;
155
+ }
156
+ const fields = capped.map((r) => r.status === "fulfilled" ? r.value : `probe_err=${quoteDiag(String(r.reason).slice(0, 40))}`).join(" ");
157
+ return `diag_host=${host} diag_port=${port} ${fields} input_keys=[${inputKeys}] ${envFields}`;
158
+ }
159
+ function agentLogStream(name, accountDir, conversationId) {
160
+ if (!conversationId) {
161
+ throw new Error(`agentLogStream: conversationId is required (name=${name}) \u2014 use preConversationLogStream for pre-session events`);
162
+ }
163
+ const logDir = resolve(accountDir, "logs");
164
+ mkdirSync(logDir, { recursive: true });
165
+ purgeOldLogs(logDir, `${name}-`);
166
+ const logPath = resolve(logDir, `${name}-${conversationId}.log`);
167
+ const stream = createWriteStream(logPath, { flags: "a" });
168
+ registerStreamLog(stream, { path: logPath, conversationId, name });
169
+ return stream;
170
+ }
171
+ function preConversationLogStream(name, accountDir) {
172
+ const logDir = resolve(accountDir, "logs");
173
+ mkdirSync(logDir, { recursive: true });
174
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
175
+ purgeOldLogs(logDir, `preconversation-${name}-`);
176
+ const logPath = resolve(logDir, `preconversation-${name}-${date}.log`);
177
+ const stream = createWriteStream(logPath, { flags: "a" });
178
+ registerStreamLog(stream, { path: logPath, conversationId: null, name: `preconversation-${name}` });
179
+ return stream;
180
+ }
181
+ var openStreamLogs = /* @__PURE__ */ new Map();
182
+ function registerStreamLog(stream, entry) {
183
+ openStreamLogs.set(stream, entry);
184
+ stream.once("close", () => {
185
+ openStreamLogs.delete(stream);
186
+ if (entry.conversationId) {
187
+ logTeeLog(`[log-tee] deregister conversationId=${entry.conversationId.slice(0, 8)} path=${JSON.stringify(entry.path)}`);
188
+ }
189
+ });
190
+ if (entry.conversationId) {
191
+ logTeeLog(`[log-tee] register conversationId=${entry.conversationId.slice(0, 8)} path=${JSON.stringify(entry.path)}`);
192
+ }
193
+ }
194
+ var LOG_TEE_TAG_RE = /^\[[a-zA-Z][a-zA-Z0-9:_\-]*\]/;
195
+ var originalConsoleError = null;
196
+ var originalConsoleLog = null;
197
+ var logTeeInstalled = false;
198
+ var logTeeCycleTimer = null;
199
+ var logTeeLinesEmitted = 0;
200
+ var logTeeLinesRouted = 0;
201
+ var logTeeBytesRouted = 0;
202
+ var logTeeFailCount = 0;
203
+ function logTeeLog(line) {
204
+ (originalConsoleError ?? console.error.bind(console))(line);
205
+ }
206
+ function appendToActiveStreams(line) {
207
+ if (openStreamLogs.size === 0) return;
208
+ const ts = isoTs();
209
+ const teeLine = `[${ts}] ${line.replace(/\n$/, "")}
210
+ `;
211
+ let routed = 0;
212
+ for (const entry of openStreamLogs.values()) {
213
+ if (!entry.conversationId) continue;
214
+ try {
215
+ appendFileSync(entry.path, teeLine);
216
+ routed++;
217
+ logTeeBytesRouted += teeLine.length;
218
+ } catch (err) {
219
+ logTeeFailCount++;
220
+ const msg = err instanceof Error ? err.message : String(err);
221
+ logTeeLog(`[log-tee] FAIL emit reason=${JSON.stringify(msg.slice(0, 80))} path=${JSON.stringify(entry.path)}`);
222
+ }
223
+ }
224
+ if (routed > 0) {
225
+ logTeeLinesRouted += routed;
226
+ logTeeLinesEmitted++;
227
+ }
228
+ }
229
+ function installLogTee() {
230
+ if (logTeeInstalled) return;
231
+ logTeeInstalled = true;
232
+ originalConsoleError = console.error.bind(console);
233
+ originalConsoleLog = console.log.bind(console);
234
+ const wrap = (orig) => {
235
+ return (...args) => {
236
+ orig(...args);
237
+ const rendered = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
238
+ if (LOG_TEE_TAG_RE.test(rendered)) {
239
+ appendToActiveStreams(rendered);
240
+ }
241
+ };
242
+ };
243
+ console.error = wrap(originalConsoleError);
244
+ console.log = wrap(originalConsoleLog);
245
+ logTeeCycleTimer = setInterval(() => {
246
+ let active = 0;
247
+ for (const e of openStreamLogs.values()) if (e.conversationId) active++;
248
+ logTeeLog(
249
+ `[log-tee] cycle activeConversations=${active} linesEmitted=${logTeeLinesEmitted} linesRouted=${logTeeLinesRouted} bytesRouted=${logTeeBytesRouted} failCount=${logTeeFailCount}`
250
+ );
251
+ }, 3e4);
252
+ logTeeCycleTimer.unref?.();
253
+ logTeeLog(`[log-tee] installed pid=${process.pid}`);
254
+ }
255
+ function safeStringify(value) {
256
+ try {
257
+ if (value instanceof Error) return value.stack ?? value.message;
258
+ return JSON.stringify(value);
259
+ } catch {
260
+ return String(value);
261
+ }
262
+ }
263
+ if (process.env.NODE_ENV !== "test") {
264
+ installLogTee();
265
+ }
266
+ function sigtermFlushStreamLogs(reason, source) {
267
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
268
+ for (const entry of openStreamLogs.values()) {
269
+ const convPart = entry.conversationId ? ` conversationId=${entry.conversationId}` : "";
270
+ const line = `[${ts}] [server-sigterm] reason=${reason}${convPart} name=${entry.name} source=${source}
271
+ `;
272
+ try {
273
+ appendFileSync(entry.path, line);
274
+ } catch (err) {
275
+ const msg = err instanceof Error ? err.message : String(err);
276
+ console.error(`[server-sigterm-flush-err] path=${entry.path} reason=${msg}`);
277
+ }
278
+ }
279
+ }
280
+ function purgeOldLogs(logDir, prefix) {
281
+ const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
282
+ let entries;
283
+ try {
284
+ entries = readdirSync(logDir);
285
+ } catch (err) {
286
+ const msg = err instanceof Error ? err.message : String(err);
287
+ console.error(`[log-purge-err] readdir dir=${logDir} prefix=${prefix} reason=${msg}`);
288
+ return;
289
+ }
290
+ for (const file of entries) {
291
+ if (!file.startsWith(prefix)) continue;
292
+ const filePath = resolve(logDir, file);
293
+ try {
294
+ if (statSync(filePath).mtimeMs < cutoff) unlinkSync(filePath);
295
+ } catch (err) {
296
+ const msg = err instanceof Error ? err.message : String(err);
297
+ console.error(`[log-purge-err] file=${file} reason=${msg}`);
298
+ }
299
+ }
300
+ }
301
+ function preflushStreamLogKey(sessionKey) {
302
+ return `preflush-${sessionKey.slice(0, 12)}`;
303
+ }
304
+ function renameStreamLogsOnFlush(accountDir, sessionKey, conversationId) {
305
+ const STREAM_LOG_NAMES = ["claude-agent-stream", "claude-agent-stderr", "public-agent-stream"];
306
+ const logDir = resolve(accountDir, "logs");
307
+ const preflushSuffix = preflushStreamLogKey(sessionKey);
308
+ const sk8 = sessionKey.slice(0, 8);
309
+ const cid8 = conversationId.slice(0, 8);
310
+ for (const name of STREAM_LOG_NAMES) {
311
+ const fromPath = resolve(logDir, `${name}-${preflushSuffix}.log`);
312
+ const toPath = resolve(logDir, `${name}-${conversationId}.log`);
313
+ let result;
314
+ let reason = "";
315
+ try {
316
+ if (!existsSync(fromPath)) {
317
+ result = "skipped";
318
+ reason = "no-preflush-file";
319
+ } else {
320
+ const targetExisted = existsSync(toPath);
321
+ renameSync(fromPath, toPath);
322
+ for (const entry of openStreamLogs.values()) {
323
+ if (entry.path === fromPath) {
324
+ const wasNull = entry.conversationId === null;
325
+ entry.path = toPath;
326
+ entry.conversationId = conversationId;
327
+ if (wasNull) {
328
+ logTeeLog(`[log-tee] register conversationId=${conversationId.slice(0, 8)} path=${JSON.stringify(toPath)} via=rename`);
329
+ }
330
+ }
331
+ }
332
+ result = "renamed";
333
+ if (targetExisted) reason = "target-existed";
334
+ }
335
+ } catch (err) {
336
+ result = "error";
337
+ reason = err instanceof Error ? err.message : String(err);
338
+ }
339
+ const reasonPart = reason ? ` reason=${JSON.stringify(reason)}` : "";
340
+ console.log(`[stream-log-rename] ${(/* @__PURE__ */ new Date()).toISOString()} sessionKey=${sk8} conversationId=${cid8} name=${name} result=${result}${reasonPart}`);
341
+ if (result === "renamed" && existsSync(fromPath)) {
342
+ console.error(`[stream-log-rename] ${(/* @__PURE__ */ new Date()).toISOString()} outcome=stale-sibling preflush=${JSON.stringify(fromPath)} full=${JSON.stringify(toPath)} reason=${JSON.stringify("rename succeeded but preflush still present")} sessionKey=${sk8} conversationId=${cid8} name=${name}`);
343
+ }
344
+ }
345
+ }
346
+
347
+ // app/lib/claude-agent/session-store.ts
348
+ import { resolve as resolve4 } from "path";
349
+
350
+ // app/lib/claude-agent/account.ts
351
+ import { resolve as resolve3 } from "path";
352
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, existsSync as existsSync4, statSync as statSync2 } from "fs";
353
+
354
+ // ../lib/brand-templating/src/index.ts
355
+ import { join } from "path";
356
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
357
+ var PLACEHOLDER = "{{productName}}";
358
+ var cachedProductName = null;
359
+ function brandJsonPath() {
360
+ const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
361
+ if (!platformRoot2) {
362
+ throw new Error(
363
+ "[skill-loader] MAXY_PLATFORM_ROOT not set \u2014 cannot resolve brand.json"
364
+ );
365
+ }
366
+ return join(platformRoot2, "config", "brand.json");
367
+ }
368
+ function getBrandProductName() {
369
+ if (cachedProductName !== null) return cachedProductName;
370
+ const path = brandJsonPath();
371
+ if (!existsSync2(path)) {
372
+ throw new Error(`[skill-loader] brand.json missing at ${path}`);
373
+ }
374
+ let parsed;
375
+ try {
376
+ parsed = JSON.parse(readFileSync2(path, "utf-8"));
377
+ } catch (err) {
378
+ throw new Error(
379
+ `[skill-loader] brand.json unreadable at ${path}: ${err instanceof Error ? err.message : String(err)}`
380
+ );
381
+ }
382
+ const value = parsed.productName;
383
+ if (typeof value !== "string" || value.trim() === "") {
384
+ throw new Error(
385
+ `[skill-loader] brand.json at ${path} has missing or empty productName`
386
+ );
387
+ }
388
+ cachedProductName = value;
389
+ return value;
390
+ }
391
+ function substituteBrandPlaceholders(content, sourcePath) {
392
+ if (!content.includes(PLACEHOLDER)) return content;
393
+ let productName;
394
+ try {
395
+ productName = getBrandProductName();
396
+ } catch (err) {
397
+ console.error(
398
+ `[skill-loader] ERROR: brand.json missing \u2014 cannot resolve productName for skill ${sourcePath}`
399
+ );
400
+ throw err;
401
+ }
402
+ const occurrences = content.split(PLACEHOLDER).length - 1;
403
+ const substituted = content.split(PLACEHOLDER).join(productName);
404
+ console.log(
405
+ `[skill-loader] brand-substituted productName=${productName} skill=${sourcePath} occurrences=${occurrences}`
406
+ );
407
+ return substituted;
408
+ }
409
+
410
+ // app/lib/paths.ts
411
+ import { homedir } from "os";
412
+ import { resolve as resolve2, join as join2 } from "path";
413
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
414
+ var configDirName = ".maxy";
415
+ var commercialMode = false;
416
+ var vncDisplayNum = 99;
417
+ var rfbPortNum = null;
418
+ var websockifyPortNum = null;
419
+ var cdpPortNum = null;
420
+ var platformRoot = process.env.MAXY_PLATFORM_ROOT;
421
+ if (platformRoot) {
422
+ const brandPath = join2(platformRoot, "config", "brand.json");
423
+ if (existsSync3(brandPath)) {
424
+ try {
425
+ const brand = JSON.parse(readFileSync3(brandPath, "utf-8"));
426
+ if (brand.configDir) configDirName = brand.configDir;
427
+ if (brand.commercialMode === true) commercialMode = true;
428
+ if (typeof brand.vncDisplay === "number") vncDisplayNum = brand.vncDisplay;
429
+ if (typeof brand.rfbPort === "number") rfbPortNum = brand.rfbPort;
430
+ if (typeof brand.websockifyPort === "number") websockifyPortNum = brand.websockifyPort;
431
+ if (typeof brand.cdpPort === "number") cdpPortNum = brand.cdpPort;
432
+ } catch {
433
+ }
434
+ }
435
+ }
436
+ var ordinalOffset = vncDisplayNum - 99;
437
+ var rfbFromBrand = rfbPortNum !== null;
438
+ var websockifyFromBrand = websockifyPortNum !== null;
439
+ var cdpFromBrand = cdpPortNum !== null;
440
+ if (rfbPortNum === null) rfbPortNum = 5900 + ordinalOffset;
441
+ if (websockifyPortNum === null) websockifyPortNum = 6080 + ordinalOffset;
442
+ if (cdpPortNum === null) cdpPortNum = 9222 + ordinalOffset;
443
+ var allFromBrand = rfbFromBrand && websockifyFromBrand && cdpFromBrand;
444
+ var noneFromBrand = !rfbFromBrand && !websockifyFromBrand && !cdpFromBrand;
445
+ var portSource = allFromBrand ? "brand.json" : noneFromBrand ? "derived" : "mixed";
446
+ var MAXY_DIR = resolve2(homedir(), configDirName);
447
+ var BRAND_NAME = configDirName.replace(/^\./, "");
448
+ var COMMERCIAL_MODE = commercialMode;
449
+ var VNC_DISPLAY = `:${vncDisplayNum}`;
450
+ var RFB_PORT = rfbPortNum;
451
+ var WEBSOCKIFY_PORT = websockifyPortNum;
452
+ var CDP_PORT = cdpPortNum;
453
+ console.log(
454
+ `[paths] brand=${configDirName.replace(/^\./, "")} vncDisplay=${vncDisplayNum} rfbPort=${RFB_PORT} websockifyPort=${WEBSOCKIFY_PORT} cdpPort=${CDP_PORT} source=${portSource}`
455
+ );
456
+ var CHROMIUM_PROFILE_DIR = resolve2(homedir(), configDirName, "chromium-profile");
457
+ var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
458
+ var USERS_FILE = resolve2(MAXY_DIR, "users.json");
459
+ var LOG_DIR = resolve2(MAXY_DIR, "logs");
460
+ var BIN_DIR = resolve2(MAXY_DIR, "bin");
461
+ var REMOTE_PASSWORD_FILE = resolve2(MAXY_DIR, ".remote-password");
462
+ var REMOTE_SESSION_SECRET_FILE = resolve2(MAXY_DIR, "credentials", "remote-session-secret");
463
+ var TELEGRAM_WEBHOOK_SECRET_FILE = resolve2(MAXY_DIR, ".telegram-webhook-secret");
464
+ var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve2(MAXY_DIR, ".telegram-admin-webhook-secret");
465
+ var CLAUDE_CREDENTIALS_FILE = resolve2(MAXY_DIR, ".claude", ".credentials.json");
466
+
467
+ // app/lib/claude-agent/account.ts
468
+ var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
469
+ var ACCOUNTS_DIR = resolve3(PLATFORM_ROOT2, "..", "data/accounts");
470
+ if (!existsSync4(PLATFORM_ROOT2)) {
471
+ throw new Error(
472
+ `PLATFORM_ROOT does not exist: ${PLATFORM_ROOT2}
473
+ Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
474
+ );
475
+ }
476
+ function hasStubAccountDir() {
477
+ if (!existsSync4(ACCOUNTS_DIR)) return { stub: false, dirs: [] };
478
+ const stubs = [];
479
+ const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
480
+ for (const entry of entries) {
481
+ if (!entry.isDirectory()) continue;
482
+ if (entry.name.startsWith(".")) continue;
483
+ const configPath = resolve3(ACCOUNTS_DIR, entry.name, "account.json");
484
+ if (!existsSync4(configPath)) stubs.push(entry.name);
485
+ }
486
+ return { stub: stubs.length > 0, dirs: stubs };
487
+ }
488
+ function resolveAccount() {
489
+ if (!existsSync4(ACCOUNTS_DIR)) return null;
490
+ let usersJsonUserId = null;
491
+ if (existsSync4(USERS_FILE)) {
492
+ try {
493
+ const raw = readFileSync4(USERS_FILE, "utf-8").trim();
494
+ if (raw) {
495
+ const users = JSON.parse(raw);
496
+ if (users.length > 0) {
497
+ usersJsonUserId = users[0].userId;
498
+ }
499
+ }
500
+ } catch {
501
+ }
502
+ }
503
+ const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
504
+ let fallback = null;
505
+ for (const entry of entries) {
506
+ if (!entry.isDirectory()) continue;
507
+ const configPath = resolve3(ACCOUNTS_DIR, entry.name, "account.json");
508
+ if (!existsSync4(configPath)) continue;
509
+ const raw = readFileSync4(configPath, "utf-8");
510
+ let config;
511
+ try {
512
+ config = JSON.parse(raw);
513
+ } catch {
514
+ console.error(`[maxy] account.json is corrupt at ${configPath} \u2014 skipping`);
515
+ continue;
516
+ }
517
+ if (!config.adminModel || !config.publicModel) {
518
+ throw new Error(
519
+ `[maxy] account.json at ${configPath} is missing required model fields (adminModel / publicModel). Update account.json with valid model identifiers.`
520
+ );
521
+ }
522
+ const result = {
523
+ accountId: config.accountId,
524
+ accountDir: resolve3(ACCOUNTS_DIR, entry.name),
525
+ config
526
+ };
527
+ if (usersJsonUserId && config.admins?.some((a) => a.userId === usersJsonUserId)) {
528
+ return result;
529
+ }
530
+ if (!fallback) {
531
+ fallback = result;
532
+ }
533
+ }
534
+ if (usersJsonUserId && fallback) {
535
+ console.warn(
536
+ `[maxy] resolveAccount: no account matches users.json userId ${usersJsonUserId} \u2014 falling back to ${fallback.accountId}`
537
+ );
538
+ }
539
+ return fallback;
540
+ }
541
+ function readAgentFile(accountDir, agentName, filename) {
542
+ const filePath = resolve3(accountDir, "agents", agentName, filename);
543
+ if (!existsSync4(filePath)) return null;
544
+ const raw = readFileSync4(filePath, "utf-8");
545
+ if (filename.endsWith(".md")) {
546
+ return substituteBrandPlaceholders(raw, filePath);
547
+ }
548
+ return raw;
549
+ }
550
+ function readIdentity(accountDir, agentName) {
551
+ return readAgentFile(accountDir, agentName, "IDENTITY.md");
552
+ }
553
+ var RESERVED_SLUGS = /* @__PURE__ */ new Set(["admin", "api", "assets", "brand", "bot", "privacy"]);
554
+ var SLUG_PATTERN = /^[a-z][a-z0-9-]{2,49}$/;
555
+ function validateAgentSlug(slug) {
556
+ if (!SLUG_PATTERN.test(slug)) return false;
557
+ if (RESERVED_SLUGS.has(slug)) return false;
558
+ return true;
559
+ }
560
+ function resolveDefaultAgentSlug(accountDir) {
561
+ const configPath = resolve3(accountDir, "account.json");
562
+ if (!existsSync4(configPath)) {
563
+ console.error("[agent-resolve] account.json not found \u2014 cannot resolve defaultAgent");
564
+ return null;
565
+ }
566
+ let config;
567
+ try {
568
+ config = JSON.parse(readFileSync4(configPath, "utf-8"));
569
+ } catch (err) {
570
+ console.error("[agent-resolve] failed to read account.json:", err);
571
+ return null;
572
+ }
573
+ if (!config.defaultAgent) {
574
+ console.error("[agent-resolve] defaultAgent not configured in account.json \u2014 set it via the connect-whatsapp skill");
575
+ return null;
576
+ }
577
+ const agentConfigPath = resolve3(accountDir, "agents", config.defaultAgent, "config.json");
578
+ if (!existsSync4(agentConfigPath)) {
579
+ console.error(`[agent-resolve] defaultAgent="${config.defaultAgent}" has no config.json at ${agentConfigPath}`);
580
+ return null;
581
+ }
582
+ return config.defaultAgent;
583
+ }
584
+ function estimateTokens(text) {
585
+ return Math.ceil(text.length / 4);
586
+ }
587
+ function resolveAgentConfig(accountDir, agentName) {
588
+ let model = null;
589
+ let plugins = null;
590
+ let status = null;
591
+ let displayName = null;
592
+ let image = null;
593
+ let imageShape = null;
594
+ let showAgentName = false;
595
+ let liveMemory = false;
596
+ let knowledgeKeywords = null;
597
+ let accessMode = "open";
598
+ const MAX_KNOWLEDGE_KEYWORDS = 5;
599
+ const configRaw = readAgentFile(accountDir, agentName, "config.json");
600
+ if (configRaw) {
601
+ let parsed;
602
+ try {
603
+ parsed = JSON.parse(configRaw);
604
+ } catch {
605
+ console.warn(`[agent-config] ${agentName}/config.json: invalid JSON \u2014 using defaults`);
606
+ parsed = {};
607
+ }
608
+ model = typeof parsed.model === "string" ? parsed.model : null;
609
+ plugins = Array.isArray(parsed.plugins) ? parsed.plugins : null;
610
+ status = typeof parsed.status === "string" ? parsed.status : null;
611
+ displayName = typeof parsed.displayName === "string" ? parsed.displayName : null;
612
+ image = typeof parsed.image === "string" ? parsed.image : null;
613
+ if (typeof parsed.imageShape === "string" && ["circle", "rounded"].includes(parsed.imageShape)) {
614
+ imageShape = parsed.imageShape;
615
+ }
616
+ if (parsed.showAgentName === true) {
617
+ showAgentName = true;
618
+ } else if (parsed.showAgentName === "none") {
619
+ showAgentName = "none";
620
+ }
621
+ if (image || imageShape || showAgentName) {
622
+ console.log(`[agent-config] ${agentName}: image=${image || "(none)"} imageShape=${imageShape || "(none)"} showAgentName=${showAgentName}`);
623
+ }
624
+ if (typeof parsed.accessMode === "string" && ["gated", "paid"].includes(parsed.accessMode)) {
625
+ accessMode = parsed.accessMode;
626
+ }
627
+ if (typeof parsed.liveMemory === "boolean") {
628
+ liveMemory = parsed.liveMemory;
629
+ } else if (typeof parsed.liveMemory === "string") {
630
+ const lower = parsed.liveMemory.toLowerCase();
631
+ if (lower === "true") {
632
+ liveMemory = true;
633
+ console.warn(`[agent-config] ${agentName}: liveMemory is string "true" \u2014 coercing to boolean. Fix the config to use a boolean value.`);
634
+ } else if (lower === "false") {
635
+ liveMemory = false;
636
+ console.warn(`[agent-config] ${agentName}: liveMemory is string "false" \u2014 coercing to boolean. Fix the config to use a boolean value.`);
637
+ } else {
638
+ throw new Error(`[agent-config] ${agentName}: liveMemory has invalid string value "${parsed.liveMemory}" \u2014 expected boolean or "true"/"false"`);
639
+ }
640
+ } else if (parsed.liveMemory !== void 0 && parsed.liveMemory !== null) {
641
+ throw new Error(`[agent-config] ${agentName}: liveMemory has invalid type ${typeof parsed.liveMemory} \u2014 expected boolean or "true"/"false"`);
642
+ }
643
+ if (Array.isArray(parsed.knowledgeKeywords) && parsed.knowledgeKeywords.length > 0) {
644
+ const filtered = parsed.knowledgeKeywords.filter((k) => typeof k === "string" && k.trim()).map((k) => k.replace(/,/g, "").trim().toLowerCase()).filter(Boolean);
645
+ if (filtered.length > MAX_KNOWLEDGE_KEYWORDS) {
646
+ console.warn(`[agent-config] ${agentName}: knowledgeKeywords has ${filtered.length} entries \u2014 capping at ${MAX_KNOWLEDGE_KEYWORDS}`);
647
+ }
648
+ knowledgeKeywords = filtered.length > 0 ? filtered.slice(0, MAX_KNOWLEDGE_KEYWORDS) : null;
649
+ }
650
+ }
651
+ let knowledge = null;
652
+ let knowledgeBaked = false;
653
+ const agentDir = resolve3(accountDir, "agents", agentName);
654
+ const knowledgePath = resolve3(agentDir, "KNOWLEDGE.md");
655
+ const summaryPath = resolve3(agentDir, "KNOWLEDGE-SUMMARY.md");
656
+ const hasKnowledge = existsSync4(knowledgePath);
657
+ const hasSummary = existsSync4(summaryPath);
658
+ if (hasKnowledge && hasSummary) {
659
+ const knowledgeMtime = statSync2(knowledgePath).mtimeMs;
660
+ const summaryMtime = statSync2(summaryPath).mtimeMs;
661
+ if (summaryMtime >= knowledgeMtime) {
662
+ knowledge = readFileSync4(summaryPath, "utf-8");
663
+ } else {
664
+ console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
665
+ knowledge = readFileSync4(knowledgePath, "utf-8");
666
+ }
667
+ knowledgeBaked = true;
668
+ } else if (hasKnowledge) {
669
+ knowledge = readFileSync4(knowledgePath, "utf-8");
670
+ knowledgeBaked = true;
671
+ }
672
+ let budget = null;
673
+ const identityRaw = readAgentFile(accountDir, agentName, "IDENTITY.md");
674
+ const soulRaw = readAgentFile(accountDir, agentName, "SOUL.md");
675
+ if (identityRaw || soulRaw || knowledge) {
676
+ const identityTokens = identityRaw ? estimateTokens(identityRaw) : 0;
677
+ const soulTokens = soulRaw ? estimateTokens(soulRaw) : 0;
678
+ const knowledgeTokens = knowledge ? estimateTokens(knowledge) : 0;
679
+ budget = {
680
+ identity: identityTokens,
681
+ soul: soulTokens,
682
+ knowledge: knowledgeTokens,
683
+ plugins: 0,
684
+ total: identityTokens + soulTokens + knowledgeTokens
685
+ };
686
+ }
687
+ return { model, plugins, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, liveMemory, knowledgeKeywords, budget, accessMode };
688
+ }
689
+ function getDefaultAccountId() {
690
+ return resolveAccount()?.accountId ?? null;
691
+ }
692
+ function resolveUserAccounts(userId) {
693
+ if (!existsSync4(ACCOUNTS_DIR)) return [];
694
+ const results = [];
695
+ const entries = readdirSync2(ACCOUNTS_DIR, { withFileTypes: true });
696
+ for (const entry of entries) {
697
+ if (!entry.isDirectory()) continue;
698
+ const configPath = resolve3(ACCOUNTS_DIR, entry.name, "account.json");
699
+ if (!existsSync4(configPath)) continue;
700
+ let config;
701
+ try {
702
+ config = JSON.parse(readFileSync4(configPath, "utf-8"));
703
+ } catch {
704
+ console.error(`[session] account.json corrupt at ${configPath} \u2014 skipping`);
705
+ continue;
706
+ }
707
+ const adminEntry = config.admins?.find((a) => a.userId === userId);
708
+ if (adminEntry) {
709
+ results.push({
710
+ accountId: config.accountId,
711
+ accountDir: resolve3(ACCOUNTS_DIR, entry.name),
712
+ config,
713
+ role: adminEntry.role
714
+ });
715
+ }
716
+ }
717
+ return results;
718
+ }
719
+
720
+ // app/lib/claude-agent/session-store.ts
721
+ function findFirstSubstantiveUserMessage(turns) {
722
+ for (const t of turns) {
723
+ if (t.role !== "user") continue;
724
+ if (isMessageUseful(t.content)) return t.content;
725
+ }
726
+ return null;
727
+ }
728
+ var sessionStore = /* @__PURE__ */ new Map();
729
+ function getSession(sessionKey) {
730
+ return sessionStore.get(sessionKey);
731
+ }
732
+ setSessionStoreRef(sessionStore);
733
+ function registerSession(sessionKey, agentType, accountId, agentName, userId, userName, role) {
734
+ const existing = sessionStore.get(sessionKey);
735
+ if (existing) {
736
+ existing.agentType = agentType;
737
+ existing.accountId = accountId;
738
+ existing.agentName = agentName ?? existing.agentName;
739
+ existing.userId = userId ?? existing.userId;
740
+ existing.userName = userName ?? existing.userName;
741
+ existing.role = role ?? existing.role;
742
+ return;
743
+ }
744
+ sessionStore.set(sessionKey, { createdAt: Date.now(), agentType, accountId, agentName, userId, userName, role });
745
+ }
746
+ function registerResumedSession(sessionKey, accountId, agentName, conversationId, messages) {
747
+ const messageHistory = messages.map((m) => ({
748
+ role: m.role,
749
+ content: m.content,
750
+ timestamp: m.timestamp ?? Date.now()
751
+ }));
752
+ sessionStore.set(sessionKey, {
753
+ createdAt: Date.now(),
754
+ agentType: "public",
755
+ accountId,
756
+ agentName,
757
+ conversationId,
758
+ messageHistory
759
+ });
760
+ }
761
+ function getSessionMessages(sessionKey) {
762
+ return sessionStore.get(sessionKey)?.messageHistory;
763
+ }
764
+ function clearSessionHistory(sessionKey) {
765
+ const session = sessionStore.get(sessionKey);
766
+ if (!session) return void 0;
767
+ const previousConversationId = session.conversationId;
768
+ session.agentSessionId = void 0;
769
+ session.pendingCompactionSummary = void 0;
770
+ session.stalledSubagents = void 0;
771
+ session.pendingTrimmedMessages = void 0;
772
+ session.pendingCommitmentOffers = void 0;
773
+ session.pendingTurns = void 0;
774
+ session.stallResume = void 0;
775
+ session.assistantTurnCount = void 0;
776
+ return previousConversationId;
777
+ }
778
+ function bufferPendingTurn(sessionKey, turn) {
779
+ const session = sessionStore.get(sessionKey);
780
+ if (!session) {
781
+ console.error(`[conversation-gate] bufferPendingTurn: session not found sessionKey=${sessionKey.slice(0, 8)}\u2026`);
782
+ return;
783
+ }
784
+ if (!session.pendingTurns) session.pendingTurns = [];
785
+ session.pendingTurns.push(turn);
786
+ console.log(`[conversation-gate] ${(/* @__PURE__ */ new Date()).toISOString()} buffered sessionKey=${sessionKey.slice(0, 8)} role=${turn.role} turnCount=${session.pendingTurns.filter((t) => t.role === "user").length}`);
787
+ }
788
+ function getPendingTurnCount(sessionKey) {
789
+ const buf = sessionStore.get(sessionKey)?.pendingTurns;
790
+ if (!buf) return 0;
791
+ let n = 0;
792
+ for (const t of buf) if (t.role === "user") n++;
793
+ return n;
794
+ }
795
+ function drainPendingTurns(sessionKey) {
796
+ const session = sessionStore.get(sessionKey);
797
+ if (!session?.pendingTurns || session.pendingTurns.length === 0) return void 0;
798
+ const drained = session.pendingTurns;
799
+ session.pendingTurns = void 0;
800
+ return drained;
801
+ }
802
+ function isFlushError(o) {
803
+ return o !== null && "error" in o;
804
+ }
805
+ async function maybeFlushConversationBuffer(sessionKey, agentType, accountId) {
806
+ const sk8 = sessionKey.slice(0, 8);
807
+ const session = sessionStore.get(sessionKey);
808
+ if (!session) {
809
+ console.log(`[admin/conversation-flush] sessionKey=${sk8} agentType=${agentType} result=missing-session`);
810
+ return { error: "missing-session" };
811
+ }
812
+ if (session.conversationId) {
813
+ console.log(`[admin/conversation-flush] sessionKey=${sk8} agentType=${agentType} result=already-flushed conversationId=${session.conversationId.slice(0, 8)}`);
814
+ return { conversationId: session.conversationId, buffered: [] };
815
+ }
816
+ const bufferedCount = session.pendingTurns?.length ?? 0;
817
+ if (bufferedCount === 0) {
818
+ console.log(`[admin/conversation-flush] sessionKey=${sk8} agentType=${agentType} result=empty-buffer`);
819
+ return null;
820
+ }
821
+ if (session.flushInFlight) return session.flushInFlight;
822
+ const attempt = (async () => {
823
+ let conversationId = null;
824
+ if (agentType === "admin") {
825
+ const userId = session.userId;
826
+ if (!userId) {
827
+ console.error(`[admin/conversation-flush] sessionKey=${sk8} agentType=admin result=missing-userId bufferedCount=${bufferedCount}`);
828
+ return { error: "missing-userId" };
829
+ }
830
+ conversationId = await createNewAdminConversation(userId, accountId, sessionKey, session.userName);
831
+ } else {
832
+ conversationId = (await ensureConversation(accountId, "public", sessionKey, void 0, session.agentName, void 0)).conversationId;
833
+ }
834
+ if (!conversationId) {
835
+ console.error(`[admin/conversation-flush] sessionKey=${sk8} agentType=${agentType} result=writer-failed bufferedCount=${bufferedCount}`);
836
+ return { error: "writer-failed" };
837
+ }
838
+ session.conversationId = conversationId;
839
+ if (agentType === "admin" && session.agentSessionId) {
840
+ setConversationAgentSessionId(conversationId, session.agentSessionId).catch(() => {
841
+ });
842
+ }
843
+ renameStreamLogsOnFlush(resolve4(ACCOUNTS_DIR, accountId), sessionKey, conversationId);
844
+ const buffered = drainPendingTurns(sessionKey) ?? [];
845
+ for (const turn of buffered) {
846
+ persistMessage(conversationId, turn.role, turn.content, accountId, turn.tokens, turn.timestamp, turn.sender, turn.components, turn.attachments).catch((err) => {
847
+ console.error(`[admin/conversation-flush] replay persistMessage failed role=${turn.role}: ${err instanceof Error ? err.message : String(err)}`);
848
+ });
849
+ }
850
+ console.log(`[admin/conversation-flush] sessionKey=${sk8} agentType=${agentType} result=ok conversationId=${conversationId.slice(0, 8)} bufferedMessages=${buffered.length}`);
851
+ return { conversationId, buffered };
852
+ })();
853
+ session.flushInFlight = attempt;
854
+ try {
855
+ return await attempt;
856
+ } finally {
857
+ if (session.flushInFlight === attempt) session.flushInFlight = void 0;
858
+ }
859
+ }
860
+ function isDmChannelSessionKey(sessionKey) {
861
+ return sessionKey.startsWith("whatsapp:") || sessionKey.startsWith("telegram:");
862
+ }
863
+ function getAgentNameForSession(sessionKey) {
864
+ return sessionStore.get(sessionKey)?.agentName;
865
+ }
866
+ function listAdminSessionsInProgress(accountId, userId) {
867
+ const rows = [];
868
+ for (const [sessionKey, session] of Array.from(sessionStore.entries())) {
869
+ if (session.agentType !== "admin") continue;
870
+ if (session.accountId !== accountId) continue;
871
+ if (session.userId !== userId) continue;
872
+ if (session.conversationId) continue;
873
+ rows.push({ sessionKey, createdAt: session.createdAt });
874
+ }
875
+ rows.sort((a, b) => b.createdAt - a.createdAt);
876
+ return rows;
877
+ }
878
+ function validateSession(sessionKey, agentType) {
879
+ const session = sessionStore.get(sessionKey);
880
+ if (!session) return { ok: false, reason: "session-not-registered" };
881
+ if (session.agentType !== agentType) return { ok: false, reason: "agent-type-mismatch" };
882
+ if (Date.now() - session.createdAt > 24 * 60 * 60 * 1e3) {
883
+ sessionStore.delete(sessionKey);
884
+ return { ok: false, reason: "session-expired-age" };
885
+ }
886
+ if (session.grantExpiresAt && Date.now() > session.grantExpiresAt) {
887
+ sessionStore.delete(sessionKey);
888
+ return { ok: false, reason: "grant-expired" };
889
+ }
890
+ return { ok: true };
891
+ }
892
+ function storeAgentSessionId(sessionKey, agentSessionId) {
893
+ const session = sessionStore.get(sessionKey);
894
+ if (session) {
895
+ session.agentSessionId = agentSessionId;
896
+ console.error(`[session-store] storeAgentSessionId sessionKey=${sessionKey.slice(0, 12)}\u2026 sessionId=${agentSessionId.slice(0, 8)}\u2026`);
897
+ if (session.agentType === "admin" && session.conversationId) {
898
+ setConversationAgentSessionId(session.conversationId, agentSessionId).catch(() => {
899
+ });
900
+ }
901
+ } else {
902
+ console.error(`[session-store] storeAgentSessionId SKIPPED \u2014 no session entry sessionKey=${sessionKey.slice(0, 12)}\u2026 sessionId=${agentSessionId.slice(0, 8)}\u2026`);
903
+ }
904
+ }
905
+ function getAgentSessionId(sessionKey) {
906
+ return sessionStore.get(sessionKey)?.agentSessionId;
907
+ }
908
+ function setAgentSessionId(sessionKey, agentSessionId) {
909
+ const session = sessionStore.get(sessionKey);
910
+ if (!session) return false;
911
+ session.agentSessionId = agentSessionId;
912
+ return true;
913
+ }
914
+ function storePendingCompactionSummary(sessionKey, summary) {
915
+ const session = sessionStore.get(sessionKey);
916
+ if (session) session.pendingCompactionSummary = summary;
917
+ }
918
+ function consumePendingCompactionSummary(sessionKey) {
919
+ const session = sessionStore.get(sessionKey);
920
+ if (!session) return void 0;
921
+ const summary = session.pendingCompactionSummary;
922
+ delete session.pendingCompactionSummary;
923
+ return summary;
924
+ }
925
+ function getAccountIdForSession(sessionKey) {
926
+ return sessionStore.get(sessionKey)?.accountId;
927
+ }
928
+ function getUserIdForSession(sessionKey) {
929
+ return sessionStore.get(sessionKey)?.userId;
930
+ }
931
+ function getUserNameForSession(sessionKey) {
932
+ return sessionStore.get(sessionKey)?.userName;
933
+ }
934
+ function getRoleForSession(sessionKey) {
935
+ return sessionStore.get(sessionKey)?.role;
936
+ }
937
+ function getConversationIdForSession(sessionKey) {
938
+ return sessionStore.get(sessionKey)?.conversationId;
939
+ }
940
+ function setConversationIdForSession(sessionKey, conversationId) {
941
+ const session = sessionStore.get(sessionKey);
942
+ if (!session) return false;
943
+ session.conversationId = conversationId;
944
+ return true;
945
+ }
946
+ function unregisterSession(sessionKey) {
947
+ return sessionStore.delete(sessionKey);
948
+ }
949
+ function getGroupSlugForSession(sessionKey) {
950
+ return sessionStore.get(sessionKey)?.groupSlug;
951
+ }
952
+ function getVisitorIdForSession(sessionKey) {
953
+ return sessionStore.get(sessionKey)?.visitorId;
954
+ }
955
+ function setGroupContextForSession(sessionKey, context) {
956
+ const session = sessionStore.get(sessionKey);
957
+ if (!session) return false;
958
+ session.groupSlug = context.groupSlug;
959
+ session.groupName = context.groupName;
960
+ session.conversationId = context.conversationId;
961
+ session.visitorId = context.visitorId;
962
+ session.senderDisplayName = context.senderDisplayName;
963
+ return true;
964
+ }
965
+ function registerGrantSession(sessionKey, accountId, agentName, opts) {
966
+ sessionStore.set(sessionKey, {
967
+ createdAt: Date.now(),
968
+ agentType: "public",
969
+ accountId,
970
+ agentName,
971
+ grantId: opts.grantId,
972
+ grantExpiresAt: opts.grantExpiresAt,
973
+ grantStatus: opts.grantStatus,
974
+ grantDisplayName: opts.grantDisplayName,
975
+ grantContactValue: opts.grantContactValue,
976
+ setupRequired: opts.setupRequired
977
+ });
978
+ }
979
+ function getGrantForSession(sessionKey) {
980
+ const session = sessionStore.get(sessionKey);
981
+ if (!session?.grantId) return void 0;
982
+ return {
983
+ grantId: session.grantId,
984
+ grantExpiresAt: session.grantExpiresAt,
985
+ grantStatus: session.grantStatus,
986
+ grantDisplayName: session.grantDisplayName,
987
+ grantContactValue: session.grantContactValue,
988
+ setupRequired: session.setupRequired
989
+ };
990
+ }
991
+ function completeGrantSetup(sessionKey) {
992
+ const session = sessionStore.get(sessionKey);
993
+ if (session) {
994
+ session.setupRequired = false;
995
+ session.grantStatus = "active";
996
+ }
997
+ }
998
+ function getMessageHistory(sessionKey) {
999
+ const session = sessionStore.get(sessionKey);
1000
+ if (!session) return [];
1001
+ if (!session.messageHistory) session.messageHistory = [];
1002
+ return session.messageHistory;
1003
+ }
1004
+ function appendMessage(sessionKey, role, content, timestamp) {
1005
+ if (!content) return;
1006
+ const session = sessionStore.get(sessionKey);
1007
+ if (!session) {
1008
+ console.error(`[managed] appendMessage: session not found for key ${sessionKey.slice(0, 8)}\u2026 (store size: ${sessionStore.size})`);
1009
+ return;
1010
+ }
1011
+ if (!session.messageHistory) session.messageHistory = [];
1012
+ session.messageHistory.push({ role, content, timestamp: timestamp ?? Date.now() });
1013
+ }
1014
+ function storePendingTrimmedMessages(sessionKey, messages) {
1015
+ const session = sessionStore.get(sessionKey);
1016
+ if (session) session.pendingTrimmedMessages = messages;
1017
+ }
1018
+ function consumePendingTrimmedMessages(sessionKey) {
1019
+ const session = sessionStore.get(sessionKey);
1020
+ if (!session) return void 0;
1021
+ const messages = session.pendingTrimmedMessages;
1022
+ delete session.pendingTrimmedMessages;
1023
+ return messages;
1024
+ }
1025
+ function storeStalledSubagent(sessionKey, info) {
1026
+ const session = sessionStore.get(sessionKey);
1027
+ if (!session) {
1028
+ console.error(`[stall-recovery] storeStalledSubagent: session not found for key ${sessionKey.slice(0, 8)}\u2026 \u2014 stall context lost`);
1029
+ return;
1030
+ }
1031
+ if (!session.stalledSubagents) session.stalledSubagents = [];
1032
+ session.stalledSubagents.push(info);
1033
+ }
1034
+ function consumeStalledSubagents(sessionKey) {
1035
+ const session = sessionStore.get(sessionKey);
1036
+ if (!session) return void 0;
1037
+ const stalls = session.stalledSubagents;
1038
+ delete session.stalledSubagents;
1039
+ return stalls && stalls.length > 0 ? stalls : void 0;
1040
+ }
1041
+ function setPendingCommitmentOffers(sessionKey, offers) {
1042
+ const session = sessionStore.get(sessionKey);
1043
+ if (session) session.pendingCommitmentOffers = offers;
1044
+ }
1045
+ function getAgentTypeForSession(sessionKey) {
1046
+ return sessionStore.get(sessionKey)?.agentType;
1047
+ }
1048
+ function clearMessageHistory(sessionKey) {
1049
+ const session = sessionStore.get(sessionKey);
1050
+ if (session) session.messageHistory = [];
1051
+ }
1052
+ var clearAgentSessionIdHandlers = [];
1053
+ function onClearAgentSessionId(handler) {
1054
+ clearAgentSessionIdHandlers.push(handler);
1055
+ }
1056
+ var evictPoolHandlers = [];
1057
+ function onEvictPool(handler) {
1058
+ evictPoolHandlers.push(handler);
1059
+ }
1060
+ function clearAgentSessionId(sessionKey, reason) {
1061
+ const session = sessionStore.get(sessionKey);
1062
+ if (session) {
1063
+ session.agentSessionId = void 0;
1064
+ session.lastClearReason = reason;
1065
+ console.error(`[session-store] clearAgentSessionId sessionKey=${sessionKey.slice(0, 12)}\u2026 reason=${reason}`);
1066
+ } else {
1067
+ console.error(`[session-store] clearAgentSessionId SKIPPED \u2014 no session entry sessionKey=${sessionKey.slice(0, 12)}\u2026 reason=${reason}`);
1068
+ }
1069
+ for (const handler of clearAgentSessionIdHandlers) {
1070
+ try {
1071
+ handler(sessionKey, reason);
1072
+ } catch (err) {
1073
+ console.error(`[session-store] clearAgentSessionId handler threw: ${err instanceof Error ? err.message : String(err)}`);
1074
+ }
1075
+ }
1076
+ }
1077
+ function evictPool(sessionKey, reason) {
1078
+ for (const handler of evictPoolHandlers) {
1079
+ try {
1080
+ handler(sessionKey, reason);
1081
+ } catch (err) {
1082
+ console.error(`[session-store] evictPool handler threw: ${err instanceof Error ? err.message : String(err)}`);
1083
+ }
1084
+ }
1085
+ }
1086
+ function storeRecoveryHandoff(sessionKey, info) {
1087
+ const session = sessionStore.get(sessionKey);
1088
+ if (!session) {
1089
+ console.error(`[recovery-handoff] storeRecoveryHandoff: session not found for key ${sessionKey.slice(0, 8)}\u2026 \u2014 handoff context lost reason=${info.reason}`);
1090
+ return;
1091
+ }
1092
+ session.recoveryHandoff = { reason: info.reason, summary: info.summary, createdAt: Date.now() };
1093
+ }
1094
+ function consumeRecoveryHandoff(sessionKey) {
1095
+ const session = sessionStore.get(sessionKey);
1096
+ if (!session?.recoveryHandoff) return void 0;
1097
+ const handoff = session.recoveryHandoff;
1098
+ delete session.recoveryHandoff;
1099
+ delete session.lastClearReason;
1100
+ return handoff;
1101
+ }
1102
+ function getLastClearReason(sessionKey) {
1103
+ return sessionStore.get(sessionKey)?.lastClearReason;
1104
+ }
1105
+ function clearLastClearReason(sessionKey) {
1106
+ const session = sessionStore.get(sessionKey);
1107
+ if (session) delete session.lastClearReason;
1108
+ }
1109
+ function storeStallResume(sessionKey, info) {
1110
+ const session = sessionStore.get(sessionKey);
1111
+ if (!session) {
1112
+ console.error(`[stall-resume] storeStallResume: session not found for key ${sessionKey.slice(0, 8)}\u2026 \u2014 resume context lost kind=${info.kind}`);
1113
+ return;
1114
+ }
1115
+ session.stallResume = { ...info, createdAt: Date.now() };
1116
+ }
1117
+ function consumeStallResume(sessionKey) {
1118
+ const session = sessionStore.get(sessionKey);
1119
+ if (!session?.stallResume) return void 0;
1120
+ const payload = session.stallResume;
1121
+ delete session.stallResume;
1122
+ return payload;
1123
+ }
1124
+
1125
+ // app/lib/claude-agent/client-pool.ts
1126
+ var AsyncQueue = class {
1127
+ buffer = [];
1128
+ resolvers = [];
1129
+ closed = false;
1130
+ push(item) {
1131
+ if (this.closed) return;
1132
+ const resolver = this.resolvers.shift();
1133
+ if (resolver) resolver({ value: item, done: false });
1134
+ else this.buffer.push(item);
1135
+ }
1136
+ close() {
1137
+ if (this.closed) return;
1138
+ this.closed = true;
1139
+ while (this.resolvers.length > 0) {
1140
+ const r = this.resolvers.shift();
1141
+ r({ value: void 0, done: true });
1142
+ }
1143
+ }
1144
+ [Symbol.asyncIterator]() {
1145
+ return {
1146
+ next: () => new Promise((resolve5) => {
1147
+ if (this.buffer.length > 0) {
1148
+ resolve5({ value: this.buffer.shift(), done: false });
1149
+ } else if (this.closed) {
1150
+ resolve5({ value: void 0, done: true });
1151
+ } else {
1152
+ this.resolvers.push(resolve5);
1153
+ }
1154
+ }),
1155
+ return: async () => {
1156
+ this.close();
1157
+ return { value: void 0, done: true };
1158
+ }
1159
+ };
1160
+ }
1161
+ };
1162
+ var clientPool = /* @__PURE__ */ new Map();
1163
+ var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
1164
+ var idleEvictMs = (() => {
1165
+ const v = Number(process.env.CLAUDE_CLIENT_IDLE_MS);
1166
+ return Number.isFinite(v) && v > 0 ? v : DEFAULT_IDLE_MS;
1167
+ })();
1168
+ var ABORT_SDK_TIMEOUT_MS = 2e3;
1169
+ function acquireClient(sessionKey, opts, streamLog) {
1170
+ const existing = clientPool.get(sessionKey);
1171
+ if (existing) {
1172
+ existing.lastUsedAt = Date.now();
1173
+ existing.turnsServed += 1;
1174
+ safeWrite(
1175
+ streamLog,
1176
+ `[${isoTs()}] [client-warm-reuse] sessionKey=${sk(sessionKey)} ageMs=${Date.now() - existing.createdAt} turnsServed=${existing.turnsServed} cachedTokens=${existing.cachedTokensLastTurn}
1177
+ `
1178
+ );
1179
+ return { entry: existing, isCold: false };
1180
+ }
1181
+ const userQueue = new AsyncQueue();
1182
+ const abortController = new AbortController();
1183
+ let q;
1184
+ try {
1185
+ const sdkOptions = opts.buildSdkOptions();
1186
+ q = query({ prompt: userQueue, options: { ...sdkOptions, abortController } });
1187
+ } catch (err) {
1188
+ const reason = err instanceof Error ? err.message : String(err);
1189
+ safeWrite(
1190
+ streamLog,
1191
+ `[${isoTs()}] [client-spawn-error] sessionKey=${sk(sessionKey)} reason=${JSON.stringify(reason.slice(0, 200))}
1192
+ `
1193
+ );
1194
+ throw err;
1195
+ }
1196
+ const entry = {
1197
+ query: q,
1198
+ userQueue,
1199
+ abortController,
1200
+ inflight: null,
1201
+ createdAt: Date.now(),
1202
+ lastUsedAt: Date.now(),
1203
+ turnsServed: 1,
1204
+ resumedFromSessionId: opts.resumedFromSessionId,
1205
+ cachedTokensLastTurn: -1,
1206
+ accountId: opts.accountId,
1207
+ accountDir: opts.accountDir,
1208
+ logKey: opts.logKey
1209
+ };
1210
+ clientPool.set(sessionKey, entry);
1211
+ safeWrite(
1212
+ streamLog,
1213
+ `[${isoTs()}] [client-cold-create] sessionKey=${sk(sessionKey)} resumedFrom=${opts.resumedFromSessionId ?? "none"} createdAtMs=${entry.createdAt}
1214
+ `
1215
+ );
1216
+ return { entry, isCold: true };
1217
+ }
1218
+ function pushUserMessage(entry, content) {
1219
+ const msg = {
1220
+ type: "user",
1221
+ message: { role: "user", content },
1222
+ parent_tool_use_id: null
1223
+ };
1224
+ entry.userQueue.push(msg);
1225
+ }
1226
+ function pushUserToolResult(entry, toolUseId, content, isError = false) {
1227
+ const msg = {
1228
+ type: "user",
1229
+ message: {
1230
+ role: "user",
1231
+ content: [{ type: "tool_result", tool_use_id: toolUseId, content, is_error: isError }]
1232
+ },
1233
+ parent_tool_use_id: null
1234
+ };
1235
+ entry.userQueue.push(msg);
1236
+ }
1237
+ function setInflight(entry, promise) {
1238
+ entry.inflight = promise;
1239
+ promise.finally(() => {
1240
+ if (entry.inflight === promise) entry.inflight = null;
1241
+ }).catch(() => {
1242
+ });
1243
+ }
1244
+ function recordCachedTokens(entry, cachedTokens) {
1245
+ entry.cachedTokensLastTurn = cachedTokens;
1246
+ }
1247
+ function getActiveClient(sessionKey) {
1248
+ return clientPool.get(sessionKey);
1249
+ }
1250
+ function appendAbortLine(entry, line) {
1251
+ const path = resolvePath(entry.accountDir, "logs", `claude-agent-stream-${entry.logKey}.log`);
1252
+ try {
1253
+ appendFileSync2(path, line);
1254
+ } catch {
1255
+ }
1256
+ console.error(line.trimEnd());
1257
+ }
1258
+ async function interruptClient(sessionKey, _streamLog) {
1259
+ void _streamLog;
1260
+ const entry = clientPool.get(sessionKey);
1261
+ if (!entry) return;
1262
+ const startedAt = Date.now();
1263
+ let resolved = false;
1264
+ const timeout = new Promise(
1265
+ (resolve5) => setTimeout(() => resolve5("timeout"), ABORT_SDK_TIMEOUT_MS)
1266
+ );
1267
+ const sdkAbort = entry.query.interrupt().then(() => {
1268
+ resolved = true;
1269
+ return "ok";
1270
+ }).catch((err) => {
1271
+ resolved = true;
1272
+ return { error: err instanceof Error ? err.message : String(err) };
1273
+ });
1274
+ const result = await Promise.race([sdkAbort, timeout]);
1275
+ const durationMs = Date.now() - startedAt;
1276
+ if (result === "timeout" && !resolved) {
1277
+ appendAbortLine(
1278
+ entry,
1279
+ `[${isoTs()}] [client-abort] sessionKey=${sk(sessionKey)} method=signal durationMs=${durationMs}
1280
+ `
1281
+ );
1282
+ evictClient(sessionKey, "abort-signal-fallback", null);
1283
+ return;
1284
+ }
1285
+ if (typeof result === "object" && "error" in result) {
1286
+ appendAbortLine(
1287
+ entry,
1288
+ `[${isoTs()}] [client-abort] sessionKey=${sk(sessionKey)} method=sdk durationMs=${durationMs} error=${JSON.stringify(result.error.slice(0, 120))}
1289
+ `
1290
+ );
1291
+ evictClient(sessionKey, "abort-error", null);
1292
+ return;
1293
+ }
1294
+ appendAbortLine(
1295
+ entry,
1296
+ `[${isoTs()}] [client-abort] sessionKey=${sk(sessionKey)} method=sdk durationMs=${durationMs}
1297
+ `
1298
+ );
1299
+ }
1300
+ function evictClient(sessionKey, reason, streamLog) {
1301
+ const entry = clientPool.get(sessionKey);
1302
+ if (!entry) return false;
1303
+ const ageMs = Date.now() - entry.createdAt;
1304
+ clientPool.delete(sessionKey);
1305
+ try {
1306
+ entry.userQueue.close();
1307
+ } catch {
1308
+ }
1309
+ try {
1310
+ entry.query.close();
1311
+ } catch {
1312
+ }
1313
+ const line = `[${isoTs()}] [client-evict] reason=${reason} sessionKey=${sk(sessionKey)} ageMs=${ageMs} turnsServed=${entry.turnsServed}
1314
+ `;
1315
+ if (streamLog) {
1316
+ safeWrite(streamLog, line);
1317
+ } else {
1318
+ appendAbortLine(entry, line);
1319
+ }
1320
+ return true;
1321
+ }
1322
+ function acquireOneShotClient(sessionKey, opts, streamLog) {
1323
+ const userQueue = new AsyncQueue();
1324
+ const abortController = new AbortController();
1325
+ let q;
1326
+ try {
1327
+ const sdkOptions = opts.buildSdkOptions();
1328
+ q = query({ prompt: userQueue, options: { ...sdkOptions, abortController } });
1329
+ } catch (err) {
1330
+ const reason = err instanceof Error ? err.message : String(err);
1331
+ safeWrite(
1332
+ streamLog,
1333
+ `[${isoTs()}] [client-spawn-error] sessionKey=${sk(sessionKey)} site=compaction-one-shot reason=${JSON.stringify(reason.slice(0, 200))}
1334
+ `
1335
+ );
1336
+ throw err;
1337
+ }
1338
+ const entry = {
1339
+ query: q,
1340
+ userQueue,
1341
+ abortController,
1342
+ inflight: null,
1343
+ createdAt: Date.now(),
1344
+ lastUsedAt: Date.now(),
1345
+ turnsServed: 1,
1346
+ resumedFromSessionId: opts.resumedFromSessionId,
1347
+ cachedTokensLastTurn: -1,
1348
+ accountId: opts.accountId,
1349
+ accountDir: opts.accountDir,
1350
+ logKey: opts.logKey
1351
+ };
1352
+ safeWrite(
1353
+ streamLog,
1354
+ `[${isoTs()}] [client-cold-create] reason=compaction-one-shot sessionKey=${sk(sessionKey)} createdAtMs=${entry.createdAt}
1355
+ `
1356
+ );
1357
+ let evicted = false;
1358
+ const evict = (reason) => {
1359
+ if (evicted) return;
1360
+ evicted = true;
1361
+ const ageMs = Date.now() - entry.createdAt;
1362
+ try {
1363
+ entry.userQueue.close();
1364
+ } catch {
1365
+ }
1366
+ try {
1367
+ entry.query.close();
1368
+ } catch {
1369
+ }
1370
+ safeWrite(
1371
+ streamLog,
1372
+ `[${isoTs()}] [client-evict] reason=${reason} sessionKey=${sk(sessionKey)} ageMs=${ageMs}
1373
+ `
1374
+ );
1375
+ };
1376
+ return { entry, evict };
1377
+ }
1378
+ function recordCrash(sessionKey, reason, streamLog) {
1379
+ const entry = clientPool.get(sessionKey);
1380
+ if (!entry) return;
1381
+ const tail = JSON.stringify(reason.slice(-512));
1382
+ clientPool.delete(sessionKey);
1383
+ try {
1384
+ entry.userQueue.close();
1385
+ } catch {
1386
+ }
1387
+ try {
1388
+ entry.query.close();
1389
+ } catch {
1390
+ }
1391
+ safeWrite(
1392
+ streamLog,
1393
+ `[${isoTs()}] [client-crash] sessionKey=${sk(sessionKey)} reason=${JSON.stringify(reason.slice(0, 80))} tail=${tail}
1394
+ `
1395
+ );
1396
+ }
1397
+ var IDLE_TICK_MS = 6e4;
1398
+ var idleTickHandle = null;
1399
+ function evictIdleTick() {
1400
+ const now = Date.now();
1401
+ for (const [sessionKey, entry] of Array.from(clientPool.entries())) {
1402
+ if (entry.inflight !== null) continue;
1403
+ if (now - entry.lastUsedAt < idleEvictMs) continue;
1404
+ const ageMs = now - entry.createdAt;
1405
+ clientPool.delete(sessionKey);
1406
+ try {
1407
+ entry.userQueue.close();
1408
+ } catch {
1409
+ }
1410
+ try {
1411
+ entry.query.close();
1412
+ } catch {
1413
+ }
1414
+ console.error(
1415
+ `[${isoTs()}] [client-evict] reason=idle sessionKey=${sk(sessionKey)} ageMs=${ageMs} idleMs=${now - entry.lastUsedAt} turnsServed=${entry.turnsServed}`
1416
+ );
1417
+ }
1418
+ }
1419
+ function startIdleEvictTick() {
1420
+ if (idleTickHandle) return;
1421
+ idleTickHandle = setInterval(evictIdleTick, IDLE_TICK_MS);
1422
+ if (idleTickHandle.unref) idleTickHandle.unref();
1423
+ }
1424
+ function stopIdleEvictTick() {
1425
+ if (idleTickHandle) {
1426
+ clearInterval(idleTickHandle);
1427
+ idleTickHandle = null;
1428
+ }
1429
+ }
1430
+ startIdleEvictTick();
1431
+ onClearAgentSessionId((sessionKey, reason) => {
1432
+ const entry = clientPool.get(sessionKey);
1433
+ if (!entry) return;
1434
+ const ageMs = Date.now() - entry.createdAt;
1435
+ clientPool.delete(sessionKey);
1436
+ try {
1437
+ entry.userQueue.close();
1438
+ } catch {
1439
+ }
1440
+ try {
1441
+ entry.query.close();
1442
+ } catch {
1443
+ }
1444
+ console.error(
1445
+ `[${isoTs()}] [client-evict] reason=clearAgentSessionId-${reason} sessionKey=${sk(sessionKey)} ageMs=${ageMs} turnsServed=${entry.turnsServed}`
1446
+ );
1447
+ });
1448
+ onEvictPool((sessionKey, reason) => {
1449
+ const entry = clientPool.get(sessionKey);
1450
+ if (!entry) return;
1451
+ const ageMs = Date.now() - entry.createdAt;
1452
+ clientPool.delete(sessionKey);
1453
+ try {
1454
+ entry.userQueue.close();
1455
+ } catch {
1456
+ }
1457
+ try {
1458
+ entry.query.close();
1459
+ } catch {
1460
+ }
1461
+ console.error(
1462
+ `[${isoTs()}] [client-evict] reason=evictPool-${reason} sessionKey=${sk(sessionKey)} ageMs=${ageMs} turnsServed=${entry.turnsServed}`
1463
+ );
1464
+ });
1465
+ function sk(sessionKey) {
1466
+ return sessionKey.length > 12 ? sessionKey.slice(0, 12) + "\u2026" : sessionKey;
1467
+ }
1468
+ function safeWrite(stream, line) {
1469
+ if (!stream || stream.destroyed) return;
1470
+ if (stream.writableEnded) return;
1471
+ try {
1472
+ stream.write(line);
1473
+ } catch {
1474
+ }
1475
+ }
1476
+ function _poolSnapshotForTest() {
1477
+ const now = Date.now();
1478
+ return Array.from(clientPool.entries()).map(([sessionKey, entry]) => ({
1479
+ sessionKey,
1480
+ ageMs: now - entry.createdAt,
1481
+ idleMs: now - entry.lastUsedAt,
1482
+ turnsServed: entry.turnsServed,
1483
+ inflight: entry.inflight !== null
1484
+ }));
1485
+ }
1486
+ function _evictAllForTest(reason = "test-tear-down") {
1487
+ for (const sessionKey of Array.from(clientPool.keys())) {
1488
+ const entry = clientPool.get(sessionKey);
1489
+ if (!entry) continue;
1490
+ clientPool.delete(sessionKey);
1491
+ try {
1492
+ entry.userQueue.close();
1493
+ } catch {
1494
+ }
1495
+ try {
1496
+ entry.query.close();
1497
+ } catch {
1498
+ }
1499
+ }
1500
+ void reason;
1501
+ }
1502
+
1503
+ export {
1504
+ MAXY_DIR,
1505
+ BRAND_NAME,
1506
+ COMMERCIAL_MODE,
1507
+ VNC_DISPLAY,
1508
+ RFB_PORT,
1509
+ WEBSOCKIFY_PORT,
1510
+ CDP_PORT,
1511
+ CHROMIUM_PROFILE_DIR,
1512
+ USERS_FILE,
1513
+ LOG_DIR,
1514
+ BIN_DIR,
1515
+ REMOTE_PASSWORD_FILE,
1516
+ REMOTE_SESSION_SECRET_FILE,
1517
+ TELEGRAM_WEBHOOK_SECRET_FILE,
1518
+ TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE,
1519
+ CLAUDE_CREDENTIALS_FILE,
1520
+ substituteBrandPlaceholders,
1521
+ PLATFORM_ROOT2 as PLATFORM_ROOT,
1522
+ ACCOUNTS_DIR,
1523
+ hasStubAccountDir,
1524
+ resolveAccount,
1525
+ readAgentFile,
1526
+ readIdentity,
1527
+ validateAgentSlug,
1528
+ resolveDefaultAgentSlug,
1529
+ resolveAgentConfig,
1530
+ getDefaultAccountId,
1531
+ resolveUserAccounts,
1532
+ isoTs,
1533
+ isBrowserTool,
1534
+ runFailureDiagnostic,
1535
+ agentLogStream,
1536
+ preConversationLogStream,
1537
+ sigtermFlushStreamLogs,
1538
+ preflushStreamLogKey,
1539
+ findFirstSubstantiveUserMessage,
1540
+ getSession,
1541
+ registerSession,
1542
+ registerResumedSession,
1543
+ getSessionMessages,
1544
+ clearSessionHistory,
1545
+ bufferPendingTurn,
1546
+ getPendingTurnCount,
1547
+ drainPendingTurns,
1548
+ isFlushError,
1549
+ maybeFlushConversationBuffer,
1550
+ isDmChannelSessionKey,
1551
+ getAgentNameForSession,
1552
+ listAdminSessionsInProgress,
1553
+ validateSession,
1554
+ storeAgentSessionId,
1555
+ getAgentSessionId,
1556
+ setAgentSessionId,
1557
+ storePendingCompactionSummary,
1558
+ consumePendingCompactionSummary,
1559
+ getAccountIdForSession,
1560
+ getUserIdForSession,
1561
+ getUserNameForSession,
1562
+ getRoleForSession,
1563
+ getConversationIdForSession,
1564
+ setConversationIdForSession,
1565
+ unregisterSession,
1566
+ getGroupSlugForSession,
1567
+ getVisitorIdForSession,
1568
+ setGroupContextForSession,
1569
+ registerGrantSession,
1570
+ getGrantForSession,
1571
+ completeGrantSetup,
1572
+ getMessageHistory,
1573
+ appendMessage,
1574
+ storePendingTrimmedMessages,
1575
+ consumePendingTrimmedMessages,
1576
+ storeStalledSubagent,
1577
+ consumeStalledSubagents,
1578
+ setPendingCommitmentOffers,
1579
+ getAgentTypeForSession,
1580
+ clearMessageHistory,
1581
+ clearAgentSessionId,
1582
+ evictPool,
1583
+ storeRecoveryHandoff,
1584
+ consumeRecoveryHandoff,
1585
+ getLastClearReason,
1586
+ clearLastClearReason,
1587
+ storeStallResume,
1588
+ consumeStallResume,
1589
+ acquireClient,
1590
+ pushUserMessage,
1591
+ pushUserToolResult,
1592
+ setInflight,
1593
+ recordCachedTokens,
1594
+ getActiveClient,
1595
+ interruptClient,
1596
+ evictClient,
1597
+ acquireOneShotClient,
1598
+ recordCrash,
1599
+ startIdleEvictTick,
1600
+ stopIdleEvictTick,
1601
+ _poolSnapshotForTest,
1602
+ _evictAllForTest
1603
+ };