very-happy-cli 0.2.124 → 0.2.125

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 (49) hide show
  1. package/dist/{AcpBackend-CT9TOBEM.mjs → AcpBackend-DOQGItcS.mjs} +2 -1
  2. package/dist/{AcpBackend-au0veuIF.cjs → AcpBackend-ssQpTLks.cjs} +30 -29
  3. package/dist/{AcpSessionManager-p1tj7FxF.mjs → AcpSessionManager-D4WkNsRs.mjs} +1 -1
  4. package/dist/{AcpSessionManager-BCWeBKDd.cjs → AcpSessionManager-DHLc-D50.cjs} +9 -9
  5. package/dist/agentGuidance-Cg9ZG1pP.cjs +236 -0
  6. package/dist/agentGuidance-QpYwykb9.mjs +224 -0
  7. package/dist/codex/happyMcpStdioBridge.cjs +29 -6
  8. package/dist/codex/happyMcpStdioBridge.mjs +25 -2
  9. package/dist/{config-GzzqE5Ev.cjs → config-BxHOGtEK.cjs} +11 -11
  10. package/dist/{config-DwMic6AC.mjs → config-CZNIz3W0.mjs} +11 -11
  11. package/dist/{index-DgN9P02i.cjs → index-CBj99SO6.cjs} +53 -19
  12. package/dist/{index-SQKnWyz-.mjs → index-CrSifgJM.mjs} +2046 -1719
  13. package/dist/{index-B3-daYaX.mjs → index-D-BgvQi5.mjs} +8 -8
  14. package/dist/{index-C69uF6Up.cjs → index-D_Rm9dw9.cjs} +8 -8
  15. package/dist/{index-DI_VC3vJ.cjs → index-Do-kjc30.cjs} +2191 -1864
  16. package/dist/{index-Ba6Fu6MJ.mjs → index-Jrk6ud3g.mjs} +49 -15
  17. package/dist/index.cjs +5 -5
  18. package/dist/index.mjs +5 -5
  19. package/dist/{installTerminalHooks-CvMt8cBI.cjs → installTerminalHooks-CPsdeOM4.cjs} +9 -9
  20. package/dist/{installTerminalHooks-Df4hzzdM.mjs → installTerminalHooks-Z1Jb6yjb.mjs} +8 -8
  21. package/dist/lib.cjs +12 -12
  22. package/dist/lib.d.cts +3 -0
  23. package/dist/lib.d.mts +3 -0
  24. package/dist/lib.mjs +9 -9
  25. package/dist/limits-DViGHwSj.cjs +1198 -0
  26. package/dist/limits-FjbPJPsF.mjs +1129 -0
  27. package/dist/{mcp-DnyLwSPq.cjs → mcp-BbQ5FWcQ.cjs} +11 -10
  28. package/dist/{mcp-C0oW5u-b.mjs → mcp-zqPJ94Bk.mjs} +7 -6
  29. package/dist/resources-CVl7NdOB.cjs +83 -0
  30. package/dist/resources-D-mn7ptO.mjs +80 -0
  31. package/dist/{runGemini-_FHrxuHN.cjs → runGemini-BnHGYX-H.cjs} +30 -30
  32. package/dist/{runGemini-mqLjz-M7.mjs → runGemini-Bu49s_bN.mjs} +13 -13
  33. package/dist/{runOpenClaw-C5VZQ6Na.cjs → runOpenClaw-3Ld1eoWV.cjs} +24 -24
  34. package/dist/{runOpenClaw-Bs03yQHe.mjs → runOpenClaw-CznAYPCh.mjs} +9 -9
  35. package/dist/{send-BaLjQPZQ.mjs → send-BevgS1mf.mjs} +10 -10
  36. package/dist/{send-BD2gTVoP.cjs → send-DCzyBxLl.cjs} +10 -10
  37. package/dist/{sessions-ggBePfSY.mjs → sessions-Ba2oRTmw.mjs} +10 -10
  38. package/dist/{sessions-B108j_Xj.cjs → sessions-DSoaBHwA.cjs} +11 -11
  39. package/dist/{spawn-iI8w8sD7.cjs → spawn-CqMLY6dE.cjs} +21 -21
  40. package/dist/{spawn-CORFRb9D.mjs → spawn-j9KBfj4e.mjs} +21 -21
  41. package/dist/teams-C0ggJTJ-.mjs +164 -0
  42. package/dist/teams-CmU3K9vB.cjs +166 -0
  43. package/dist/{types-CZIwVSN2.cjs → types-BGHnQXl5.cjs} +462 -1571
  44. package/dist/{types-gDOmwOKw.mjs → types-DLDkfrTC.mjs} +76 -1148
  45. package/package.json +7 -7
  46. package/dist/agentGuidance-BifkGXdE.cjs +0 -46
  47. package/dist/agentGuidance-C2_pkgRI.mjs +0 -37
  48. package/dist/limits-CwFb5S2b.mjs +0 -22
  49. package/dist/limits-KdWKmwtH.cjs +0 -28
@@ -1,33 +1,32 @@
1
1
  'use strict';
2
2
 
3
3
  var axios = require('axios');
4
- var chalk = require('chalk');
5
- var node_util = require('node:util');
6
- var node_fs = require('node:fs');
7
- var os = require('node:os');
8
- var path = require('node:path');
9
- var promises = require('node:fs/promises');
4
+ var persistence = require('./limits-DViGHwSj.cjs');
10
5
  var node_events = require('node:events');
11
6
  var socket_ioClient = require('socket.io-client');
12
7
  var z = require('zod');
13
8
  var cuid2 = require('@paralleldrive/cuid2');
14
9
  var node_crypto = require('node:crypto');
15
- var tweetnacl = require('tweetnacl');
16
- var limits = require('./limits-KdWKmwtH.cjs');
17
10
  var child_process = require('child_process');
18
11
  var util = require('util');
19
12
  var fs = require('fs/promises');
20
13
  var crypto = require('crypto');
21
- var path$1 = require('path');
14
+ var path = require('path');
22
15
  var spawn = require('cross-spawn');
23
16
  var url = require('url');
24
- var os$1 = require('os');
17
+ var os = require('os');
18
+ var promises = require('node:fs/promises');
19
+ var path$1 = require('node:path');
20
+ var os$1 = require('node:os');
25
21
  var node_child_process = require('node:child_process');
26
22
  var pty = require('node-pty');
23
+ var node_fs = require('node:fs');
27
24
  var fs$1 = require('fs');
25
+ var tweetnacl = require('tweetnacl');
28
26
  var node_readline = require('node:readline');
29
27
  var sandboxRuntime = require('@anthropic-ai/sandbox-runtime');
30
28
  var expoServerSdk = require('expo-server-sdk');
29
+ var chalk = require('chalk');
31
30
 
32
31
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
33
32
  function _interopNamespaceDefault(e) {
@@ -50,599 +49,6 @@ function _interopNamespaceDefault(e) {
50
49
  var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
51
50
  var pty__namespace = /*#__PURE__*/_interopNamespaceDefault(pty);
52
51
 
53
- var name = "very-happy-cli";
54
- var version = "0.2.124";
55
- var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
56
- var author = "Kirill Dubovitskiy";
57
- var contributors = [
58
- "Mereith (very-happy-cli fork)"
59
- ];
60
- var license = "MIT";
61
- var engines = {
62
- node: "^20.19.0 || ^22.13.0 || >=24.0.0"
63
- };
64
- var type = "module";
65
- var homepage = "https://github.com/Mereithhh/very-happy";
66
- var bugs = "https://github.com/Mereithhh/very-happy/issues";
67
- var repository = {
68
- type: "git",
69
- url: "git+https://github.com/Mereithhh/very-happy.git"
70
- };
71
- var bin = {
72
- "very-happy": "./bin/very-happy.mjs",
73
- "very-happy-mcp": "./bin/very-happy-mcp.mjs"
74
- };
75
- var main = "./dist/index.cjs";
76
- var module$1 = "./dist/index.mjs";
77
- var types = "./dist/index.d.cts";
78
- var exports$1 = {
79
- ".": {
80
- require: {
81
- types: "./dist/index.d.cts",
82
- "default": "./dist/index.cjs"
83
- },
84
- "import": {
85
- types: "./dist/index.d.mts",
86
- "default": "./dist/index.mjs"
87
- }
88
- },
89
- "./lib": {
90
- require: {
91
- types: "./dist/lib.d.cts",
92
- "default": "./dist/lib.cjs"
93
- },
94
- "import": {
95
- types: "./dist/lib.d.mts",
96
- "default": "./dist/lib.mjs"
97
- }
98
- },
99
- "./codex/happyMcpStdioBridge": {
100
- require: {
101
- types: "./dist/codex/happyMcpStdioBridge.d.cts",
102
- "default": "./dist/codex/happyMcpStdioBridge.cjs"
103
- },
104
- "import": {
105
- types: "./dist/codex/happyMcpStdioBridge.d.mts",
106
- "default": "./dist/codex/happyMcpStdioBridge.mjs"
107
- }
108
- }
109
- };
110
- var files = [
111
- "dist",
112
- "bin",
113
- "scripts",
114
- "tools/licenses",
115
- "package.json",
116
- "README.md",
117
- "LICENSE"
118
- ];
119
- var scripts = {
120
- typecheck: "tsc --noEmit",
121
- "bundle:server": "node scripts/bundle-server.cjs",
122
- "bundle:server:all": "node scripts/bundle-server.cjs --all-platforms",
123
- "bundle:webapp": "node scripts/bundle-webapp.cjs",
124
- build: "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc --noEmit && pkgroll",
125
- test: "pnpm run build && vitest run --project unit",
126
- "test:integration": "pnpm run build && vitest run --project integration-empty --project integration-plan-mode --project integration-authenticated",
127
- "cli:install": "node scripts/install-local.cjs",
128
- prepublishOnly: "pnpm test",
129
- postinstall: "node scripts/unpack-tools.cjs && node scripts/fix-pty-perms.cjs"
130
- };
131
- var dependencies = {
132
- "@agentclientprotocol/sdk": "^0.14.1",
133
- "@anthropic-ai/claude-agent-sdk": "^0.3.232",
134
- "@anthropic-ai/sandbox-runtime": "^0.0.37",
135
- "@modelcontextprotocol/sdk": "1.30.0",
136
- "@noble/ed25519": "^3.0.0",
137
- "@noble/hashes": "^2.0.1",
138
- "@paralleldrive/cuid2": "^2.2.2",
139
- "@stablelib/base64": "^2.0.1",
140
- "@stablelib/hex": "^2.0.1",
141
- "@types/cross-spawn": "^6.0.6",
142
- "@types/http-proxy": "^1.17.17",
143
- "@types/react": "^19.2.7",
144
- "@types/tmp": "^0.2.6",
145
- axios: "^1.13.2",
146
- chalk: "^5.6.2",
147
- "cross-spawn": "^7.0.6",
148
- "expo-server-sdk": "^3.15.0",
149
- fastify: "5.8.5",
150
- "fastify-type-provider-zod": "^6.1.0",
151
- "http-proxy": "^1.18.1",
152
- "http-proxy-middleware": "^3.0.7",
153
- ink: "^6.5.1",
154
- inquirer: "^13.2.2",
155
- "node-pty": "^1.0.0",
156
- open: "^10.2.0",
157
- "ps-list": "^8.1.1",
158
- react: "^19.2.0",
159
- "socket.io-client": "^4.8.1",
160
- tar: "^7.5.22",
161
- tmp: "^0.2.7",
162
- tweetnacl: "^1.0.3",
163
- ws: "^8.21.3",
164
- zod: "^4.0.0"
165
- };
166
- var optionalDependencies = {
167
- "very-happy-tools-arm64-darwin": "0.2.124",
168
- "very-happy-tools-x64-darwin": "0.2.124",
169
- "very-happy-tools-arm64-linux": "0.2.124",
170
- "very-happy-tools-x64-linux": "0.2.124",
171
- "very-happy-tools-arm64-win32": "0.2.124",
172
- "very-happy-tools-x64-win32": "0.2.124"
173
- };
174
- var devDependencies = {
175
- "@slopus/happy-wire": "workspace:*",
176
- "@xterm/addon-serialize": "0.13.0",
177
- "@xterm/headless": "5.5.0",
178
- "@types/inquirer": "^9.0.9",
179
- "@types/node": ">=20",
180
- "@types/ws": "^8.18.1",
181
- "cross-env": "^10.1.0",
182
- dotenv: "^16.6.1",
183
- pkgroll: "^2.14.2",
184
- "ts-node": "^10",
185
- tsx: "^4.20.6",
186
- typescript: "5.9.3",
187
- vitest: "3.2.6"
188
- };
189
- var publishConfig = {
190
- registry: "https://registry.npmjs.org"
191
- };
192
- var packageManager = "pnpm@10.11.0";
193
- var packageJson = {
194
- name: name,
195
- version: version,
196
- description: description,
197
- author: author,
198
- contributors: contributors,
199
- license: license,
200
- engines: engines,
201
- type: type,
202
- homepage: homepage,
203
- bugs: bugs,
204
- repository: repository,
205
- bin: bin,
206
- main: main,
207
- module: module$1,
208
- types: types,
209
- exports: exports$1,
210
- files: files,
211
- scripts: scripts,
212
- dependencies: dependencies,
213
- optionalDependencies: optionalDependencies,
214
- devDependencies: devDependencies,
215
- publishConfig: publishConfig,
216
- packageManager: packageManager
217
- };
218
-
219
- const PRIVATE_DIRECTORY_MODE = 448;
220
- const PRIVATE_FILE_MODE = 384;
221
- function ignoreUnsupportedPermissions(error) {
222
- if (process.platform !== "win32") throw error;
223
- }
224
- function ensurePrivateDirectorySync(path) {
225
- node_fs.mkdirSync(path, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
226
- try {
227
- node_fs.chmodSync(path, PRIVATE_DIRECTORY_MODE);
228
- } catch (error) {
229
- ignoreUnsupportedPermissions(error);
230
- }
231
- }
232
- async function ensurePrivateDirectory(path) {
233
- await promises.mkdir(path, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
234
- try {
235
- await promises.chmod(path, PRIVATE_DIRECTORY_MODE);
236
- } catch (error) {
237
- ignoreUnsupportedPermissions(error);
238
- }
239
- }
240
- function hardenPrivateFileSync(path) {
241
- try {
242
- node_fs.chmodSync(path, PRIVATE_FILE_MODE);
243
- } catch (error) {
244
- ignoreUnsupportedPermissions(error);
245
- }
246
- }
247
- function hardenPrivateDirectoryFilesSync(path$1) {
248
- for (const name of node_fs.readdirSync(path$1)) {
249
- const candidate = path.join(path$1, name);
250
- try {
251
- if (node_fs.statSync(candidate).isFile()) hardenPrivateFileSync(candidate);
252
- } catch {
253
- }
254
- }
255
- }
256
- async function hardenPrivateFile(path) {
257
- try {
258
- await promises.chmod(path, PRIVATE_FILE_MODE);
259
- } catch (error) {
260
- ignoreUnsupportedPermissions(error);
261
- }
262
- }
263
- function writePrivateFileSync(path, data) {
264
- node_fs.writeFileSync(path, data, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
265
- hardenPrivateFileSync(path);
266
- }
267
- async function writePrivateFile(path, data) {
268
- await promises.writeFile(path, data, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
269
- await hardenPrivateFile(path);
270
- }
271
- function appendPrivateFileSync(path, data) {
272
- node_fs.appendFileSync(path, data, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
273
- hardenPrivateFileSync(path);
274
- }
275
-
276
- const DEFAULT_RELAY_URL = "https://veryhappy.dev";
277
- function normalizeHttpEndpoint(value, source) {
278
- const trimmed = value.trim();
279
- let parsed;
280
- try {
281
- parsed = new URL(trimmed);
282
- } catch {
283
- throw new Error(`${source} must be an absolute http(s) origin`);
284
- }
285
- const onlyOriginPath = /^\/+$/u.test(parsed.pathname);
286
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username.length > 0 || parsed.password.length > 0 || !onlyOriginPath || parsed.search.length > 0 || parsed.hash.length > 0) {
287
- throw new Error(`${source} must be an http(s) origin without credentials, path, query, or fragment`);
288
- }
289
- return parsed.origin;
290
- }
291
- function resolveHttpEndpoint(environmentValue, settingsValue, environmentName, settingsName) {
292
- if (environmentValue !== void 0) {
293
- return normalizeHttpEndpoint(environmentValue, environmentName);
294
- }
295
- if (settingsValue !== void 0) {
296
- return normalizeHttpEndpoint(settingsValue, `settings.${settingsName}`);
297
- }
298
- return DEFAULT_RELAY_URL;
299
- }
300
- class Configuration {
301
- serverUrl;
302
- webappUrl;
303
- isDaemonProcess;
304
- // Directories and paths (from persistence)
305
- happyHomeDir;
306
- logsDir;
307
- settingsFile;
308
- privateKeyFile;
309
- daemonStateFile;
310
- daemonLockFile;
311
- sessionsFile;
312
- currentCliVersion;
313
- isExperimentalEnabled;
314
- disableCaffeinate;
315
- constructor() {
316
- const args = process.argv.slice(2);
317
- this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
318
- if (process.env.HAPPY_HOME_DIR) {
319
- const expandedPath = process.env.HAPPY_HOME_DIR.replace(/^~/, os.homedir());
320
- this.happyHomeDir = expandedPath;
321
- } else {
322
- this.happyHomeDir = path.join(os.homedir(), ".happy");
323
- }
324
- this.logsDir = path.join(this.happyHomeDir, "logs");
325
- this.settingsFile = path.join(this.happyHomeDir, "settings.json");
326
- this.privateKeyFile = path.join(this.happyHomeDir, "access.key");
327
- this.daemonStateFile = path.join(this.happyHomeDir, "daemon.state.json");
328
- this.daemonLockFile = path.join(this.happyHomeDir, "daemon.state.json.lock");
329
- this.sessionsFile = path.join(this.happyHomeDir, "sessions.json");
330
- try {
331
- this.serverUrl = resolveHttpEndpoint(
332
- process.env.HAPPY_SERVER_URL,
333
- readSettingsStringSync(this.settingsFile, "serverUrl"),
334
- "HAPPY_SERVER_URL",
335
- "serverUrl"
336
- );
337
- this.webappUrl = resolveHttpEndpoint(
338
- process.env.HAPPY_WEBAPP_URL,
339
- readSettingsStringSync(this.settingsFile, "webappUrl"),
340
- "HAPPY_WEBAPP_URL",
341
- "webappUrl"
342
- );
343
- } catch (error) {
344
- const message = error instanceof Error ? error.message : "invalid client endpoint";
345
- process.stderr.write(`[very-happy] Configuration error: ${message}
346
- `);
347
- process.exit(1);
348
- }
349
- this.isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || "");
350
- this.disableCaffeinate = ["true", "1", "yes"].includes(process.env.HAPPY_DISABLE_CAFFEINATE?.toLowerCase() || "");
351
- this.currentCliVersion = packageJson.version;
352
- const variant = process.env.HAPPY_VARIANT || "stable";
353
- if (!this.isDaemonProcess && variant === "dev") {
354
- console.log("\x1B[33m\u{1F527} DEV MODE\x1B[0m - Data: " + this.happyHomeDir);
355
- }
356
- ensurePrivateDirectorySync(this.happyHomeDir);
357
- ensurePrivateDirectorySync(this.logsDir);
358
- for (const privateFile of [
359
- this.settingsFile,
360
- this.privateKeyFile,
361
- this.daemonStateFile,
362
- this.daemonLockFile,
363
- this.sessionsFile
364
- ]) {
365
- if (node_fs.existsSync(privateFile)) hardenPrivateFileSync(privateFile);
366
- }
367
- hardenPrivateDirectoryFilesSync(this.logsDir);
368
- }
369
- }
370
- function readSettingsStringSync(settingsFile, key) {
371
- try {
372
- if (!node_fs.existsSync(settingsFile)) return void 0;
373
- const raw = JSON.parse(node_fs.readFileSync(settingsFile, "utf8"));
374
- const value = raw?.[key];
375
- return typeof value === "string" && value.length > 0 ? value : void 0;
376
- } catch {
377
- return void 0;
378
- }
379
- }
380
- const configuration = new Configuration();
381
-
382
- function sensitiveKey(key) {
383
- const normalized = key.toLowerCase().replace(/[_\-\s]/g, "");
384
- return /(token|secret|password|authorization|cookie|credential|apikey|privatekey|encryptionkey|accesskey|claimsecret)/.test(normalized);
385
- }
386
- function redactLogString(value) {
387
- return value.replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, "$1[REDACTED]").replace(/([?&](?:token|secret|password|api[_-]?key)=)[^&#\s]+/gi, "$1[REDACTED]").replace(/((?:token|secret|password|api[_-]?key)\s*[=:]\s*)[^\s,;]+/gi, "$1[REDACTED]");
388
- }
389
- function redactLogValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
390
- if (typeof value === "string") return redactLogString(value);
391
- if (value === null || typeof value !== "object") return value;
392
- if (depth >= 6) return "[TRUNCATED]";
393
- if (seen.has(value)) return "[CIRCULAR]";
394
- seen.add(value);
395
- if (value instanceof Error) {
396
- const error = value;
397
- return {
398
- name: error.name,
399
- message: redactLogString(error.message),
400
- ...typeof error.code === "string" || typeof error.code === "number" ? { code: error.code } : {},
401
- ...typeof (error.status ?? error.response?.status) === "number" ? { status: error.status ?? error.response?.status } : {}
402
- };
403
- }
404
- if (Array.isArray(value)) return value.map((item) => redactLogValue(item, depth + 1, seen));
405
- const result = {};
406
- for (const [key, child] of Object.entries(value)) {
407
- if (sensitiveKey(key)) {
408
- result[key] = "[REDACTED]";
409
- } else if (key === "environmentVariables" && child && typeof child === "object") {
410
- result[key] = { names: Object.keys(child).sort() };
411
- } else {
412
- result[key] = redactLogValue(child, depth + 1, seen);
413
- }
414
- }
415
- return result;
416
- }
417
-
418
- function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
419
- return date.toLocaleString("sv-SE", {
420
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
421
- year: "numeric",
422
- month: "2-digit",
423
- day: "2-digit",
424
- hour: "2-digit",
425
- minute: "2-digit",
426
- second: "2-digit"
427
- }).replace(/[: ]/g, "-").replace(/,/g, "") + "-pid-" + process.pid;
428
- }
429
- function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
430
- return date.toLocaleTimeString("en-US", {
431
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
432
- hour12: false,
433
- hour: "2-digit",
434
- minute: "2-digit",
435
- second: "2-digit",
436
- fractionalSecondDigits: 3
437
- });
438
- }
439
- function getSessionLogPath() {
440
- const timestamp = createTimestampForFilename();
441
- const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
442
- return path.join(configuration.logsDir, filename);
443
- }
444
- function safeRemoteLogUrl(raw) {
445
- if (!raw) return void 0;
446
- try {
447
- const url = new URL(raw);
448
- const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
449
- if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) return void 0;
450
- if (url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "") return void 0;
451
- return url.origin;
452
- } catch {
453
- return void 0;
454
- }
455
- }
456
- class Logger {
457
- constructor(logFilePath = getSessionLogPath()) {
458
- this.logFilePath = logFilePath;
459
- const remoteUrl = safeRemoteLogUrl(process.env.HAPPY_SERVER_URL);
460
- const remoteToken = process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING_TOKEN;
461
- if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING && remoteUrl && remoteToken && Buffer.byteLength(remoteToken) >= 32) {
462
- this.dangerouslyUnencryptedServerLoggingUrl = remoteUrl;
463
- this.dangerouslyUnencryptedServerLoggingToken = remoteToken;
464
- console.log(chalk.yellow("[REMOTE LOGGING] Sending logs to server for AI debugging"));
465
- }
466
- }
467
- dangerouslyUnencryptedServerLoggingUrl;
468
- dangerouslyUnencryptedServerLoggingToken;
469
- // Use local timezone for simplicity of locating the logs,
470
- // in practice you will not need absolute timestamps
471
- localTimezoneTimestamp() {
472
- return createTimestampForLogEntry();
473
- }
474
- debug(message, ...args) {
475
- this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, ...args);
476
- }
477
- debugLargeJson(message, object, maxStringLength = 100, maxArrayLength = 10) {
478
- if (!process.env.DEBUG) {
479
- this.debug(`In production, skipping message inspection`);
480
- return;
481
- }
482
- const truncateStrings = (obj) => {
483
- if (typeof obj === "string") {
484
- return obj.length > maxStringLength ? obj.substring(0, maxStringLength) + "... [truncated for logs]" : obj;
485
- }
486
- if (Array.isArray(obj)) {
487
- const truncatedArray = obj.map((item) => truncateStrings(item)).slice(0, maxArrayLength);
488
- if (obj.length > maxArrayLength) {
489
- truncatedArray.push(`... [truncated array for logs up to ${maxArrayLength} items]`);
490
- }
491
- return truncatedArray;
492
- }
493
- if (obj && typeof obj === "object") {
494
- const result = {};
495
- for (const [key, value] of Object.entries(obj)) {
496
- if (key === "usage") {
497
- continue;
498
- }
499
- result[key] = truncateStrings(value);
500
- }
501
- return result;
502
- }
503
- return obj;
504
- };
505
- const truncatedObject = truncateStrings(redactLogValue(object));
506
- const json = JSON.stringify(truncatedObject, null, 2);
507
- this.logToFile(`[${this.localTimezoneTimestamp()}]`, redactLogString(message), "\n", json);
508
- }
509
- info(message, ...args) {
510
- this.logToConsole("info", "", message, ...args);
511
- this.debug(message, args);
512
- }
513
- infoDeveloper(message, ...args) {
514
- this.debug(message, ...args);
515
- if (process.env.DEBUG) {
516
- this.logToConsole("info", "[DEV]", message, ...args);
517
- }
518
- }
519
- warn(message, ...args) {
520
- this.logToConsole("warn", "", message, ...args);
521
- this.debug(`[WARN] ${message}`, ...args);
522
- }
523
- getLogPath() {
524
- return this.logFilePath;
525
- }
526
- logToConsole(level, prefix, message, ...args) {
527
- message = redactLogString(message);
528
- args = args.map((arg) => redactLogValue(arg));
529
- switch (level) {
530
- case "debug": {
531
- console.log(chalk.gray(prefix), message, ...args);
532
- break;
533
- }
534
- case "error": {
535
- console.error(chalk.red(prefix), message, ...args);
536
- break;
537
- }
538
- case "info": {
539
- console.log(chalk.blue(prefix), message, ...args);
540
- break;
541
- }
542
- case "warn": {
543
- console.log(chalk.yellow(prefix), message, ...args);
544
- break;
545
- }
546
- default: {
547
- this.debug("Unknown log level:", level);
548
- console.log(chalk.blue(prefix), message, ...args);
549
- break;
550
- }
551
- }
552
- }
553
- async sendToRemoteServer(level, message, ...args) {
554
- if (!this.dangerouslyUnencryptedServerLoggingUrl) return;
555
- try {
556
- await fetch(this.dangerouslyUnencryptedServerLoggingUrl + "/logs-combined-from-cli-and-mobile-for-simple-ai-debugging", {
557
- method: "POST",
558
- headers: {
559
- "Content-Type": "application/json",
560
- "Authorization": `Bearer ${this.dangerouslyUnencryptedServerLoggingToken}`
561
- },
562
- body: JSON.stringify({
563
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
564
- level,
565
- message: `${redactLogString(message)} ${args.map((raw) => {
566
- const safe = redactLogValue(raw);
567
- return typeof safe === "object" ? JSON.stringify(safe, null, 2) : String(safe);
568
- }).join(" ")}`,
569
- source: "cli",
570
- platform: process.platform
571
- })
572
- });
573
- } catch (error) {
574
- }
575
- }
576
- logToFile(prefix, message, ...args) {
577
- const safeMessage = redactLogString(message);
578
- const safeArgs = args.map((arg) => redactLogValue(arg));
579
- const logLine = `${prefix} ${safeMessage} ${safeArgs.map(
580
- (arg) => typeof arg === "string" ? arg : node_util.inspect(arg, { depth: 5, breakLength: 120 })
581
- ).join(" ")}
582
- `;
583
- if (this.dangerouslyUnencryptedServerLoggingUrl) {
584
- let level = "info";
585
- if (prefix.includes(this.localTimezoneTimestamp())) {
586
- level = "debug";
587
- }
588
- this.sendToRemoteServer(level, safeMessage, ...safeArgs).catch(() => {
589
- });
590
- }
591
- try {
592
- appendPrivateFileSync(this.logFilePath, logLine);
593
- } catch (appendError) {
594
- if (process.env.DEBUG) {
595
- console.error("[DEV MODE ONLY THROWING] Failed to append to log file:", appendError);
596
- throw appendError;
597
- }
598
- }
599
- }
600
- }
601
- let logger = new Logger();
602
- async function listDaemonLogFiles(limit = 50) {
603
- try {
604
- const logsDir = configuration.logsDir;
605
- if (!node_fs.existsSync(logsDir)) {
606
- return [];
607
- }
608
- const logs = node_fs.readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
609
- const fullPath = path.join(logsDir, file);
610
- const stats = node_fs.statSync(fullPath);
611
- return { file, path: fullPath, modified: stats.mtime };
612
- }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
613
- try {
614
- const { readDaemonState } = await Promise.resolve().then(function () { return persistence; });
615
- const state = await readDaemonState();
616
- if (!state) {
617
- return logs;
618
- }
619
- if (state.daemonLogPath && node_fs.existsSync(state.daemonLogPath)) {
620
- const stats = node_fs.statSync(state.daemonLogPath);
621
- const persisted = {
622
- file: path.basename(state.daemonLogPath),
623
- path: state.daemonLogPath,
624
- modified: stats.mtime
625
- };
626
- const idx = logs.findIndex((l) => l.path === persisted.path);
627
- if (idx >= 0) {
628
- const [found] = logs.splice(idx, 1);
629
- logs.unshift(found);
630
- } else {
631
- logs.unshift(persisted);
632
- }
633
- }
634
- } catch {
635
- }
636
- return logs.slice(0, Math.max(0, limit));
637
- } catch {
638
- return [];
639
- }
640
- }
641
- async function getLatestDaemonLog() {
642
- const [latest] = await listDaemonLogFiles(1);
643
- return latest || null;
644
- }
645
-
646
52
  const sessionRoleSchema = z__namespace.enum(["user", "agent"]);
647
53
  const sessionTextEventSchema = z__namespace.object({
648
54
  t: z__namespace.literal("text"),
@@ -1048,8 +454,34 @@ const ConnectionDiagnosticEventSchema = z.z.object({
1048
454
  z.z.object({
1049
455
  events: z.z.array(ConnectionDiagnosticEventSchema).min(1).max(32)
1050
456
  }).strict();
457
+ const id = z.z.string().min(1).max(128);
458
+ const text = z.z.string().min(1).max(32e3);
459
+ const TeamActionSchema = z.z.discriminatedUnion("type", [
460
+ z.z.object({ type: z.z.literal("archive") }),
461
+ z.z.object({ type: z.z.literal("schedule-create"), name: id, botId: id, body: text, runAt: z.z.number().int().positive().max(864e13), intervalMs: z.z.number().int().min(6e4).max(366 * 864e5).optional() }),
462
+ z.z.object({ type: z.z.literal("schedule-pause"), scheduleId: id, version: z.z.number().int().positive() }),
463
+ z.z.object({ type: z.z.literal("schedule-resume"), scheduleId: id, version: z.z.number().int().positive() }),
464
+ z.z.object({ type: z.z.literal("schedule-cancel"), scheduleId: id, version: z.z.number().int().positive() }),
465
+ z.z.object({ type: z.z.literal("reconcile-operation"), operationId: id, claimId: id, note: z.z.string().trim().min(1).max(4e3) }),
466
+ z.z.object({ type: z.z.literal("join"), name: id, sessionId: id, botId: id.optional() }),
467
+ z.z.object({ type: z.z.literal("delegate"), goal: text, acceptance: z.z.array(text).min(1).max(32), parentTaskId: id.optional(), assigneeBotId: id.optional(), botName: id.optional(), directory: z.z.string().min(1).max(4096).optional(), assistant: z.z.enum(["claude", "codex", "pi-acp"]).optional() }),
468
+ z.z.object({ type: z.z.literal("message"), taskId: id, body: text, recipientBotId: id.optional() }),
469
+ z.z.object({ type: z.z.literal("submit"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), result: text }),
470
+ z.z.object({ type: z.z.literal("accept"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive() }),
471
+ z.z.object({ type: z.z.literal("return"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), reason: text }),
472
+ z.z.object({ type: z.z.literal("cancel"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), reason: text }),
473
+ z.z.object({ type: z.z.literal("handoff"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), assigneeBotId: id }),
474
+ z.z.object({ type: z.z.literal("claim-operation"), operationId: id, machineId: id }),
475
+ z.z.object({ type: z.z.literal("complete-operation"), operationId: id, machineId: id, claimId: id, sessionId: id.optional() }),
476
+ z.z.object({ type: z.z.literal("fail-operation"), operationId: id, machineId: id, claimId: id, error: text, unknown: z.z.boolean().optional() }),
477
+ z.z.object({ type: z.z.literal("session-event"), sessionId: id, event: z.z.enum(["idle", "blocked", "exited"]) }),
478
+ z.z.object({ type: z.z.literal("message-delivered"), messageId: id, recipientBotId: id, generation: z.z.number().int().positive(), sessionId: id })
479
+ ]);
480
+ z.z.object({ requestId: id, action: TeamActionSchema });
481
+ z.z.object({ name: id, machineId: id, requestId: id.optional() });
1051
482
 
1052
483
  z.z.object({
484
+ teamsVersion: z.z.number().int().positive().optional(),
1053
485
  host: z.z.string(),
1054
486
  platform: z.z.string(),
1055
487
  happyCliVersion: z.z.string(),
@@ -1324,125 +756,6 @@ const AgentMessageSchema = z.z.object({
1324
756
  });
1325
757
  z.z.union([UserMessageSchema, AgentMessageSchema]);
1326
758
 
1327
- function encodeBase64(buffer, variant = "base64") {
1328
- if (variant === "base64url") {
1329
- return encodeBase64Url(buffer);
1330
- }
1331
- return Buffer.from(buffer).toString("base64");
1332
- }
1333
- function encodeBase64Url(buffer) {
1334
- return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
1335
- }
1336
- function decodeBase64(base64, variant = "base64") {
1337
- if (variant === "base64url") {
1338
- const base64Standard = base64.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - base64.length % 4) % 4);
1339
- return new Uint8Array(Buffer.from(base64Standard, "base64"));
1340
- }
1341
- return new Uint8Array(Buffer.from(base64, "base64"));
1342
- }
1343
- function getRandomBytes(size) {
1344
- return new Uint8Array(node_crypto.randomBytes(size));
1345
- }
1346
- function libsodiumPublicKeyFromSecretKey(seed) {
1347
- const hashedSeed = new Uint8Array(node_crypto.createHash("sha512").update(seed).digest());
1348
- const secretKey = hashedSeed.slice(0, 32);
1349
- return new Uint8Array(tweetnacl.box.keyPair.fromSecretKey(secretKey).publicKey);
1350
- }
1351
- function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
1352
- const ephemeralKeyPair = tweetnacl.box.keyPair();
1353
- const nonce = getRandomBytes(tweetnacl.box.nonceLength);
1354
- const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
1355
- const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
1356
- result.set(ephemeralKeyPair.publicKey, 0);
1357
- result.set(nonce, ephemeralKeyPair.publicKey.length);
1358
- result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
1359
- return result;
1360
- }
1361
- function encryptLegacy(data, secret) {
1362
- const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
1363
- const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
1364
- const result = new Uint8Array(nonce.length + encrypted.length);
1365
- result.set(nonce);
1366
- result.set(encrypted, nonce.length);
1367
- return result;
1368
- }
1369
- function decryptLegacy(data, secret) {
1370
- const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
1371
- const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
1372
- const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
1373
- if (!decrypted) {
1374
- return null;
1375
- }
1376
- return JSON.parse(new TextDecoder().decode(decrypted));
1377
- }
1378
- function decryptBlob(bundle, key) {
1379
- if (bundle.length < tweetnacl.secretbox.nonceLength + 16) {
1380
- return null;
1381
- }
1382
- const nonce = bundle.slice(0, tweetnacl.secretbox.nonceLength);
1383
- const ciphertext = bundle.slice(tweetnacl.secretbox.nonceLength);
1384
- const decrypted = tweetnacl.secretbox.open(ciphertext, nonce, key);
1385
- if (!decrypted) {
1386
- return null;
1387
- }
1388
- return new Uint8Array(decrypted);
1389
- }
1390
- function encryptWithDataKey(data, dataKey) {
1391
- const nonce = getRandomBytes(12);
1392
- const cipher = node_crypto.createCipheriv("aes-256-gcm", dataKey, nonce);
1393
- const plaintext = new TextEncoder().encode(JSON.stringify(data));
1394
- const encrypted = Buffer.concat([
1395
- cipher.update(plaintext),
1396
- cipher.final()
1397
- ]);
1398
- const authTag = cipher.getAuthTag();
1399
- const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
1400
- bundle.set([0], 0);
1401
- bundle.set(nonce, 1);
1402
- bundle.set(new Uint8Array(encrypted), 13);
1403
- bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
1404
- return bundle;
1405
- }
1406
- function decryptWithDataKey(bundle, dataKey) {
1407
- if (bundle.length < 1) {
1408
- return null;
1409
- }
1410
- if (bundle[0] !== 0) {
1411
- return null;
1412
- }
1413
- if (bundle.length < 12 + 16 + 1) {
1414
- return null;
1415
- }
1416
- const nonce = bundle.slice(1, 13);
1417
- const authTag = bundle.slice(bundle.length - 16);
1418
- const ciphertext = bundle.slice(13, bundle.length - 16);
1419
- try {
1420
- const decipher = node_crypto.createDecipheriv("aes-256-gcm", dataKey, nonce);
1421
- decipher.setAuthTag(authTag);
1422
- const decrypted = Buffer.concat([
1423
- decipher.update(ciphertext),
1424
- decipher.final()
1425
- ]);
1426
- return JSON.parse(new TextDecoder().decode(decrypted));
1427
- } catch (error) {
1428
- return null;
1429
- }
1430
- }
1431
- function encrypt(key, variant, data) {
1432
- if (variant === "legacy") {
1433
- return encryptLegacy(data, key);
1434
- } else {
1435
- return encryptWithDataKey(data, key);
1436
- }
1437
- }
1438
- function decrypt(key, variant, data) {
1439
- if (variant === "legacy") {
1440
- return decryptLegacy(data, key);
1441
- } else {
1442
- return decryptWithDataKey(data, key);
1443
- }
1444
- }
1445
-
1446
759
  async function delay(ms) {
1447
760
  return new Promise((resolve) => setTimeout(resolve, ms));
1448
761
  }
@@ -1474,7 +787,7 @@ function createBackoff(opts) {
1474
787
  }
1475
788
  let backoff = createBackoff({
1476
789
  onError: (e, failuresCount) => {
1477
- logger.debug(`[BACKOFF] retry ${failuresCount}:`, e?.message || e);
790
+ persistence.logger.debug(`[BACKOFF] retry ${failuresCount}:`, e?.message || e);
1478
791
  }
1479
792
  });
1480
793
 
@@ -1572,7 +885,7 @@ class RpcHandlerManager {
1572
885
  this.scopePrefix = config.scopePrefix;
1573
886
  this.encryptionKey = config.encryptionKey;
1574
887
  this.encryptionVariant = config.encryptionVariant;
1575
- this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
888
+ this.logger = config.logger || ((msg, data) => persistence.logger.debug(msg, data));
1576
889
  }
1577
890
  /**
1578
891
  * Register an RPC handler for a specific method
@@ -1600,14 +913,14 @@ class RpcHandlerManager {
1600
913
  if (!handler) {
1601
914
  this.logger("[RPC] [ERROR] Method not found", { method: request.method });
1602
915
  const errorResponse = { error: "Method not found" };
1603
- const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
916
+ const encryptedError = persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
1604
917
  return encryptedError;
1605
918
  }
1606
- const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
919
+ const decryptedParams = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(request.params));
1607
920
  this.logger("[RPC] Calling handler", { method: request.method });
1608
921
  const result = await handler(decryptedParams);
1609
922
  this.logger("[RPC] Handler returned", { method: request.method, hasResult: result !== void 0 });
1610
- const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
923
+ const encryptedResponse = persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, result));
1611
924
  this.logger("[RPC] Sending encrypted response", { method: request.method, responseLength: encryptedResponse.length });
1612
925
  return encryptedResponse;
1613
926
  } catch (error) {
@@ -1615,7 +928,7 @@ class RpcHandlerManager {
1615
928
  const errorResponse = {
1616
929
  error: error instanceof Error ? error.message : "Unknown error"
1617
930
  };
1618
- return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
931
+ return persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
1619
932
  }
1620
933
  }
1621
934
  onSocketConnect(socket) {
@@ -1708,14 +1021,14 @@ class RpcHandlerManager {
1708
1021
  }
1709
1022
  }
1710
1023
 
1711
- const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-CZIwVSN2.cjs', document.baseURI).href))));
1024
+ const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BGHnQXl5.cjs', document.baseURI).href))));
1712
1025
  function projectPath() {
1713
- const path = path$1.resolve(__dirname$1, "..");
1714
- return path;
1026
+ const path$1 = path.resolve(__dirname$1, "..");
1027
+ return path$1;
1715
1028
  }
1716
1029
 
1717
1030
  function run$1(args, options) {
1718
- const RUNNER_PATH = path$1.resolve(path$1.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
1031
+ const RUNNER_PATH = path.resolve(path.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
1719
1032
  return new Promise((resolve2, reject) => {
1720
1033
  const child = spawn.spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
1721
1034
  stdio: ["pipe", "pipe", "pipe"],
@@ -1744,9 +1057,9 @@ function run$1(args, options) {
1744
1057
  }
1745
1058
 
1746
1059
  function getBinaryPath() {
1747
- const platformName = os$1.platform();
1060
+ const platformName = os.platform();
1748
1061
  const binaryName = platformName === "win32" ? "difft.exe" : "difft";
1749
- return path$1.resolve(path$1.join(projectPath(), "tools", "unpacked", binaryName));
1062
+ return path.resolve(path.join(projectPath(), "tools", "unpacked", binaryName));
1750
1063
  }
1751
1064
  function run(args, options) {
1752
1065
  const binaryPath = getBinaryPath();
@@ -1783,9 +1096,9 @@ function run(args, options) {
1783
1096
  }
1784
1097
 
1785
1098
  function validatePath(targetPath, workingDirectory) {
1786
- const resolvedTarget = path$1.resolve(workingDirectory, targetPath);
1787
- const resolvedWorkingDir = path$1.resolve(workingDirectory);
1788
- if (!resolvedTarget.startsWith(resolvedWorkingDir + path$1.sep) && resolvedTarget !== resolvedWorkingDir) {
1099
+ const resolvedTarget = path.resolve(workingDirectory, targetPath);
1100
+ const resolvedWorkingDir = path.resolve(workingDirectory);
1101
+ if (!resolvedTarget.startsWith(resolvedWorkingDir + path.sep) && resolvedTarget !== resolvedWorkingDir) {
1789
1102
  return {
1790
1103
  valid: false,
1791
1104
  resolvedPath: resolvedTarget,
@@ -1858,9 +1171,9 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1858
1171
  const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
1859
1172
  await Promise.all(entries.map(async (entry) => {
1860
1173
  if (!entry.isFile() || !/^\..+\.[A-Za-z0-9_-]{8,80}\.part$/.test(entry.name)) return;
1861
- const path = path$1.join(dir, entry.name);
1862
- const fileStat = await fs.stat(path).catch(() => null);
1863
- if (fileStat && fileStat.mtimeMs < cutoff) await fs.unlink(path).catch(() => {
1174
+ const path$1 = path.join(dir, entry.name);
1175
+ const fileStat = await fs.stat(path$1).catch(() => null);
1176
+ if (fileStat && fileStat.mtimeMs < cutoff) await fs.unlink(path$1).catch(() => {
1864
1177
  });
1865
1178
  }));
1866
1179
  };
@@ -1868,12 +1181,12 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1868
1181
  let tempPath = null;
1869
1182
  try {
1870
1183
  const buffer = decodeUploadBase64(data.content, MAX_USER_UPLOAD_BYTES);
1871
- const dir = path$1.join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1184
+ const dir = path.join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1872
1185
  await fs.mkdir(dir, { recursive: true });
1873
1186
  const name = safeFileName(data.name);
1874
1187
  const token = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
1875
- const target = path$1.join(dir, uniqueFileName(name, token));
1876
- tempPath = path$1.join(dir, `.${name}.legacy_${crypto.randomUUID().replace(/-/g, "")}.part`);
1188
+ const target = path.join(dir, uniqueFileName(name, token));
1189
+ tempPath = path.join(dir, `.${name}.legacy_${crypto.randomUUID().replace(/-/g, "")}.part`);
1877
1190
  await fs.writeFile(tempPath, buffer, { flag: "wx" });
1878
1191
  await fs.rename(tempPath, target);
1879
1192
  tempPath = null;
@@ -1894,12 +1207,12 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1894
1207
  }
1895
1208
  if (active.has(uploadId)) throw new Error("Upload id is already active");
1896
1209
  if (active.size >= MAX_ACTIVE_UPLOADS) throw new Error("Too many active uploads");
1897
- const dir = path$1.join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1210
+ const dir = path.join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1898
1211
  await fs.mkdir(dir, { recursive: true });
1899
1212
  await sweepExpiredFiles(dir);
1900
1213
  const name = safeFileName(data.name);
1901
- const targetPath = path$1.join(dir, uniqueFileName(name, crypto.randomUUID().replace(/-/g, "").slice(0, 12)));
1902
- const tempPath = path$1.join(dir, `.${name}.${uploadId}.part`);
1214
+ const targetPath = path.join(dir, uniqueFileName(name, crypto.randomUUID().replace(/-/g, "").slice(0, 12)));
1215
+ const tempPath = path.join(dir, `.${name}.${uploadId}.part`);
1903
1216
  await fs.writeFile(tempPath, Buffer.alloc(0), { flag: "wx" });
1904
1217
  active.set(uploadId, { tempPath, targetPath, totalSize: data.totalSize, received: 0, touchedAt: now() });
1905
1218
  return { success: true, size: 0 };
@@ -1945,7 +1258,7 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1945
1258
  const execAsync = util.promisify(child_process.exec);
1946
1259
  function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
1947
1260
  rpcHandlerManager.registerHandler("bash", async (data) => {
1948
- logger.debug("Shell command request:", data.command);
1261
+ persistence.logger.debug("Shell command request:", data.command);
1949
1262
  if (data.cwd && data.cwd !== "/") {
1950
1263
  const validation = validatePath(data.cwd, workingDirectory);
1951
1264
  if (!validation.valid) {
@@ -1961,16 +1274,16 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
1961
1274
  windowsHide: true
1962
1275
  // Prevent cmd.exe popup on Windows for every RPC bash call
1963
1276
  };
1964
- logger.debug("Shell command executing...", { cwd: options.cwd, timeout: options.timeout });
1277
+ persistence.logger.debug("Shell command executing...", { cwd: options.cwd, timeout: options.timeout });
1965
1278
  const { stdout, stderr } = await execAsync(data.command, options);
1966
- logger.debug("Shell command executed, processing result...");
1279
+ persistence.logger.debug("Shell command executed, processing result...");
1967
1280
  const result = {
1968
1281
  success: true,
1969
1282
  stdout: stdout ? stdout.toString() : "",
1970
1283
  stderr: stderr ? stderr.toString() : "",
1971
1284
  exitCode: 0
1972
1285
  };
1973
- logger.debug("Shell command result:", {
1286
+ persistence.logger.debug("Shell command result:", {
1974
1287
  success: true,
1975
1288
  exitCode: 0,
1976
1289
  stdoutLen: result.stdout.length,
@@ -1987,7 +1300,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
1987
1300
  exitCode: typeof execError.code === "number" ? execError.code : -1,
1988
1301
  error: "Command timed out"
1989
1302
  };
1990
- logger.debug("Shell command timed out:", {
1303
+ persistence.logger.debug("Shell command timed out:", {
1991
1304
  success: false,
1992
1305
  exitCode: result2.exitCode,
1993
1306
  error: "Command timed out"
@@ -2001,7 +1314,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2001
1314
  exitCode: typeof execError.code === "number" ? execError.code : 1,
2002
1315
  error: execError.message || "Command failed"
2003
1316
  };
2004
- logger.debug("Shell command failed:", {
1317
+ persistence.logger.debug("Shell command failed:", {
2005
1318
  success: false,
2006
1319
  exitCode: result.exitCode,
2007
1320
  error: result.error,
@@ -2012,7 +1325,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2012
1325
  }
2013
1326
  });
2014
1327
  rpcHandlerManager.registerHandler("readFile", async (data) => {
2015
- logger.debug("Read file request:", data.path);
1328
+ persistence.logger.debug("Read file request:", data.path);
2016
1329
  const validation = validatePath(data.path, workingDirectory);
2017
1330
  if (!validation.valid) {
2018
1331
  return { success: false, error: validation.error };
@@ -2022,12 +1335,12 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2022
1335
  const content = buffer.toString("base64");
2023
1336
  return { success: true, content };
2024
1337
  } catch (error) {
2025
- logger.debug("Failed to read file:", error);
1338
+ persistence.logger.debug("Failed to read file:", error);
2026
1339
  return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
2027
1340
  }
2028
1341
  });
2029
1342
  rpcHandlerManager.registerHandler("writeFile", async (data) => {
2030
- logger.debug("Write file request:", data.path);
1343
+ persistence.logger.debug("Write file request:", data.path);
2031
1344
  const validation = validatePath(data.path, workingDirectory);
2032
1345
  if (!validation.valid) {
2033
1346
  return { success: false, error: validation.error };
@@ -2072,15 +1385,15 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2072
1385
  const hash = crypto.createHash("sha256").update(buffer).digest("hex");
2073
1386
  return { success: true, hash };
2074
1387
  } catch (error) {
2075
- logger.debug("Failed to write file:", error);
1388
+ persistence.logger.debug("Failed to write file:", error);
2076
1389
  return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
2077
1390
  }
2078
1391
  });
2079
- const uploadHandlers = createUploadFileHandlers(configuration.happyHomeDir);
1392
+ const uploadHandlers = createUploadFileHandlers(persistence.configuration.happyHomeDir);
2080
1393
  rpcHandlerManager.registerHandler("uploadFile", uploadHandlers.uploadFile);
2081
1394
  rpcHandlerManager.registerHandler("uploadFileChunk", uploadHandlers.uploadFileChunk);
2082
1395
  rpcHandlerManager.registerHandler("listDirectory", async (data) => {
2083
- logger.debug("List directory request:", data.path);
1396
+ persistence.logger.debug("List directory request:", data.path);
2084
1397
  const validation = validatePath(data.path, workingDirectory);
2085
1398
  if (!validation.valid) {
2086
1399
  return { success: false, error: validation.error };
@@ -2090,7 +1403,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2090
1403
  const entries = await fs.readdir(directoryPath, { withFileTypes: true });
2091
1404
  const directoryEntries = await Promise.all(
2092
1405
  entries.map(async (entry) => {
2093
- const fullPath = path$1.join(directoryPath, entry.name);
1406
+ const fullPath = path.join(directoryPath, entry.name);
2094
1407
  let type = "other";
2095
1408
  let size;
2096
1409
  let modified;
@@ -2104,7 +1417,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2104
1417
  size = stats.size;
2105
1418
  modified = stats.mtime.getTime();
2106
1419
  } catch (error) {
2107
- logger.debug(`Failed to stat ${fullPath}:`, error);
1420
+ persistence.logger.debug(`Failed to stat ${fullPath}:`, error);
2108
1421
  }
2109
1422
  return {
2110
1423
  name: entry.name,
@@ -2121,36 +1434,36 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2121
1434
  });
2122
1435
  return { success: true, entries: directoryEntries };
2123
1436
  } catch (error) {
2124
- logger.debug("Failed to list directory:", error);
1437
+ persistence.logger.debug("Failed to list directory:", error);
2125
1438
  return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
2126
1439
  }
2127
1440
  });
2128
1441
  rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
2129
- logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
1442
+ persistence.logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
2130
1443
  const validation = validatePath(data.path, workingDirectory);
2131
1444
  if (!validation.valid) {
2132
1445
  return { success: false, error: validation.error };
2133
1446
  }
2134
- async function buildTree(path, name, currentDepth) {
1447
+ async function buildTree(path$1, name, currentDepth) {
2135
1448
  try {
2136
- const stats = await fs.stat(path);
1449
+ const stats = await fs.stat(path$1);
2137
1450
  const node = {
2138
1451
  name,
2139
- path,
1452
+ path: path$1,
2140
1453
  type: stats.isDirectory() ? "directory" : "file",
2141
1454
  size: stats.size,
2142
1455
  modified: stats.mtime.getTime()
2143
1456
  };
2144
1457
  if (stats.isDirectory() && currentDepth < data.maxDepth) {
2145
- const entries = await fs.readdir(path, { withFileTypes: true });
1458
+ const entries = await fs.readdir(path$1, { withFileTypes: true });
2146
1459
  const children = [];
2147
1460
  await Promise.all(
2148
1461
  entries.map(async (entry) => {
2149
1462
  if (entry.isSymbolicLink()) {
2150
- logger.debug(`Skipping symlink: ${path$1.join(path, entry.name)}`);
1463
+ persistence.logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
2151
1464
  return;
2152
1465
  }
2153
- const childPath = path$1.join(path, entry.name);
1466
+ const childPath = path.join(path$1, entry.name);
2154
1467
  const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
2155
1468
  if (childNode) {
2156
1469
  children.push(childNode);
@@ -2166,7 +1479,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2166
1479
  }
2167
1480
  return node;
2168
1481
  } catch (error) {
2169
- logger.debug(`Failed to process ${path}:`, error instanceof Error ? error.message : String(error));
1482
+ persistence.logger.debug(`Failed to process ${path$1}:`, error instanceof Error ? error.message : String(error));
2170
1483
  return null;
2171
1484
  }
2172
1485
  }
@@ -2182,12 +1495,12 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2182
1495
  }
2183
1496
  return { success: true, tree };
2184
1497
  } catch (error) {
2185
- logger.debug("Failed to get directory tree:", error);
1498
+ persistence.logger.debug("Failed to get directory tree:", error);
2186
1499
  return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
2187
1500
  }
2188
1501
  });
2189
1502
  rpcHandlerManager.registerHandler("ripgrep", async (data) => {
2190
- logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
1503
+ persistence.logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
2191
1504
  if (data.cwd) {
2192
1505
  const validation = validatePath(data.cwd, workingDirectory);
2193
1506
  if (!validation.valid) {
@@ -2204,7 +1517,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2204
1517
  stderr: result.stderr.toString()
2205
1518
  };
2206
1519
  } catch (error) {
2207
- logger.debug("Failed to run ripgrep:", error);
1520
+ persistence.logger.debug("Failed to run ripgrep:", error);
2208
1521
  return {
2209
1522
  success: false,
2210
1523
  error: error instanceof Error ? error.message : "Failed to run ripgrep"
@@ -2212,7 +1525,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2212
1525
  }
2213
1526
  });
2214
1527
  rpcHandlerManager.registerHandler("difftastic", async (data) => {
2215
- logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
1528
+ persistence.logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
2216
1529
  if (data.cwd) {
2217
1530
  const validation = validatePath(data.cwd, workingDirectory);
2218
1531
  if (!validation.valid) {
@@ -2229,7 +1542,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2229
1542
  stderr: result.stderr.toString()
2230
1543
  };
2231
1544
  } catch (error) {
2232
- logger.debug("Failed to run difftastic:", error);
1545
+ persistence.logger.debug("Failed to run difftastic:", error);
2233
1546
  return {
2234
1547
  success: false,
2235
1548
  error: error instanceof Error ? error.message : "Failed to run difftastic"
@@ -2312,7 +1625,7 @@ function calculateCost(usage, modelId) {
2312
1625
  }
2313
1626
 
2314
1627
  function hasNetworkConnectivity() {
2315
- const interfaces = os$1.networkInterfaces();
1628
+ const interfaces = os.networkInterfaces();
2316
1629
  for (const name of Object.keys(interfaces)) {
2317
1630
  for (const iface of interfaces[name] || []) {
2318
1631
  if (!iface.internal && iface.family === "IPv4") return true;
@@ -3326,13 +2639,13 @@ class ApiSessionClient extends node_events.EventEmitter {
3326
2639
  scopePrefix: this.sessionId,
3327
2640
  encryptionKey: this.encryptionKey,
3328
2641
  encryptionVariant: this.encryptionVariant,
3329
- logger: (msg, data) => logger.debug(msg, data)
2642
+ logger: (msg, data) => persistence.logger.debug(msg, data)
3330
2643
  });
3331
2644
  registerCommonHandlers(this.rpcHandlerManager, this.metadata.path);
3332
2645
  this.socket = this.createControlSocket();
3333
2646
  const controlSocket = this.socket;
3334
2647
  this.socket.on("connect", () => {
3335
- logger.debug("Socket connected successfully");
2648
+ persistence.logger.debug("Socket connected successfully");
3336
2649
  if (this.reconnectInterval) {
3337
2650
  clearInterval(this.reconnectInterval);
3338
2651
  this.reconnectInterval = null;
@@ -3345,15 +2658,15 @@ class ApiSessionClient extends node_events.EventEmitter {
3345
2658
  callback(await this.rpcHandlerManager.handleRequest(data));
3346
2659
  });
3347
2660
  this.socket.on("disconnect", (reason) => {
3348
- logger.debug(`[API] Socket disconnected: ${reason}`);
2661
+ persistence.logger.debug(`[API] Socket disconnected: ${reason}`);
3349
2662
  this.rpcHandlerManager.onSocketDisconnect(controlSocket);
3350
2663
  this.startSmartReconnect();
3351
2664
  });
3352
2665
  this.socket.on("connect_error", (error) => {
3353
- logger.debug("[API] Socket connection error:", error);
2666
+ persistence.logger.debug("[API] Socket connection error:", error);
3354
2667
  this.rpcHandlerManager.onSocketDisconnect(controlSocket);
3355
2668
  if (error.message === "Session archived") {
3356
- logger.debug("[API] Archived session rejected during reconnect; exiting");
2669
+ persistence.logger.debug("[API] Archived session rejected during reconnect; exiting");
3357
2670
  this.emit("archived");
3358
2671
  return;
3359
2672
  }
@@ -3361,14 +2674,14 @@ class ApiSessionClient extends node_events.EventEmitter {
3361
2674
  });
3362
2675
  this.socket.on("session-archive", ({ sessionId }) => {
3363
2676
  if (sessionId !== this.sessionId) return;
3364
- logger.debug("[SOCKET] Server archived this session; exiting...");
2677
+ persistence.logger.debug("[SOCKET] Server archived this session; exiting...");
3365
2678
  this.emit("archived");
3366
2679
  });
3367
2680
  this.socket.on("update", (data) => {
3368
2681
  try {
3369
- logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
2682
+ persistence.logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
3370
2683
  if (!data.body) {
3371
- logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
2684
+ persistence.logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
3372
2685
  return;
3373
2686
  }
3374
2687
  if (data.body.t === "new-message") {
@@ -3386,41 +2699,41 @@ class ApiSessionClient extends node_events.EventEmitter {
3386
2699
  this.lastSeq = messageSeq;
3387
2700
  return;
3388
2701
  }
3389
- const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
3390
- logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
2702
+ const body = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(data.body.message.content.c));
2703
+ persistence.logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
3391
2704
  this.routeIncomingMessage(body, localId);
3392
2705
  if (localId) this.rememberRoutedInbound(localId);
3393
2706
  this.lastSeq = messageSeq;
3394
2707
  } else if (data.body.t === "update-session") {
3395
2708
  if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
3396
- this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
2709
+ this.metadata = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(data.body.metadata.value));
3397
2710
  this.metadataVersion = data.body.metadata.version;
3398
2711
  const meta = this.metadata;
3399
2712
  if (meta?.lifecycleState === "archiveRequested" || meta?.lifecycleState === "archived") {
3400
2713
  if (this.ignoreArchiveSignal) {
3401
- logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}) but suppressed for reconnect`);
2714
+ persistence.logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}) but suppressed for reconnect`);
3402
2715
  this.ignoreArchiveSignal = false;
3403
2716
  } else {
3404
- logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}), exiting...`);
2717
+ persistence.logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}), exiting...`);
3405
2718
  this.emit("archived");
3406
2719
  }
3407
2720
  }
3408
2721
  }
3409
2722
  if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
3410
- this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
2723
+ this.agentState = data.body.agentState.value ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(data.body.agentState.value)) : null;
3411
2724
  this.agentStateVersion = data.body.agentState.version;
3412
2725
  }
3413
2726
  } else if (data.body.t === "update-machine") {
3414
- logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
2727
+ persistence.logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
3415
2728
  } else {
3416
2729
  this.emit("message", data.body);
3417
2730
  }
3418
2731
  } catch (error) {
3419
- logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
2732
+ persistence.logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
3420
2733
  }
3421
2734
  });
3422
2735
  this.socket.on("error", (error) => {
3423
- logger.debug("[API] Socket error:", error);
2736
+ persistence.logger.debug("[API] Socket error:", error);
3424
2737
  });
3425
2738
  this.socket.on("server-draining", (data) => {
3426
2739
  const parsed = ReleaseDrainNoticeSchema.safeParse(data);
@@ -3430,12 +2743,12 @@ class ApiSessionClient extends node_events.EventEmitter {
3430
2743
  this.socket.connect();
3431
2744
  }
3432
2745
  createControlSocket(handover) {
3433
- return socket_ioClient.io(configuration.serverUrl, {
2746
+ return socket_ioClient.io(persistence.configuration.serverUrl, {
3434
2747
  auth: {
3435
2748
  token: this.token,
3436
2749
  clientType: "session-scoped",
3437
2750
  sessionId: this.sessionId,
3438
- happyClient: `cli-coding-session/${configuration.currentCliVersion}`,
2751
+ happyClient: `cli-coding-session/${persistence.configuration.currentCliVersion}`,
3439
2752
  ...handover ? { handoverEpoch: handover.epoch } : {}
3440
2753
  },
3441
2754
  ...handover ? { query: { vh_slot: handover.candidateSlot }, forceNew: true } : {},
@@ -3516,7 +2829,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3516
2829
  this.rpcHandlerManager.onSocketDisconnect(candidate);
3517
2830
  candidate.close();
3518
2831
  if (previous.connected) previous.emit("release-handover-result", { result: "failed", durationMs: Date.now() - startedAt });
3519
- logger.debug(`[API] Release handover failed: ${error instanceof Error ? error.message : error}`);
2832
+ persistence.logger.debug(`[API] Release handover failed: ${error instanceof Error ? error.message : error}`);
3520
2833
  }
3521
2834
  }
3522
2835
  onUserMessage(callback) {
@@ -3550,7 +2863,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3550
2863
  * presigned URL that does not accept extra headers.
3551
2864
  */
3552
2865
  async downloadAttachment(ref) {
3553
- const requestUrl = `${configuration.serverUrl}/v1/sessions/${this.sessionId}/attachments/request-download`;
2866
+ const requestUrl = `${persistence.configuration.serverUrl}/v1/sessions/${this.sessionId}/attachments/request-download`;
3554
2867
  const requestRes = await axios.post(
3555
2868
  requestUrl,
3556
2869
  { ref },
@@ -3563,7 +2876,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3563
2876
  if (typeof downloadUrl !== "string") {
3564
2877
  throw new Error("request-download returned no downloadUrl");
3565
2878
  }
3566
- const isServerUrl = downloadUrl.startsWith(configuration.serverUrl);
2879
+ const isServerUrl = downloadUrl.startsWith(persistence.configuration.serverUrl);
3567
2880
  const headers = {};
3568
2881
  if (isServerUrl) {
3569
2882
  headers["Authorization"] = `Bearer ${this.token}`;
@@ -3585,7 +2898,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3585
2898
  async downloadAndDecryptAttachment(ref) {
3586
2899
  const encrypted = await this.downloadAttachment(ref);
3587
2900
  const key = await this.getBlobKey();
3588
- const decrypted = decryptBlob(encrypted, key);
2901
+ const decrypted = persistence.decryptBlob(encrypted, key);
3589
2902
  return decrypted;
3590
2903
  }
3591
2904
  /**
@@ -3614,7 +2927,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3614
2927
  return {
3615
2928
  "Authorization": `Bearer ${this.token}`,
3616
2929
  "Content-Type": "application/json",
3617
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
2930
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
3618
2931
  };
3619
2932
  }
3620
2933
  routeIncomingMessage(message, sourceLocalId) {
@@ -3630,7 +2943,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3630
2943
  }
3631
2944
  const fileResult = FileEventMessageSchema.safeParse(message);
3632
2945
  if (fileResult.success) {
3633
- logger.debug(`[API] Received file event: ${fileResult.data.content.data.ev.name} (ref: ${fileResult.data.content.data.ev.ref})`);
2946
+ persistence.logger.debug(`[API] Received file event: ${fileResult.data.content.data.ev.name} (ref: ${fileResult.data.content.data.ev.ref})`);
3634
2947
  if (this.pendingFileEventCallback) {
3635
2948
  this.pendingFileEventCallback(fileResult.data);
3636
2949
  } else {
@@ -3644,12 +2957,12 @@ class ApiSessionClient extends node_events.EventEmitter {
3644
2957
  const skipRouting = this.skipInitialMessages;
3645
2958
  if (skipRouting) {
3646
2959
  this.skipInitialMessages = false;
3647
- logger.debug("[API] Reconnect mode: skipping existing messages, advancing lastSeq");
2960
+ persistence.logger.debug("[API] Reconnect mode: skipping existing messages, advancing lastSeq");
3648
2961
  }
3649
2962
  let afterSeq = this.lastSeq;
3650
2963
  while (true) {
3651
2964
  const response = await axios.get(
3652
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
2965
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3653
2966
  {
3654
2967
  params: {
3655
2968
  after_seq: afterSeq,
@@ -3676,11 +2989,11 @@ class ApiSessionClient extends node_events.EventEmitter {
3676
2989
  continue;
3677
2990
  }
3678
2991
  try {
3679
- const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(message.content.c));
2992
+ const body = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(message.content.c));
3680
2993
  this.routeIncomingMessage(body, message.localId);
3681
2994
  if (message.localId) this.rememberRoutedInbound(message.localId);
3682
2995
  } catch (error) {
3683
- logger.debug("[API] Failed to decrypt fetched message", {
2996
+ persistence.logger.debug("[API] Failed to decrypt fetched message", {
3684
2997
  sessionId: this.sessionId,
3685
2998
  seq: message.seq,
3686
2999
  error
@@ -3690,7 +3003,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3690
3003
  this.lastSeq = Math.max(this.lastSeq, maxSeq);
3691
3004
  const hasMore = !!response.data.hasMore;
3692
3005
  if (hasMore && maxSeq === afterSeq) {
3693
- logger.debug("[API] fetchMessages pagination stalled, stopping to avoid infinite loop", {
3006
+ persistence.logger.debug("[API] fetchMessages pagination stalled, stopping to avoid infinite loop", {
3694
3007
  sessionId: this.sessionId,
3695
3008
  afterSeq
3696
3009
  });
@@ -3709,7 +3022,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3709
3022
  const batchSize = Math.min(this.pendingOutbox.length, ApiSessionClient.MAX_OUTBOX_BATCH_SIZE);
3710
3023
  const batch = this.pendingOutbox.slice(0, batchSize);
3711
3024
  const response = await axios.post(
3712
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3025
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3713
3026
  {
3714
3027
  messages: batch
3715
3028
  },
@@ -3744,7 +3057,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3744
3057
  if (!machineId || this.relaySocket?.connected) return;
3745
3058
  try {
3746
3059
  const response = await fetch(
3747
- `${configuration.serverUrl}/v1/relays/sessions/${encodeURIComponent(this.sessionId)}/machines/${encodeURIComponent(machineId)}`,
3060
+ `${persistence.configuration.serverUrl}/v1/relays/sessions/${encodeURIComponent(this.sessionId)}/machines/${encodeURIComponent(machineId)}`,
3748
3061
  { headers: this.authHeaders(), cache: "no-store" }
3749
3062
  );
3750
3063
  if (!response.ok) return;
@@ -3763,7 +3076,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3763
3076
  relaySocket.on("connect", () => {
3764
3077
  if (this.relaySocket !== relaySocket) return;
3765
3078
  this.rpcHandlerManager.onSocketConnect(relaySocket);
3766
- logger.debug(`[API] Session connected to regional relay ${assignment.relayId}`);
3079
+ persistence.logger.debug(`[API] Session connected to regional relay ${assignment.relayId}`);
3767
3080
  });
3768
3081
  relaySocket.on("rpc-request", async (data, callback) => {
3769
3082
  callback(await this.rpcHandlerManager.handleRequest(data));
@@ -3785,7 +3098,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3785
3098
  for (const item of batch) this.rememberDirectInbound(item.localId);
3786
3099
  try {
3787
3100
  const persisted = await axios.post(
3788
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3101
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3789
3102
  { messages: batch },
3790
3103
  { headers: this.authHeaders(), timeout: 6e4 }
3791
3104
  );
@@ -3797,7 +3110,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3797
3110
  this.lastSeq = stored.reduce((max, message) => Math.max(max, message.seq), this.lastSeq);
3798
3111
  for (const item of batch) {
3799
3112
  if (this.routedInboundLocalIds.has(item.localId)) continue;
3800
- const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(item.content));
3113
+ const body = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(item.content));
3801
3114
  this.routeIncomingMessage(body, item.localId);
3802
3115
  this.rememberRoutedInbound(item.localId);
3803
3116
  }
@@ -3811,7 +3124,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3811
3124
  if (this.relaySocket === relaySocket) this.rpcHandlerManager.onSocketDisconnect(relaySocket);
3812
3125
  });
3813
3126
  } catch (error) {
3814
- logger.debug(`[API] Session regional relay unavailable: ${error instanceof Error ? error.message : error}`);
3127
+ persistence.logger.debug(`[API] Session regional relay unavailable: ${error instanceof Error ? error.message : error}`);
3815
3128
  }
3816
3129
  }
3817
3130
  pushCommittedMessagesToRelay(batch, messages) {
@@ -3831,7 +3144,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3831
3144
  }
3832
3145
  }
3833
3146
  enqueueMessage(content, invalidate = true, localId) {
3834
- const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
3147
+ const encrypted = persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, content));
3835
3148
  this.pendingOutbox.push({
3836
3149
  content: encrypted,
3837
3150
  // Deterministic localIds (terminal mirror, B-105) ride the server's
@@ -3873,7 +3186,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3873
3186
  while (!reachedBoundary && pages < MAX_SCAN_PAGES) {
3874
3187
  pages += 1;
3875
3188
  const response = await axios.get(
3876
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3189
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3877
3190
  {
3878
3191
  params: { before_seq: beforeSeq, limit: 100 },
3879
3192
  headers: this.authHeaders(),
@@ -3885,9 +3198,9 @@ class ApiSessionClient extends node_events.EventEmitter {
3885
3198
  const records = messages.map((message) => {
3886
3199
  let body = null;
3887
3200
  try {
3888
- body = message.content?.t === "encrypted" ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(message.content.c)) : null;
3201
+ body = message.content?.t === "encrypted" ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(message.content.c)) : null;
3889
3202
  } catch (error) {
3890
- logger.debug("[API] Failed to decrypt during undelivered scan:", error);
3203
+ persistence.logger.debug("[API] Failed to decrypt during undelivered scan:", error);
3891
3204
  }
3892
3205
  return { seq: message.seq, localId: message.localId, body };
3893
3206
  });
@@ -3900,11 +3213,11 @@ class ApiSessionClient extends node_events.EventEmitter {
3900
3213
  beforeSeq = oldest;
3901
3214
  }
3902
3215
  } catch (error) {
3903
- logger.debug("[API] cancelUndeliveredQueuedInputs scan failed:", error);
3216
+ persistence.logger.debug("[API] cancelUndeliveredQueuedInputs scan failed:", error);
3904
3217
  return;
3905
3218
  }
3906
3219
  if (!reachedBoundary) {
3907
- logger.debug("[API] cancelUndeliveredQueuedInputs found no turn boundary; leaving messages as-is");
3220
+ persistence.logger.debug("[API] cancelUndeliveredQueuedInputs found no turn boundary; leaving messages as-is");
3908
3221
  return;
3909
3222
  }
3910
3223
  undelivered.reverse();
@@ -3926,7 +3239,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3926
3239
  const eventId = typeof body.uuid === "string" ? body.uuid : typeof body.message.id === "string" ? body.message.id : void 0;
3927
3240
  this.sendUsageData(body.message.usage, body.message.model, eventId);
3928
3241
  } catch (error) {
3929
- logger.debug("[SOCKET] Failed to send usage data:", error);
3242
+ persistence.logger.debug("[SOCKET] Failed to send usage data:", error);
3930
3243
  }
3931
3244
  }
3932
3245
  if (body.type === "summary" && "summary" in body && "leafUuid" in body) {
@@ -3993,7 +3306,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3993
3306
  sentFrom: "cli"
3994
3307
  }
3995
3308
  };
3996
- logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
3309
+ persistence.logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
3997
3310
  this.enqueueMessage(content);
3998
3311
  }
3999
3312
  sendSessionEvent(event, id) {
@@ -4012,7 +3325,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4012
3325
  */
4013
3326
  keepAlive(thinking, mode) {
4014
3327
  if (process.env.DEBUG) {
4015
- logger.debug(`[API] Sending keep alive message: ${thinking}`);
3328
+ persistence.logger.debug(`[API] Sending keep alive message: ${thinking}`);
4016
3329
  }
4017
3330
  this.socket.volatile.emit("session-alive", {
4018
3331
  sid: this.sessionId,
@@ -4041,11 +3354,11 @@ class ApiSessionClient extends node_events.EventEmitter {
4041
3354
  // The frame object, NOT a JSON string: `encrypt` serializes
4042
3355
  // internally, so pre-stringifying would double-encode and
4043
3356
  // escape every quote in the streamed text.
4044
- payload: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, frame)),
3357
+ payload: persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, frame)),
4045
3358
  enc: true
4046
3359
  });
4047
3360
  } catch (error) {
4048
- logger.debug(`[API] Failed to relay stream frame: ${error}`);
3361
+ persistence.logger.debug(`[API] Failed to relay stream frame: ${error}`);
4049
3362
  }
4050
3363
  }
4051
3364
  /**
@@ -4062,12 +3375,12 @@ class ApiSessionClient extends node_events.EventEmitter {
4062
3375
  * Returns delivery info for the MCP tool to report back to the model.
4063
3376
  */
4064
3377
  pushClipboard(text) {
4065
- const prepared = limits.prepareClipboardText(text);
3378
+ const prepared = persistence.prepareClipboardText(text);
4066
3379
  if (!this.socket.connected) {
4067
3380
  return { delivered: false, truncated: prepared.truncated, totalBytes: prepared.totalBytes };
4068
3381
  }
4069
3382
  this.socket.emit("clipboard-push", {
4070
- payload: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, prepared.text)),
3383
+ payload: persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, prepared.text)),
4071
3384
  enc: true,
4072
3385
  truncated: prepared.truncated,
4073
3386
  totalBytes: prepared.totalBytes
@@ -4093,7 +3406,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4093
3406
  return { delivered: false, error: "not connected to the server" };
4094
3407
  }
4095
3408
  this.socket.emit("file-preview-push", {
4096
- payload: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, path)),
3409
+ payload: persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, path)),
4097
3410
  enc: true,
4098
3411
  mode
4099
3412
  });
@@ -4133,7 +3446,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4133
3446
  output: this.claudeUsageTotals.costOutput
4134
3447
  }
4135
3448
  };
4136
- logger.debugLargeJson("[SOCKET] Sending usage data:", usageReport);
3449
+ persistence.logger.debugLargeJson("[SOCKET] Sending usage data:", usageReport);
4137
3450
  this.socket.emit("usage-report", usageReport);
4138
3451
  }
4139
3452
  /** Save a provider's cumulative session snapshot under one upsert key. */
@@ -4148,7 +3461,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4148
3461
  // The Web treats this sentinel as unknown, not as real $0.00.
4149
3462
  cost: { total: 0 }
4150
3463
  };
4151
- logger.debugLargeJson("[SOCKET] Sending agent usage snapshot:", usageReport);
3464
+ persistence.logger.debugLargeJson("[SOCKET] Sending agent usage snapshot:", usageReport);
4152
3465
  this.socket.emit("usage-report", usageReport);
4153
3466
  return true;
4154
3467
  }
@@ -4181,15 +3494,15 @@ class ApiSessionClient extends node_events.EventEmitter {
4181
3494
  let rateRefusals = 0;
4182
3495
  await backoff(async () => {
4183
3496
  let updated = handler(this.metadata);
4184
- const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
3497
+ const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
4185
3498
  if (answer.result === "success") {
4186
- this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
3499
+ this.metadata = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.metadata));
4187
3500
  this.metadataVersion = answer.version;
4188
3501
  rateRefusals = 0;
4189
3502
  } else if (answer.result === "version-mismatch") {
4190
3503
  if (answer.version > this.metadataVersion) {
4191
3504
  this.metadataVersion = answer.version;
4192
- this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
3505
+ this.metadata = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.metadata));
4193
3506
  }
4194
3507
  throw new Error("Metadata version mismatch");
4195
3508
  } else if (answer.result === "error") {
@@ -4200,7 +3513,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4200
3513
  code: answer.error,
4201
3514
  attempt: rateRefusals,
4202
3515
  sleep: delay,
4203
- log: (message) => logger.debug(message)
3516
+ log: (message) => persistence.logger.debug(message)
4204
3517
  });
4205
3518
  }
4206
3519
  throw new Error("Metadata update failed");
@@ -4213,21 +3526,21 @@ class ApiSessionClient extends node_events.EventEmitter {
4213
3526
  * @param handler - Handler function that returns the updated agent state
4214
3527
  */
4215
3528
  updateAgentState(handler) {
4216
- logger.debugLargeJson("Updating agent state", this.agentState);
3529
+ persistence.logger.debugLargeJson("Updating agent state", this.agentState);
4217
3530
  this.agentStateLock.inLock(async () => {
4218
3531
  let rateRefusals = 0;
4219
3532
  await backoff(async () => {
4220
3533
  let updated = handler(this.agentState || {});
4221
- const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
3534
+ const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
4222
3535
  if (answer.result === "success") {
4223
- this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
3536
+ this.agentState = answer.agentState ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.agentState)) : null;
4224
3537
  this.agentStateVersion = answer.version;
4225
3538
  rateRefusals = 0;
4226
- logger.debug("Agent state updated", this.agentState);
3539
+ persistence.logger.debug("Agent state updated", this.agentState);
4227
3540
  } else if (answer.result === "version-mismatch") {
4228
3541
  if (answer.version > this.agentStateVersion) {
4229
3542
  this.agentStateVersion = answer.version;
4230
- this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
3543
+ this.agentState = answer.agentState ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.agentState)) : null;
4231
3544
  }
4232
3545
  throw new Error("Agent state version mismatch");
4233
3546
  } else if (answer.result === "error") {
@@ -4238,7 +3551,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4238
3551
  code: answer.error,
4239
3552
  attempt: rateRefusals,
4240
3553
  sleep: delay,
4241
- log: (message) => logger.debug(message)
3554
+ log: (message) => persistence.logger.debug(message)
4242
3555
  });
4243
3556
  }
4244
3557
  throw new Error("Agent state update failed");
@@ -4267,7 +3580,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4267
3580
  });
4268
3581
  }
4269
3582
  async close() {
4270
- logger.debug("[API] socket.close() called");
3583
+ persistence.logger.debug("[API] socket.close() called");
4271
3584
  this.sendSync.stop();
4272
3585
  this.receiveSync.stop();
4273
3586
  if (this.reconnectInterval) {
@@ -4287,14 +3600,14 @@ class ApiSessionClient extends node_events.EventEmitter {
4287
3600
  return;
4288
3601
  }
4289
3602
  if (!shouldReconnect()) {
4290
- logger.debug("[API] Still not ready to reconnect");
3603
+ persistence.logger.debug("[API] Still not ready to reconnect");
4291
3604
  return;
4292
3605
  }
4293
- logger.debug("[API] Attempting reconnect");
3606
+ persistence.logger.debug("[API] Attempting reconnect");
4294
3607
  this.socket.connect();
4295
3608
  }, 3e3);
4296
3609
  if (shouldReconnect()) {
4297
- logger.debug("[API] Network up + lid open \u2014 reconnecting in 1s");
3610
+ persistence.logger.debug("[API] Network up + lid open \u2014 reconnecting in 1s");
4298
3611
  setTimeout(() => {
4299
3612
  if (!this.socket.connected) this.socket.connect();
4300
3613
  }, 1e3);
@@ -4445,12 +3758,12 @@ function normalizeFsPath(input, homeDir) {
4445
3758
  if (p === "~") {
4446
3759
  p = homeDir;
4447
3760
  } else if (p.startsWith("~/")) {
4448
- p = path.join(homeDir, p.slice(2));
3761
+ p = path$1.join(homeDir, p.slice(2));
4449
3762
  }
4450
- if (!path.isAbsolute(p)) {
4451
- p = path.join(homeDir, p);
3763
+ if (!path$1.isAbsolute(p)) {
3764
+ p = path$1.join(homeDir, p);
4452
3765
  }
4453
- return path.resolve(p);
3766
+ return path$1.resolve(p);
4454
3767
  }
4455
3768
  function isBinaryContent(bytes) {
4456
3769
  const n = Math.min(bytes.length, BINARY_SNIFF_BYTES);
@@ -4490,13 +3803,13 @@ function fsError(error) {
4490
3803
  if (error instanceof Error) return error;
4491
3804
  return new Error("fs-failed");
4492
3805
  }
4493
- async function fsList(inputPath, homeDir = os.homedir()) {
4494
- const path$1 = normalizeFsPath(inputPath, homeDir);
3806
+ async function fsList(inputPath, homeDir = os$1.homedir()) {
3807
+ const path = normalizeFsPath(inputPath, homeDir);
4495
3808
  let dirents;
4496
3809
  try {
4497
- const st = await promises.stat(path$1);
3810
+ const st = await promises.stat(path);
4498
3811
  if (!st.isDirectory()) throw new Error("not-a-directory");
4499
- dirents = await promises.readdir(path$1, { withFileTypes: true });
3812
+ dirents = await promises.readdir(path, { withFileTypes: true });
4500
3813
  } catch (error) {
4501
3814
  throw fsError(error);
4502
3815
  }
@@ -4504,7 +3817,7 @@ async function fsList(inputPath, homeDir = os.homedir()) {
4504
3817
  dirents.map(async (d) => {
4505
3818
  const entry = { name: d.name, type: entryTypeOf(d) };
4506
3819
  try {
4507
- const s = await promises.lstat(path.join(path$1, d.name));
3820
+ const s = await promises.lstat(path$1.join(path, d.name));
4508
3821
  entry.size = s.size;
4509
3822
  entry.mtimeMs = Math.round(s.mtimeMs);
4510
3823
  } catch {
@@ -4515,12 +3828,12 @@ async function fsList(inputPath, homeDir = os.homedir()) {
4515
3828
  entries.sort(compareFsEntries);
4516
3829
  const truncated = entries.length > FS_LIST_MAX_ENTRIES;
4517
3830
  return {
4518
- path: path$1,
3831
+ path,
4519
3832
  entries: truncated ? entries.slice(0, FS_LIST_MAX_ENTRIES) : entries,
4520
3833
  truncated
4521
3834
  };
4522
3835
  }
4523
- async function fsRead(inputPath, options = {}, homeDir = os.homedir()) {
3836
+ async function fsRead(inputPath, options = {}, homeDir = os$1.homedir()) {
4524
3837
  const path = normalizeFsPath(inputPath, homeDir);
4525
3838
  const limit = clampReadLimit(options.maxBytes);
4526
3839
  const offset = clampReadOffset(options.offset);
@@ -4552,400 +3865,15 @@ async function fsRead(inputPath, options = {}, homeDir = os.homedir()) {
4552
3865
  }
4553
3866
  function registerFsHandlers(rpcHandlerManager) {
4554
3867
  rpcHandlerManager.registerHandler("fs-list", async (params) => {
4555
- logger.debug("[FS RPC] fs-list", params?.path);
3868
+ persistence.logger.debug("[FS RPC] fs-list", params?.path);
4556
3869
  return fsList(params?.path);
4557
3870
  });
4558
3871
  rpcHandlerManager.registerHandler("fs-read", async (params) => {
4559
- logger.debug("[FS RPC] fs-read", params?.path);
3872
+ persistence.logger.debug("[FS RPC] fs-read", params?.path);
4560
3873
  return fsRead(params?.path, { maxBytes: params?.maxBytes, allowBinary: params?.allowBinary === true, offset: params?.offset });
4561
3874
  });
4562
3875
  }
4563
3876
 
4564
- const DEFAULT_SERVER_URL = "https://veryhappy.dev";
4565
- const LEGACY_CLOUD_SERVER_URL = "https://happy.mereith.com";
4566
- const CLOUD_SERVER_URLS = [DEFAULT_SERVER_URL, LEGACY_CLOUD_SERVER_URL];
4567
- function sameRelay(left, right) {
4568
- try {
4569
- return new URL(left).origin === new URL(right).origin;
4570
- } catch {
4571
- return left.replace(/\/+$/, "") === right.replace(/\/+$/, "");
4572
- }
4573
- }
4574
- function credentialRelayProblem(credentialServerUrl, configuredServerUrl) {
4575
- if (credentialServerUrl) {
4576
- return sameRelay(credentialServerUrl, configuredServerUrl) ? void 0 : `Credentials belong to ${credentialServerUrl}, but the active relay is ${configuredServerUrl}.`;
4577
- }
4578
- if (CLOUD_SERVER_URLS.some((cloudUrl) => sameRelay(configuredServerUrl, cloudUrl))) {
4579
- return void 0;
4580
- }
4581
- return `These credentials predate relay tracking, so they cannot be safely reused with ${configuredServerUrl}.`;
4582
- }
4583
-
4584
- const SandboxConfigSchema = z__namespace.object({
4585
- enabled: z__namespace.boolean().default(false),
4586
- workspaceRoot: z__namespace.string().optional(),
4587
- sessionIsolation: z__namespace.enum(["strict", "workspace", "custom"]).default("workspace"),
4588
- customWritePaths: z__namespace.array(z__namespace.string()).default([]),
4589
- denyReadPaths: z__namespace.array(z__namespace.string()).default(["~/.ssh", "~/.aws", "~/.gnupg"]),
4590
- extraWritePaths: z__namespace.array(z__namespace.string()).default(["/tmp"]),
4591
- denyWritePaths: z__namespace.array(z__namespace.string()).default([".env"]),
4592
- networkMode: z__namespace.enum(["blocked", "allowed", "custom"]).default("allowed"),
4593
- allowedDomains: z__namespace.array(z__namespace.string()).default([]),
4594
- deniedDomains: z__namespace.array(z__namespace.string()).default([]),
4595
- allowLocalBinding: z__namespace.boolean().default(true)
4596
- });
4597
- const SUPPORTED_SCHEMA_VERSION = 2;
4598
- const defaultSettings = {
4599
- schemaVersion: SUPPORTED_SCHEMA_VERSION,
4600
- onboardingCompleted: false,
4601
- sandboxConfig: void 0
4602
- };
4603
- function migrateSettings(raw, fromVersion) {
4604
- let migrated = { ...raw };
4605
- return migrated;
4606
- }
4607
- async function readSettingsOutcome() {
4608
- if (!node_fs.existsSync(configuration.settingsFile)) {
4609
- return { kind: "absent" };
4610
- }
4611
- try {
4612
- return { kind: "ok", settings: await parseSettingsFile() };
4613
- } catch (error) {
4614
- return { kind: "unreadable", error: error?.message ?? String(error) };
4615
- }
4616
- }
4617
- async function readSettings() {
4618
- const outcome = await readSettingsOutcome();
4619
- if (outcome.kind === "ok") return outcome.settings;
4620
- if (outcome.kind === "unreadable") {
4621
- logger.warn(`Failed to read settings: ${outcome.error}`);
4622
- }
4623
- return { ...defaultSettings };
4624
- }
4625
- async function parseSettingsFile() {
4626
- const content = await promises.readFile(configuration.settingsFile, "utf8");
4627
- const raw = JSON.parse(content);
4628
- const schemaVersion = raw.schemaVersion ?? 1;
4629
- if (schemaVersion > SUPPORTED_SCHEMA_VERSION) {
4630
- logger.warn(
4631
- `\u26A0\uFE0F Settings schema v${schemaVersion} > supported v${SUPPORTED_SCHEMA_VERSION}. Update happy-cli for full functionality.`
4632
- );
4633
- }
4634
- const migrated = migrateSettings(raw);
4635
- if (migrated.sandboxConfig !== void 0) {
4636
- try {
4637
- migrated.sandboxConfig = SandboxConfigSchema.parse(migrated.sandboxConfig);
4638
- } catch (error) {
4639
- logger.warn(`\u26A0\uFE0F Invalid sandbox config - skipping. Error: ${error.message}`);
4640
- migrated.sandboxConfig = void 0;
4641
- }
4642
- }
4643
- return { ...defaultSettings, ...migrated };
4644
- }
4645
- async function writeSettings(settings) {
4646
- await ensurePrivateDirectory(configuration.happyHomeDir);
4647
- const settingsWithVersion = {
4648
- ...settings,
4649
- schemaVersion: settings.schemaVersion ?? SUPPORTED_SCHEMA_VERSION
4650
- };
4651
- await writePrivateFile(configuration.settingsFile, JSON.stringify(settingsWithVersion, null, 2));
4652
- }
4653
- async function updateSettings(updater) {
4654
- const LOCK_RETRY_INTERVAL_MS = 100;
4655
- const MAX_LOCK_ATTEMPTS = 50;
4656
- const STALE_LOCK_TIMEOUT_MS = 1e4;
4657
- const lockFile = configuration.settingsFile + ".lock";
4658
- const tmpFile = configuration.settingsFile + ".tmp";
4659
- let fileHandle;
4660
- let attempts = 0;
4661
- while (attempts < MAX_LOCK_ATTEMPTS) {
4662
- try {
4663
- fileHandle = await promises.open(lockFile, node_fs.constants.O_CREAT | node_fs.constants.O_EXCL | node_fs.constants.O_WRONLY, PRIVATE_FILE_MODE);
4664
- break;
4665
- } catch (err) {
4666
- if (err.code === "EEXIST") {
4667
- attempts++;
4668
- await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
4669
- try {
4670
- const stats = await promises.stat(lockFile);
4671
- if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
4672
- await promises.unlink(lockFile).catch(() => {
4673
- });
4674
- }
4675
- } catch {
4676
- }
4677
- } else {
4678
- throw err;
4679
- }
4680
- }
4681
- }
4682
- if (!fileHandle) {
4683
- throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
4684
- }
4685
- try {
4686
- const outcome = await readSettingsOutcome();
4687
- if (outcome.kind === "unreadable") {
4688
- throw new Error(
4689
- `Settings file ${configuration.settingsFile} exists but could not be read (${outcome.error}). Refusing to overwrite it \u2014 fix or remove the file (a removed file is recreated with defaults).`
4690
- );
4691
- }
4692
- const current = outcome.kind === "ok" ? outcome.settings : { ...defaultSettings };
4693
- const updated = await updater(current);
4694
- await ensurePrivateDirectory(configuration.happyHomeDir);
4695
- await writePrivateFile(tmpFile, JSON.stringify(updated, null, 2));
4696
- await promises.rename(tmpFile, configuration.settingsFile);
4697
- await hardenPrivateFile(configuration.settingsFile);
4698
- return updated;
4699
- } finally {
4700
- await fileHandle.close();
4701
- await promises.unlink(lockFile).catch(() => {
4702
- });
4703
- }
4704
- }
4705
- const credentialsSchema = z__namespace.object({
4706
- token: z__namespace.string(),
4707
- authServerUrl: z__namespace.string().optional(),
4708
- secret: z__namespace.string().base64().nullish(),
4709
- // Legacy
4710
- encryption: z__namespace.object({
4711
- publicKey: z__namespace.string().base64(),
4712
- machineKey: z__namespace.string().base64()
4713
- }).nullish()
4714
- });
4715
- async function readCredentials() {
4716
- if (!node_fs.existsSync(configuration.privateKeyFile)) {
4717
- return null;
4718
- }
4719
- try {
4720
- const keyBase64 = await promises.readFile(configuration.privateKeyFile, "utf8");
4721
- const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
4722
- if (credentials.secret) {
4723
- return {
4724
- token: credentials.token,
4725
- authServerUrl: credentials.authServerUrl,
4726
- encryption: {
4727
- type: "legacy",
4728
- secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
4729
- }
4730
- };
4731
- } else if (credentials.encryption) {
4732
- return {
4733
- token: credentials.token,
4734
- authServerUrl: credentials.authServerUrl,
4735
- encryption: {
4736
- type: "dataKey",
4737
- publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
4738
- machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
4739
- }
4740
- };
4741
- }
4742
- } catch {
4743
- return null;
4744
- }
4745
- return null;
4746
- }
4747
- async function readCredentialsForConfiguredRelay() {
4748
- const credentials = await readCredentials();
4749
- if (!credentials) return null;
4750
- const relayProblem = credentialRelayProblem(credentials.authServerUrl, configuration.serverUrl);
4751
- if (relayProblem) {
4752
- throw new Error(
4753
- `${relayProblem} Use a separate HAPPY_HOME_DIR for this relay, or run \`very-happy auth login --force\` before making relay requests.`
4754
- );
4755
- }
4756
- return credentials;
4757
- }
4758
- async function writeCredentialsLegacy(credentials) {
4759
- await ensurePrivateDirectory(configuration.happyHomeDir);
4760
- await writePrivateFile(configuration.privateKeyFile, JSON.stringify({
4761
- secret: encodeBase64(credentials.secret),
4762
- token: credentials.token,
4763
- authServerUrl: configuration.serverUrl
4764
- }, null, 2));
4765
- }
4766
- async function writeCredentialsDataKey(credentials) {
4767
- await ensurePrivateDirectory(configuration.happyHomeDir);
4768
- await writePrivateFile(configuration.privateKeyFile, JSON.stringify({
4769
- encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
4770
- token: credentials.token,
4771
- authServerUrl: configuration.serverUrl
4772
- }, null, 2));
4773
- }
4774
- async function clearCredentials() {
4775
- if (node_fs.existsSync(configuration.privateKeyFile)) {
4776
- await promises.unlink(configuration.privateKeyFile);
4777
- }
4778
- }
4779
- async function clearMachineId() {
4780
- await updateSettings((settings) => ({
4781
- ...settings,
4782
- machineId: void 0
4783
- }));
4784
- }
4785
- async function readDaemonState() {
4786
- try {
4787
- if (!node_fs.existsSync(configuration.daemonStateFile)) {
4788
- return null;
4789
- }
4790
- const content = await promises.readFile(configuration.daemonStateFile, "utf-8");
4791
- return JSON.parse(content);
4792
- } catch (error) {
4793
- console.error(`[PERSISTENCE] Daemon state file corrupted: ${configuration.daemonStateFile}`, error);
4794
- return null;
4795
- }
4796
- }
4797
- function writeDaemonState(state) {
4798
- writePrivateFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2));
4799
- }
4800
- async function clearDaemonState() {
4801
- if (node_fs.existsSync(configuration.daemonStateFile)) {
4802
- await promises.unlink(configuration.daemonStateFile);
4803
- }
4804
- if (node_fs.existsSync(configuration.daemonLockFile)) {
4805
- try {
4806
- await promises.unlink(configuration.daemonLockFile);
4807
- } catch {
4808
- }
4809
- }
4810
- }
4811
- async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
4812
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4813
- try {
4814
- const fileHandle = await promises.open(
4815
- configuration.daemonLockFile,
4816
- node_fs.constants.O_CREAT | node_fs.constants.O_EXCL | node_fs.constants.O_WRONLY,
4817
- PRIVATE_FILE_MODE
4818
- );
4819
- await fileHandle.writeFile(String(process.pid));
4820
- return fileHandle;
4821
- } catch (error) {
4822
- if (error.code === "EEXIST") {
4823
- try {
4824
- const lockPid = node_fs.readFileSync(configuration.daemonLockFile, "utf-8").trim();
4825
- if (lockPid && !isNaN(Number(lockPid))) {
4826
- try {
4827
- process.kill(Number(lockPid), 0);
4828
- } catch {
4829
- node_fs.unlinkSync(configuration.daemonLockFile);
4830
- continue;
4831
- }
4832
- }
4833
- } catch {
4834
- }
4835
- }
4836
- if (attempt === maxAttempts) {
4837
- return null;
4838
- }
4839
- const delayMs = attempt * delayIncrementMs;
4840
- await new Promise((resolve) => setTimeout(resolve, delayMs));
4841
- }
4842
- }
4843
- return null;
4844
- }
4845
- async function releaseDaemonLock(lockHandle) {
4846
- try {
4847
- await lockHandle.close();
4848
- } catch {
4849
- }
4850
- try {
4851
- if (node_fs.existsSync(configuration.daemonLockFile)) {
4852
- node_fs.unlinkSync(configuration.daemonLockFile);
4853
- }
4854
- } catch {
4855
- }
4856
- }
4857
- const SESSION_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
4858
- function readPersistedSessions() {
4859
- try {
4860
- if (!node_fs.existsSync(configuration.sessionsFile)) return {};
4861
- const data = JSON.parse(node_fs.readFileSync(configuration.sessionsFile, "utf-8"));
4862
- if (!data?.sessions || typeof data.sessions !== "object") return {};
4863
- const now = Date.now();
4864
- const sessions = {};
4865
- for (const [id, session] of Object.entries(data.sessions)) {
4866
- if (now - session.savedAt < SESSION_MAX_AGE_MS) {
4867
- sessions[id] = session;
4868
- }
4869
- }
4870
- return sessions;
4871
- } catch {
4872
- return {};
4873
- }
4874
- }
4875
- function readTrackedClaudeSessionIds() {
4876
- try {
4877
- if (!node_fs.existsSync(configuration.sessionsFile)) return [];
4878
- const data = JSON.parse(node_fs.readFileSync(configuration.sessionsFile, "utf-8"));
4879
- if (!data?.sessions || typeof data.sessions !== "object") return [];
4880
- const ids = /* @__PURE__ */ new Set();
4881
- for (const session of Object.values(data.sessions)) {
4882
- const metadata = (session ?? {}).metadata;
4883
- for (const id of [metadata?.claudeSessionId, metadata?.importedFromClaudeSessionId]) {
4884
- if (typeof id === "string" && id.length > 0) ids.add(id.toLowerCase());
4885
- }
4886
- }
4887
- return [...ids];
4888
- } catch {
4889
- return [];
4890
- }
4891
- }
4892
- function persistSession(sessionId, session) {
4893
- try {
4894
- const existing = readPersistedSessions();
4895
- existing[sessionId] = session;
4896
- const tmpFile = configuration.sessionsFile + ".tmp";
4897
- writePrivateFileSync(tmpFile, JSON.stringify({ sessions: existing }, null, 2));
4898
- node_fs.renameSync(tmpFile, configuration.sessionsFile);
4899
- hardenPrivateFileSync(configuration.sessionsFile);
4900
- } catch (error) {
4901
- logger.debug(`[PERSISTENCE] Failed to persist session ${sessionId}:`, error);
4902
- }
4903
- }
4904
- function deletePersistedSessions(sessionIds) {
4905
- try {
4906
- const existing = readPersistedSessions();
4907
- let changed = false;
4908
- for (const id of sessionIds) {
4909
- if (id in existing) {
4910
- delete existing[id];
4911
- changed = true;
4912
- }
4913
- }
4914
- if (!changed) return;
4915
- const tmpFile = configuration.sessionsFile + ".tmp";
4916
- writePrivateFileSync(tmpFile, JSON.stringify({ sessions: existing }, null, 2));
4917
- node_fs.renameSync(tmpFile, configuration.sessionsFile);
4918
- hardenPrivateFileSync(configuration.sessionsFile);
4919
- } catch (error) {
4920
- logger.debug(`[PERSISTENCE] Failed to delete persisted sessions:`, error);
4921
- }
4922
- }
4923
-
4924
- var persistence = /*#__PURE__*/Object.freeze({
4925
- __proto__: null,
4926
- SUPPORTED_SCHEMA_VERSION: SUPPORTED_SCHEMA_VERSION,
4927
- SandboxConfigSchema: SandboxConfigSchema,
4928
- acquireDaemonLock: acquireDaemonLock,
4929
- clearCredentials: clearCredentials,
4930
- clearDaemonState: clearDaemonState,
4931
- clearMachineId: clearMachineId,
4932
- deletePersistedSessions: deletePersistedSessions,
4933
- persistSession: persistSession,
4934
- readCredentials: readCredentials,
4935
- readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay,
4936
- readDaemonState: readDaemonState,
4937
- readPersistedSessions: readPersistedSessions,
4938
- readSettings: readSettings,
4939
- readSettingsOutcome: readSettingsOutcome,
4940
- readTrackedClaudeSessionIds: readTrackedClaudeSessionIds,
4941
- releaseDaemonLock: releaseDaemonLock,
4942
- updateSettings: updateSettings,
4943
- writeCredentialsDataKey: writeCredentialsDataKey,
4944
- writeCredentialsLegacy: writeCredentialsLegacy,
4945
- writeDaemonState: writeDaemonState,
4946
- writeSettings: writeSettings
4947
- });
4948
-
4949
3877
  const TODO_LIST_MAX_ITEMS = 500;
4950
3878
  const TODO_TITLE_MAX_CHARS = 500;
4951
3879
  const FIELD_MAX_CHARS = 200;
@@ -5011,7 +3939,7 @@ function buildProviderArgv(config, verb, operand) {
5011
3939
  const DEFAULT_TIMEOUT_MS = 2e4;
5012
3940
  const MAX_STDOUT_BYTES = 2 * 1024 * 1024;
5013
3941
  async function readProviderConfig() {
5014
- const settings = await readSettings();
3942
+ const settings = await persistence.readSettings();
5015
3943
  const raw = settings?.todoProvider;
5016
3944
  if (!raw || typeof raw !== "object") return null;
5017
3945
  const cfg = raw;
@@ -5090,7 +4018,7 @@ function registerTodoHandlers(manager) {
5090
4018
  if ("error" in parsed) {
5091
4019
  throw new Error(`bad-output: ${parsed.error}`);
5092
4020
  }
5093
- logger.debug(`[todo] list \u2192 ${parsed.items.length} items (dropped ${parsed.dropped})`);
4021
+ persistence.logger.debug(`[todo] list \u2192 ${parsed.items.length} items (dropped ${parsed.dropped})`);
5094
4022
  return { ...parsed, machineConfigured: true };
5095
4023
  });
5096
4024
  manager.registerHandler("todo-complete", async (params) => {
@@ -5304,14 +4232,14 @@ function scrubTmuxClientEnv(env) {
5304
4232
  }
5305
4233
 
5306
4234
  function getClaudeConfigDir() {
5307
- return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
4235
+ return process.env.CLAUDE_CONFIG_DIR || path$1.join(os$1.homedir(), ".claude");
5308
4236
  }
5309
4237
  function getClaudeProjectsRoot() {
5310
- return path.join(getClaudeConfigDir(), "projects");
4238
+ return path$1.join(getClaudeConfigDir(), "projects");
5311
4239
  }
5312
4240
  function getProjectPath(workingDirectory) {
5313
- const projectId = path.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
5314
- return path.join(getClaudeProjectsRoot(), projectId);
4241
+ const projectId = path$1.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
4242
+ return path$1.join(getClaudeProjectsRoot(), projectId);
5315
4243
  }
5316
4244
 
5317
4245
  var xtermHeadless = {};
@@ -9656,18 +8584,18 @@ class ControlClient {
9656
8584
  });
9657
8585
  this.child.stdout.on("data", (chunk) => this.consume(this.decoder.push(chunk)));
9658
8586
  this.child.stderr.on("data", (chunk) => {
9659
- logger.debug(`[CONTROL ${tmuxSession}] stderr: ${chunk.toString("utf8").trim()}`);
8587
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] stderr: ${chunk.toString("utf8").trim()}`);
9660
8588
  });
9661
8589
  this.child.stdin.on("error", (e) => {
9662
- logger.debug(`[CONTROL ${tmuxSession}] stdin error (session gone?): ${e}`);
8590
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] stdin error (session gone?): ${e}`);
9663
8591
  });
9664
8592
  this.child.stdout.on("error", (e) => {
9665
- logger.debug(`[CONTROL ${tmuxSession}] stdout error: ${e}`);
8593
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] stdout error: ${e}`);
9666
8594
  });
9667
8595
  this.child.stderr.on("error", () => {
9668
8596
  });
9669
8597
  this.child.on("error", (e) => {
9670
- logger.debug(`[CONTROL ${tmuxSession}] spawn error: ${e}`);
8598
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] spawn error: ${e}`);
9671
8599
  this.failAll(new Error(`control client failed: ${e}`));
9672
8600
  });
9673
8601
  this.child.on("exit", (code, signal) => {
@@ -9704,7 +8632,7 @@ class ControlClient {
9704
8632
  if (!ev.solicited) continue;
9705
8633
  const pending = this.queue.shift();
9706
8634
  if (!pending) {
9707
- logger.debug(`[CONTROL ${this.tmuxSession}] unmatched block cmd=${ev.cmdNum}`);
8635
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] unmatched block cmd=${ev.cmdNum}`);
9708
8636
  continue;
9709
8637
  }
9710
8638
  clearTimeout(pending.timer);
@@ -9713,7 +8641,7 @@ class ControlClient {
9713
8641
  try {
9714
8642
  pending.onBlock?.(ev.body);
9715
8643
  } catch (e) {
9716
- logger.debug(`[CONTROL ${this.tmuxSession}] onBlock threw: ${e}`);
8644
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] onBlock threw: ${e}`);
9717
8645
  }
9718
8646
  pending.resolve(ev.body);
9719
8647
  }
@@ -9723,7 +8651,7 @@ class ControlClient {
9723
8651
  this.handlers.onNotification(ev.name, ev.args);
9724
8652
  continue;
9725
8653
  }
9726
- logger.debug(`[CONTROL ${this.tmuxSession}] protocol error ${ev.reason}: ${ev.detail}`);
8654
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] protocol error ${ev.reason}: ${ev.detail}`);
9727
8655
  }
9728
8656
  }
9729
8657
  failAll(err) {
@@ -9784,7 +8712,7 @@ class ControlClient {
9784
8712
  try {
9785
8713
  this.child.stdin.write(text);
9786
8714
  } catch (e) {
9787
- logger.debug(`[CONTROL ${this.tmuxSession}] write failed (session gone?): ${e}`);
8715
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] write failed (session gone?): ${e}`);
9788
8716
  }
9789
8717
  }
9790
8718
  /** SIGTERM → grace → SIGKILL (rule 3). Resolves once the child is gone. */
@@ -9794,7 +8722,7 @@ class ControlClient {
9794
8722
  this.stopping = true;
9795
8723
  return new Promise((resolve) => {
9796
8724
  const killer = setTimeout(() => {
9797
- logger.debug(`[CONTROL ${this.tmuxSession}] SIGTERM did not land in ${graceMs}ms \u2014 SIGKILL`);
8725
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] SIGTERM did not land in ${graceMs}ms \u2014 SIGKILL`);
9798
8726
  try {
9799
8727
  this.child.kill("SIGKILL");
9800
8728
  } catch {
@@ -10389,14 +9317,14 @@ function sendTerminalNotification(opts) {
10389
9317
  headers: {
10390
9318
  "Authorization": `Bearer ${opts.token}`,
10391
9319
  "Content-Type": "application/json",
10392
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
9320
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
10393
9321
  },
10394
9322
  timeout: 15e3
10395
9323
  }
10396
9324
  ).then(() => {
10397
- logger.debug(`[WEB TERMINAL] notify sent (event=${opts.event}, link=${opts.link})`);
9325
+ persistence.logger.debug(`[WEB TERMINAL] notify sent (event=${opts.event}, link=${opts.link})`);
10398
9326
  }).catch((error) => {
10399
- logger.debug("[WEB TERMINAL] notify failed:", error);
9327
+ persistence.logger.debug("[WEB TERMINAL] notify failed:", error);
10400
9328
  });
10401
9329
  }
10402
9330
 
@@ -10543,7 +9471,7 @@ function pruneTombstones(map, now, ttlMs = TOMBSTONE_TTL_MS) {
10543
9471
  return out;
10544
9472
  }
10545
9473
  function tombstoneFile() {
10546
- return path.join(configuration.happyHomeDir, "terminal-tombstones.json");
9474
+ return path$1.join(persistence.configuration.happyHomeDir, "terminal-tombstones.json");
10547
9475
  }
10548
9476
  function loadTombstones() {
10549
9477
  try {
@@ -10555,14 +9483,14 @@ function loadTombstones() {
10555
9483
  }
10556
9484
  function saveTombstones(map) {
10557
9485
  try {
10558
- node_fs.mkdirSync(configuration.happyHomeDir, { recursive: true });
9486
+ node_fs.mkdirSync(persistence.configuration.happyHomeDir, { recursive: true });
10559
9487
  node_fs.writeFileSync(tombstoneFile(), JSON.stringify(pruneTombstones(map, Date.now())));
10560
9488
  } catch (e) {
10561
- logger.debug(`[WEB TERMINAL] tombstone save failed: ${e}`);
9489
+ persistence.logger.debug(`[WEB TERMINAL] tombstone save failed: ${e}`);
10562
9490
  }
10563
9491
  }
10564
9492
  function closedTerminalsFile() {
10565
- return path.join(configuration.happyHomeDir, "closed-terminals.json");
9493
+ return path$1.join(persistence.configuration.happyHomeDir, "closed-terminals.json");
10566
9494
  }
10567
9495
  function loadClosedTerminals() {
10568
9496
  try {
@@ -10573,14 +9501,14 @@ function loadClosedTerminals() {
10573
9501
  }
10574
9502
  function saveClosedTerminals(list) {
10575
9503
  try {
10576
- node_fs.mkdirSync(configuration.happyHomeDir, { recursive: true });
9504
+ node_fs.mkdirSync(persistence.configuration.happyHomeDir, { recursive: true });
10577
9505
  node_fs.writeFileSync(closedTerminalsFile(), JSON.stringify(list));
10578
9506
  } catch (e) {
10579
- logger.debug(`[WEB TERMINAL] closed-terminals save failed: ${e}`);
9507
+ persistence.logger.debug(`[WEB TERMINAL] closed-terminals save failed: ${e}`);
10580
9508
  }
10581
9509
  }
10582
9510
  function liveTerminalsFile() {
10583
- return path.join(configuration.happyHomeDir, "live-terminals.json");
9511
+ return path$1.join(persistence.configuration.happyHomeDir, "live-terminals.json");
10584
9512
  }
10585
9513
  function loadLiveSnapshot() {
10586
9514
  try {
@@ -10591,10 +9519,10 @@ function loadLiveSnapshot() {
10591
9519
  }
10592
9520
  function saveLiveSnapshot(map) {
10593
9521
  try {
10594
- node_fs.mkdirSync(configuration.happyHomeDir, { recursive: true });
9522
+ node_fs.mkdirSync(persistence.configuration.happyHomeDir, { recursive: true });
10595
9523
  node_fs.writeFileSync(liveTerminalsFile(), JSON.stringify(serializeLiveSnapshot(map)));
10596
9524
  } catch (e) {
10597
- logger.debug(`[WEB TERMINAL] live-terminals save failed: ${e}`);
9525
+ persistence.logger.debug(`[WEB TERMINAL] live-terminals save failed: ${e}`);
10598
9526
  }
10599
9527
  }
10600
9528
  const TERMINAL_OPEN_TIMEOUT = "terminal-open-timeout";
@@ -10855,7 +9783,7 @@ function tmuxSupportsEnvFlag() {
10855
9783
  }
10856
9784
  const PASTE_FILE_TTL_MS = 5e3;
10857
9785
  function pasteSpoolDir() {
10858
- const dir = path.join(configuration.happyHomeDir, "paste-spool");
9786
+ const dir = path$1.join(persistence.configuration.happyHomeDir, "paste-spool");
10859
9787
  try {
10860
9788
  node_fs.mkdirSync(dir, { recursive: true, mode: 448 });
10861
9789
  } catch {
@@ -10906,7 +9834,7 @@ function ptyEnv() {
10906
9834
  if (typeof v === "string") env[k] = v;
10907
9835
  }
10908
9836
  scrubTmuxClientEnv(env);
10909
- const local = `${os.homedir()}/.local/bin`;
9837
+ const local = `${os$1.homedir()}/.local/bin`;
10910
9838
  if (!(env.PATH || "").split(":").includes(local)) {
10911
9839
  env.PATH = `${local}:${env.PATH || ""}`;
10912
9840
  }
@@ -11382,7 +10310,7 @@ class WebTerminalManager {
11382
10310
  try {
11383
10311
  this.emit("terminal-activity", { terminals: updates });
11384
10312
  } catch (e) {
11385
- logger.debug(`[WEB TERMINAL] activity emit failed: ${e}`);
10313
+ persistence.logger.debug(`[WEB TERMINAL] activity emit failed: ${e}`);
11386
10314
  }
11387
10315
  }
11388
10316
  /** Seed the change signature from a list the caller already delivered by
@@ -11472,7 +10400,7 @@ class WebTerminalManager {
11472
10400
  this.lastListSignature = sig;
11473
10401
  cb(list);
11474
10402
  } catch (e) {
11475
- logger.debug(`[WEB TERMINAL] list track tick failed: ${e}`);
10403
+ persistence.logger.debug(`[WEB TERMINAL] list track tick failed: ${e}`);
11476
10404
  }
11477
10405
  }
11478
10406
  /** Closed-record diff (B-084): a terminal id that was in the last observed
@@ -11500,7 +10428,7 @@ class WebTerminalManager {
11500
10428
  next.set(id, info);
11501
10429
  continue;
11502
10430
  }
11503
- persisted ??= readPersistedSessions();
10431
+ persisted ??= persistence.readPersistedSessions();
11504
10432
  const mirror = pickMirrorForTerminal(persisted, id);
11505
10433
  this.closedTerminals = appendClosedTerminal(this.closedTerminals, {
11506
10434
  id,
@@ -11518,11 +10446,11 @@ class WebTerminalManager {
11518
10446
  closedAt: now
11519
10447
  });
11520
10448
  changed = true;
11521
- logger.debug(`[WEB TERMINAL] recorded closed terminal ${id}`);
10449
+ persistence.logger.debug(`[WEB TERMINAL] recorded closed terminal ${id}`);
11522
10450
  try {
11523
10451
  this.onTerminalClosedCb?.(id);
11524
10452
  } catch (e) {
11525
- logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
10453
+ persistence.logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
11526
10454
  }
11527
10455
  }
11528
10456
  const pruned = pruneClosedAgainstLive(this.closedTerminals, liveIds);
@@ -11554,7 +10482,7 @@ class WebTerminalManager {
11554
10482
  for (const [id, info] of snapshot) {
11555
10483
  if (liveIds.has(id)) continue;
11556
10484
  if (this.closedTerminals.some((r) => r.id === id)) continue;
11557
- persisted ??= readPersistedSessions();
10485
+ persisted ??= persistence.readPersistedSessions();
11558
10486
  const mirror = pickMirrorForTerminal(persisted, id);
11559
10487
  this.closedTerminals = appendClosedTerminal(this.closedTerminals, {
11560
10488
  id,
@@ -11583,7 +10511,7 @@ class WebTerminalManager {
11583
10511
  }
11584
10512
  if (recorded > 0) {
11585
10513
  saveClosedTerminals(this.closedTerminals);
11586
- logger.debug(`[WEB TERMINAL] daemon gap: archived ${recorded} terminal(s) from the persisted snapshot`);
10514
+ persistence.logger.debug(`[WEB TERMINAL] daemon gap: archived ${recorded} terminal(s) from the persisted snapshot`);
11587
10515
  }
11588
10516
  if (candidates.length > 0) void this.autoRestore(candidates, liveIds);
11589
10517
  }
@@ -11601,7 +10529,7 @@ class WebTerminalManager {
11601
10529
  this.autoRestoreDone = true;
11602
10530
  try {
11603
10531
  if (!isTmuxAvailable()) return;
11604
- const settings = await readSettings();
10532
+ const settings = await persistence.readSettings();
11605
10533
  const config = resolveAutoRestoreConfig(settings);
11606
10534
  const selection = selectAutoRestore(candidates, {
11607
10535
  now: Date.now(),
@@ -11617,7 +10545,7 @@ class WebTerminalManager {
11617
10545
  });
11618
10546
  for (const plan of selection.plans) {
11619
10547
  if (this.shuttingDown) {
11620
- logger.debug("[WEB TERMINAL] auto-restore aborted: daemon shutting down");
10548
+ persistence.logger.debug("[WEB TERMINAL] auto-restore aborted: daemon shutting down");
11621
10549
  return;
11622
10550
  }
11623
10551
  const ok = this.restoreOneTerminal(plan);
@@ -11630,15 +10558,15 @@ class WebTerminalManager {
11630
10558
  }
11631
10559
  const line = autoRestoreSummary(selection);
11632
10560
  if (line) {
11633
- logger.info(`[WEB TERMINAL] auto-restore: ${line}`);
10561
+ persistence.logger.info(`[WEB TERMINAL] auto-restore: ${line}`);
11634
10562
  try {
11635
10563
  this.onAutoRestoreSummaryCb?.(line);
11636
10564
  } catch (e) {
11637
- logger.debug(`[WEB TERMINAL] auto-restore report failed: ${e}`);
10565
+ persistence.logger.debug(`[WEB TERMINAL] auto-restore report failed: ${e}`);
11638
10566
  }
11639
10567
  }
11640
10568
  } catch (e) {
11641
- logger.debug(`[WEB TERMINAL] auto-restore failed: ${e}`);
10569
+ persistence.logger.debug(`[WEB TERMINAL] auto-restore failed: ${e}`);
11642
10570
  }
11643
10571
  }
11644
10572
  /**
@@ -11684,17 +10612,17 @@ class WebTerminalManager {
11684
10612
  return false;
11685
10613
  }
11686
10614
  },
11687
- conversationExists: (cwd, claudeSessionId) => node_fs.existsSync(path.join(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
10615
+ conversationExists: (cwd, claudeSessionId) => node_fs.existsSync(path$1.join(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
11688
10616
  // B-273: only consulted for records that were attach terminals.
11689
10617
  // Unbounded: the panel's 50-row cap must not hide a restore target.
11690
10618
  userSessions: record.attachTmux ? this.listUserTmuxSessions(Infinity) : [],
11691
10619
  attachSocket: process.env[VH_TMUX_SOCKET_ENV],
11692
- homeDir: os.homedir()
10620
+ homeDir: os$1.homedir()
11693
10621
  });
11694
10622
  if (plan.kind === "already-live") return { type: "success", terminalId };
11695
10623
  if (plan.kind === "error") return { type: "error", reason: plan.reason };
11696
10624
  if (!this.createDetachedTerminal(plan)) return { type: "error", reason: "create-failed" };
11697
- logger.info(`[WEB TERMINAL] restored closed terminal ${terminalId} in ${plan.cwd}${plan.attachTmux ? ` (attached tmux session ${plan.attachTmux})` : plan.command ? " (conversation resumed)" : ""}`);
10625
+ persistence.logger.info(`[WEB TERMINAL] restored closed terminal ${terminalId} in ${plan.cwd}${plan.attachTmux ? ` (attached tmux session ${plan.attachTmux})` : plan.command ? " (conversation resumed)" : ""}`);
11698
10626
  this.kickListRefresh();
11699
10627
  return { type: "success", terminalId };
11700
10628
  }
@@ -11716,7 +10644,7 @@ class WebTerminalManager {
11716
10644
  "-e",
11717
10645
  `VH_TERMINAL_ID=${plan.terminalId}`,
11718
10646
  "-e",
11719
- `VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
10647
+ `VH_HAPPY_HOME_DIR=${persistence.configuration.happyHomeDir}`
11720
10648
  ] : [];
11721
10649
  const { cols, rows } = coldCreateGeometry(plan);
11722
10650
  const created = node_child_process.spawnSync(
@@ -11730,7 +10658,7 @@ class WebTerminalManager {
11730
10658
  { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
11731
10659
  ).status === 0;
11732
10660
  if (!live) {
11733
- logger.debug(`[WEB TERMINAL] auto-restore: tmux create failed for ${plan.terminalId}`);
10661
+ persistence.logger.debug(`[WEB TERMINAL] auto-restore: tmux create failed for ${plan.terminalId}`);
11734
10662
  return false;
11735
10663
  }
11736
10664
  if (plan.title) {
@@ -11767,7 +10695,7 @@ class WebTerminalManager {
11767
10695
  node_child_process.spawnSync("tmux", tmuxArgs(argv), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
11768
10696
  }
11769
10697
  }
11770
- logger.info(`[WEB TERMINAL] cold-created ${plan.terminalId} in ${plan.cwd}`);
10698
+ persistence.logger.info(`[WEB TERMINAL] cold-created ${plan.terminalId} in ${plan.cwd}`);
11771
10699
  return true;
11772
10700
  }
11773
10701
  /** Mirror the in-memory cache to disk, but only when something a restart
@@ -11801,7 +10729,7 @@ class WebTerminalManager {
11801
10729
  try {
11802
10730
  this.onNotify({ terminalId: item.id, title: item.title || "Terminal", event });
11803
10731
  } catch (e) {
11804
- logger.debug(`[WEB TERMINAL] notify callback failed: ${e}`);
10732
+ persistence.logger.debug(`[WEB TERMINAL] notify callback failed: ${e}`);
11805
10733
  }
11806
10734
  }
11807
10735
  }
@@ -11815,7 +10743,7 @@ class WebTerminalManager {
11815
10743
  this.snapshots.sweep(now);
11816
10744
  for (const [id, session] of [...this.terminals]) {
11817
10745
  if (session.subscribers === 0 && now - session.lastTouch > SESSION_IDLE_MS) {
11818
- logger.debug(`[WEB TERMINAL] reaping orphaned idle session ${id} (idle ${Math.round((now - session.lastTouch) / 6e4)}m)`);
10746
+ persistence.logger.debug(`[WEB TERMINAL] reaping orphaned idle session ${id} (idle ${Math.round((now - session.lastTouch) / 6e4)}m)`);
11819
10747
  this.detach(id);
11820
10748
  }
11821
10749
  }
@@ -11841,8 +10769,8 @@ class WebTerminalManager {
11841
10769
  }
11842
10770
  const victimId = unwatchedVictim ?? anyVictim;
11843
10771
  if (!victimId) break;
11844
- if (!unwatchedVictim) logger.debug(`[WEB TERMINAL] pty cap reached with all sessions claiming watchers; force-evicting LRU ${victimId}`);
11845
- else logger.debug(`[WEB TERMINAL] pty cap reached (${this.terminals.size}); evicting LRU ${victimId}`);
10772
+ if (!unwatchedVictim) persistence.logger.debug(`[WEB TERMINAL] pty cap reached with all sessions claiming watchers; force-evicting LRU ${victimId}`);
10773
+ else persistence.logger.debug(`[WEB TERMINAL] pty cap reached (${this.terminals.size}); evicting LRU ${victimId}`);
11846
10774
  this.detach(victimId);
11847
10775
  }
11848
10776
  }
@@ -11867,7 +10795,7 @@ class WebTerminalManager {
11867
10795
  async open(opts) {
11868
10796
  const cols = Math.max(2, Math.floor(opts.cols ?? 80));
11869
10797
  const rows = Math.max(2, Math.floor(opts.rows ?? 24));
11870
- const cwd = opts.cwd && opts.cwd.length > 0 ? opts.cwd : os.homedir();
10798
+ const cwd = opts.cwd && opts.cwd.length > 0 ? opts.cwd : os$1.homedir();
11871
10799
  const id = opts.terminalId && /^[a-zA-Z0-9_-]{1,64}$/.test(opts.terminalId) ? opts.terminalId : node_crypto.randomBytes(5).toString("hex");
11872
10800
  const lines = opts.streamMode === "lines";
11873
10801
  const existing = this.terminals.get(id);
@@ -11878,7 +10806,7 @@ class WebTerminalManager {
11878
10806
  this.applyResize(existing, cols, rows);
11879
10807
  if (opts.fromSeq !== void 0 && existing.ringCovers(opts.fromSeq)) {
11880
10808
  const chunks = existing.chunksAfter(opts.fromSeq);
11881
- logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=replay, seq=${existing.seq})`);
10809
+ persistence.logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=replay, seq=${existing.seq})`);
11882
10810
  return {
11883
10811
  terminalId: id,
11884
10812
  tmuxSession: existing.tmuxSession,
@@ -11897,7 +10825,7 @@ class WebTerminalManager {
11897
10825
  return { ...await this.linesSnapshotResponse(id, existing), ...echo2 };
11898
10826
  }
11899
10827
  const state = existing.subscribeState(opts.fromSeq);
11900
- logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=${state.mode}, seq=${state.seq})`);
10828
+ persistence.logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=${state.mode}, seq=${state.seq})`);
11901
10829
  return { terminalId: id, tmuxSession: existing.tmuxSession, ...state, ...echo2 };
11902
10830
  }
11903
10831
  if (opts.attachOnly || opts.resub) {
@@ -11914,7 +10842,7 @@ class WebTerminalManager {
11914
10842
  { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11915
10843
  ).status === 0;
11916
10844
  if (!alive) {
11917
- logger.debug(`[WEB TERMINAL] refused stale-client resurrection of tombstoned ${id}`);
10845
+ persistence.logger.debug(`[WEB TERMINAL] refused stale-client resurrection of tombstoned ${id}`);
11918
10846
  throw new Error("terminal-gone");
11919
10847
  }
11920
10848
  }
@@ -11936,7 +10864,7 @@ class WebTerminalManager {
11936
10864
  "-e",
11937
10865
  `VH_TERMINAL_ID=${id}`,
11938
10866
  "-e",
11939
- `VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
10867
+ `VH_HAPPY_HOME_DIR=${persistence.configuration.happyHomeDir}`
11940
10868
  ] : [];
11941
10869
  const attachOnly = !!(opts.attachOnly || opts.resub);
11942
10870
  const attachTarget = !attachOnly && opts.attachTmux ? this.resolveAttachTarget(opts.attachTmux) : void 0;
@@ -12022,7 +10950,7 @@ class WebTerminalManager {
12022
10950
  ).status === 0;
12023
10951
  if (!alive) {
12024
10952
  if (attachTarget) throw new Error("tmux-unavailable");
12025
- logger.info(`[WEB TERMINAL] tmux session creation unavailable; using direct PTY for ${id}`);
10953
+ persistence.logger.info(`[WEB TERMINAL] tmux session creation unavailable; using direct PTY for ${id}`);
12026
10954
  tmuxSession = void 0;
12027
10955
  }
12028
10956
  }
@@ -12060,11 +10988,11 @@ class WebTerminalManager {
12060
10988
  created.announcedRows = size.rows;
12061
10989
  const marker = created.ingest(geometryMarker(size.cols, size.rows));
12062
10990
  this.emit("terminal-output", { terminalId: id, data: marker.data, seq: marker.seq });
12063
- logger.debug(`[WEB TERMINAL] ${id} pane geometry \u2192 ${size.cols}x${size.rows} (seq=${marker.seq})`);
10991
+ persistence.logger.debug(`[WEB TERMINAL] ${id} pane geometry \u2192 ${size.cols}x${size.rows} (seq=${marker.seq})`);
12064
10992
  return;
12065
10993
  }
12066
10994
  if (name === "exit") {
12067
- logger.debug(`[WEB TERMINAL] control client for ${id} got %exit (${args2})`);
10995
+ persistence.logger.debug(`[WEB TERMINAL] control client for ${id} got %exit (${args2})`);
12068
10996
  }
12069
10997
  },
12070
10998
  onExit: (code) => {
@@ -12113,7 +11041,7 @@ class WebTerminalManager {
12113
11041
  if (attachedTmux) session.attachedTmux = attachedTmux;
12114
11042
  this.terminals.set(id, session);
12115
11043
  session.onTitleChange(() => this.kickListRefresh());
12116
- logger.debug(`[WEB TERMINAL] opened ${id} (${tmuxSession ? "control" : "pty"}) ${cols}x${rows} cwd=${cwd}`);
11044
+ persistence.logger.debug(`[WEB TERMINAL] opened ${id} (${tmuxSession ? "control" : "pty"}) ${cols}x${rows} cwd=${cwd}`);
12117
11045
  this.kickListRefresh();
12118
11046
  const echo = attachedTmux ? { attachedTmux } : {};
12119
11047
  if (session.transport.kind !== "control") {
@@ -12147,7 +11075,7 @@ class WebTerminalManager {
12147
11075
  } catch {
12148
11076
  }
12149
11077
  }
12150
- logger.debug(`[WEB TERMINAL] ${tmuxSession} attaches user tmux session ${attachTarget.id} (${attachTarget.name})`);
11078
+ persistence.logger.debug(`[WEB TERMINAL] ${tmuxSession} attaches user tmux session ${attachTarget.id} (${attachTarget.name})`);
12151
11079
  return { id: attachTarget.id, name: attachTarget.name };
12152
11080
  }
12153
11081
  const startup = normalizeStartupCommand(startupCommand);
@@ -12156,7 +11084,7 @@ class WebTerminalManager {
12156
11084
  for (const a of startupInjectionArgs(tmuxSession, startup)) {
12157
11085
  node_child_process.spawnSync("tmux", tmuxArgs(a), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
12158
11086
  }
12159
- logger.debug(`[WEB TERMINAL] injected startup command into new session ${tmuxSession}`);
11087
+ persistence.logger.debug(`[WEB TERMINAL] injected startup command into new session ${tmuxSession}`);
12160
11088
  } catch {
12161
11089
  }
12162
11090
  }
@@ -12206,7 +11134,7 @@ class WebTerminalManager {
12206
11134
  );
12207
11135
  this.lastUserSessionsProbe = { status: r.status, error: r.error?.message, stderr: (r.stderr || "").toString().trim() || void 0, stdoutBytes: typeof r.stdout === "string" ? r.stdout.length : 0 };
12208
11136
  if (r.status !== 0 || typeof r.stdout !== "string") {
12209
- logger.debug(`[WEB TERMINAL] list-tmux-sessions failed: ${JSON.stringify(this.lastUserSessionsProbe)}`);
11137
+ persistence.logger.debug(`[WEB TERMINAL] list-tmux-sessions failed: ${JSON.stringify(this.lastUserSessionsProbe)}`);
12210
11138
  return [];
12211
11139
  }
12212
11140
  return parseUserSessions(r.stdout, max);
@@ -12253,7 +11181,7 @@ class WebTerminalManager {
12253
11181
  try {
12254
11182
  bodies = await client.send(requests, CAPTURE_TIMEOUT_MS);
12255
11183
  } catch (e) {
12256
- logger.debug(`[WEB TERMINAL] capture batch failed for ${session.id}: ${e}`);
11184
+ persistence.logger.debug(`[WEB TERMINAL] capture batch failed for ${session.id}: ${e}`);
12257
11185
  if (fresh) {
12258
11186
  this.terminals.delete(session.id);
12259
11187
  session.dispose();
@@ -12277,7 +11205,7 @@ class WebTerminalManager {
12277
11205
  }
12278
11206
  /** Build the lines-mode open response from a completed capture. */
12279
11207
  linesResponse(id, session, restored) {
12280
- logger.debug(`[WEB TERMINAL] lines open ${id}: seq=${restored.seqAtAnchor} small=${restored.small.length}B full=${restored.full.length}B pages=${restored.totalPages} alt=${restored.alternateOn}`);
11208
+ persistence.logger.debug(`[WEB TERMINAL] lines open ${id}: seq=${restored.seqAtAnchor} small=${restored.small.length}B full=${restored.full.length}B pages=${restored.totalPages} alt=${restored.alternateOn}`);
12281
11209
  return {
12282
11210
  terminalId: id,
12283
11211
  tmuxSession: session.tmuxSession,
@@ -12332,7 +11260,7 @@ class WebTerminalManager {
12332
11260
  const target = session.paneId ?? `=${session.tmuxSession}:`;
12333
11261
  const { commands, dropped } = encodeTerminalWrite(text, target, { normalizeKeyNames: true });
12334
11262
  if (dropped) {
12335
- logger.debug(`[WEB TERMINAL] dropped terminal auto-reply for ${terminalId}`);
11263
+ persistence.logger.debug(`[WEB TERMINAL] dropped terminal auto-reply for ${terminalId}`);
12336
11264
  return;
12337
11265
  }
12338
11266
  if (commands.length === 0) return;
@@ -12393,7 +11321,7 @@ class WebTerminalManager {
12393
11321
  try {
12394
11322
  session.transport.pty.resize(c, r);
12395
11323
  } catch (e) {
12396
- logger.debug(`[WEB TERMINAL] resize ${session.id} failed: ${e}`);
11324
+ persistence.logger.debug(`[WEB TERMINAL] resize ${session.id} failed: ${e}`);
12397
11325
  }
12398
11326
  } else if (c !== session.cols || r !== session.rows) {
12399
11327
  session.transport.client.sendFireAndForget([
@@ -12414,7 +11342,7 @@ class WebTerminalManager {
12414
11342
  if (!session) return;
12415
11343
  session.subscribers = Math.max(0, session.subscribers - 1);
12416
11344
  session.lastTouch = Date.now();
12417
- logger.debug(`[WEB TERMINAL] unsubscribed ${terminalId} (subs=${session.subscribers})`);
11345
+ persistence.logger.debug(`[WEB TERMINAL] unsubscribed ${terminalId} (subs=${session.subscribers})`);
12418
11346
  }
12419
11347
  /** Detach the pty (its single tmux client) without killing the tmux session.
12420
11348
  * Used by the reaper / cap eviction. Reopening the id respawns a pty and
@@ -12426,7 +11354,7 @@ class WebTerminalManager {
12426
11354
  this.snapshots.drop(terminalId);
12427
11355
  this.captureInFlight.delete(terminalId);
12428
11356
  session.dispose();
12429
- logger.debug(`[WEB TERMINAL] detached ${terminalId} (tmux session survives)`);
11357
+ persistence.logger.debug(`[WEB TERMINAL] detached ${terminalId} (tmux session survives)`);
12430
11358
  }
12431
11359
  /** Stop every live session (test teardown / daemon shutdown). Control
12432
11360
  * clients get the SIGTERM→SIGKILL discipline, so no tmux child is left
@@ -12450,20 +11378,20 @@ class WebTerminalManager {
12450
11378
  const killed = node_child_process.spawnSync("tmux", tmuxArgs(["kill-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
12451
11379
  const verified = node_child_process.spawnSync("tmux", tmuxArgs(["has-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
12452
11380
  if (!tmuxKillVerified(killed, verified)) {
12453
- logger.debug(`[WEB TERMINAL] failed to verify tmux session removal ${target}`);
11381
+ persistence.logger.debug(`[WEB TERMINAL] failed to verify tmux session removal ${target}`);
12454
11382
  return false;
12455
11383
  }
12456
11384
  if (opts?.alsoAttached && info?.attachTmux) {
12457
11385
  const matches = this.listUserTmuxSessions(Infinity).filter((s) => s.name === info.attachTmux);
12458
11386
  if (matches.length === 1) {
12459
11387
  node_child_process.spawnSync("tmux", tmuxArgs(["kill-session", "-t", matches[0].id]), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() });
12460
- logger.info(`[WEB TERMINAL] killed attached user tmux session ${matches[0].id} (${info.attachTmux}) with ${terminalId}`);
11388
+ persistence.logger.info(`[WEB TERMINAL] killed attached user tmux session ${matches[0].id} (${info.attachTmux}) with ${terminalId}`);
12461
11389
  } else {
12462
- logger.debug(`[WEB TERMINAL] alsoAttached: no unique session named ${info.attachTmux} (${matches.length} matches)`);
11390
+ persistence.logger.debug(`[WEB TERMINAL] alsoAttached: no unique session named ${info.attachTmux} (${matches.length} matches)`);
12463
11391
  }
12464
11392
  }
12465
11393
  if (info) {
12466
- const mirror = pickMirrorForTerminal(readPersistedSessions(), terminalId);
11394
+ const mirror = pickMirrorForTerminal(persistence.readPersistedSessions(), terminalId);
12467
11395
  this.recordClosed({
12468
11396
  id: terminalId,
12469
11397
  title: info.title,
@@ -12482,13 +11410,13 @@ class WebTerminalManager {
12482
11410
  try {
12483
11411
  this.onTerminalClosedCb?.(terminalId);
12484
11412
  } catch (e) {
12485
- logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
11413
+ persistence.logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
12486
11414
  }
12487
11415
  this.detach(terminalId);
12488
11416
  this.tombstones[terminalId] = Date.now();
12489
11417
  saveTombstones(this.tombstones);
12490
11418
  this.notifyTracker.remove(terminalId);
12491
- logger.debug(`[WEB TERMINAL] killed session vh-${terminalId}`);
11419
+ persistence.logger.debug(`[WEB TERMINAL] killed session vh-${terminalId}`);
12492
11420
  this.kickListRefresh();
12493
11421
  return true;
12494
11422
  }
@@ -12526,7 +11454,7 @@ class WebTerminalManager {
12526
11454
  { encoding: "utf8", env }
12527
11455
  );
12528
11456
  if (r.status !== 0 || !r.stdout) return [];
12529
- const hostname = os.hostname();
11457
+ const hostname = os$1.hostname();
12530
11458
  const out = [];
12531
11459
  for (const line of r.stdout.split("\n")) {
12532
11460
  const s = parseSessionListLine(line);
@@ -12758,7 +11686,7 @@ class WebTerminalManager {
12758
11686
 
12759
11687
  const TMUX_TIMEOUT_MS = 3e3;
12760
11688
  const VH_LIST_SESSIONS_FORMAT = LIST_SESSIONS_FORMAT;
12761
- function parseVhTerminals(stdout, hostname = os.hostname()) {
11689
+ function parseVhTerminals(stdout, hostname = os$1.hostname()) {
12762
11690
  const out = [];
12763
11691
  for (const line of stdout.split("\n")) {
12764
11692
  const s = parseSessionListLine(line);
@@ -12897,7 +11825,7 @@ function decideBackfill(messages, maxLines) {
12897
11825
  }
12898
11826
 
12899
11827
  function detectCLIAvailability() {
12900
- const isWindows = os$1.platform() === "win32";
11828
+ const isWindows = os.platform() === "win32";
12901
11829
  if (isWindows) {
12902
11830
  return detectWindows();
12903
11831
  }
@@ -12922,14 +11850,14 @@ function commandExistsWindows(name) {
12922
11850
  const SAFE_COMMAND_NAME = /^[A-Za-z0-9._-]+$/;
12923
11851
  function commandOnPath(name) {
12924
11852
  if (!SAFE_COMMAND_NAME.test(name)) return false;
12925
- return os$1.platform() === "win32" ? commandExistsWindows(name) : commandExists(name);
11853
+ return os.platform() === "win32" ? commandExistsWindows(name) : commandExists(name);
12926
11854
  }
12927
11855
  function detectPosix() {
12928
11856
  const claude = commandExists("claude");
12929
11857
  const codex = commandExists("codex");
12930
11858
  const gemini = commandExists("gemini");
12931
11859
  const openclawCommand = commandExists("openclaw");
12932
- const openclawConfig = fs$1.existsSync(path$1.join(os$1.homedir(), ".openclaw", "openclaw.json"));
11860
+ const openclawConfig = fs$1.existsSync(path.join(os.homedir(), ".openclaw", "openclaw.json"));
12933
11861
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12934
11862
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12935
11863
  const pi = commandExists("pi") && commandExists("pi-acp");
@@ -12941,7 +11869,7 @@ function detectWindows() {
12941
11869
  const codex = checkCommand("codex");
12942
11870
  const gemini = checkCommand("gemini");
12943
11871
  const openclawCommand = checkCommand("openclaw");
12944
- const openclawConfig = fs$1.existsSync(path$1.join(process.env.USERPROFILE || os$1.homedir(), ".openclaw", "openclaw.json"));
11872
+ const openclawConfig = fs$1.existsSync(path.join(process.env.USERPROFILE || os.homedir(), ".openclaw", "openclaw.json"));
12945
11873
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12946
11874
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12947
11875
  const pi = checkCommand("pi") && checkCommand("pi-acp");
@@ -12984,10 +11912,10 @@ function deriveContentKeyPair(secret) {
12984
11912
  secretKey: keyPair.secretKey
12985
11913
  };
12986
11914
  }
12987
- function getLocalHappyAgentCredentialPath(happyHomeDir = configuration.happyHomeDir) {
12988
- return path.join(happyHomeDir, "agent.key");
11915
+ function getLocalHappyAgentCredentialPath(happyHomeDir = persistence.configuration.happyHomeDir) {
11916
+ return path$1.join(happyHomeDir, "agent.key");
12989
11917
  }
12990
- function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDir, serverUrl = configuration.serverUrl) {
11918
+ function readLocalHappyAgentCredentials(happyHomeDir = persistence.configuration.happyHomeDir, serverUrl = persistence.configuration.serverUrl) {
12991
11919
  const credentialPath = getLocalHappyAgentCredentialPath(happyHomeDir);
12992
11920
  if (!node_fs.existsSync(credentialPath)) {
12993
11921
  return null;
@@ -12995,8 +11923,8 @@ function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDi
12995
11923
  try {
12996
11924
  const parsed = AgentCredentialsSchema.parse(JSON.parse(node_fs.readFileSync(credentialPath, "utf8")));
12997
11925
  if (!parsed.authServerUrl) return null;
12998
- if (credentialRelayProblem(parsed.authServerUrl, serverUrl)) return null;
12999
- const secret = decodeBase64(parsed.secret);
11926
+ if (persistence.credentialRelayProblem(parsed.authServerUrl, serverUrl)) return null;
11927
+ const secret = persistence.decodeBase64(parsed.secret);
13000
11928
  return {
13001
11929
  token: parsed.token,
13002
11930
  authServerUrl: parsed.authServerUrl,
@@ -13007,10 +11935,10 @@ function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDi
13007
11935
  return null;
13008
11936
  }
13009
11937
  }
13010
- function hasLocalHappyAgentAuth(happyHomeDir = configuration.happyHomeDir) {
11938
+ function hasLocalHappyAgentAuth(happyHomeDir = persistence.configuration.happyHomeDir) {
13011
11939
  return readLocalHappyAgentCredentials(happyHomeDir) !== null;
13012
11940
  }
13013
- function detectResumeSupport(happyHomeDir = configuration.happyHomeDir) {
11941
+ function detectResumeSupport(happyHomeDir = persistence.configuration.happyHomeDir) {
13014
11942
  const happyAgentAuthenticated = hasLocalHappyAgentAuth(happyHomeDir);
13015
11943
  return {
13016
11944
  rpcAvailable: happyAgentAuthenticated,
@@ -13043,7 +11971,7 @@ async function listClaudeProjectDirs(projectsRoot) {
13043
11971
  }
13044
11972
  const dirs = [];
13045
11973
  for (const name of names) {
13046
- const full = path.join(projectsRoot, name);
11974
+ const full = path$1.join(projectsRoot, name);
13047
11975
  try {
13048
11976
  if ((await promises.stat(full)).isDirectory()) dirs.push(full);
13049
11977
  } catch {
@@ -13064,11 +11992,11 @@ async function collectCandidates(projectDirs, exclude) {
13064
11992
  if (!name.endsWith(".jsonl")) continue;
13065
11993
  const id = name.slice(0, -".jsonl".length);
13066
11994
  if (!UUID_RE$1.test(id) || exclude.has(id.toLowerCase())) continue;
13067
- const path$1 = path.join(dir, name);
11995
+ const path = path$1.join(dir, name);
13068
11996
  try {
13069
- const s = await promises.stat(path$1);
11997
+ const s = await promises.stat(path);
13070
11998
  if (!s.isFile() || s.size === 0) continue;
13071
- out.push({ path: path$1, id, updatedAt: s.mtimeMs, sizeBytes: s.size });
11999
+ out.push({ path, id, updatedAt: s.mtimeMs, sizeBytes: s.size });
13072
12000
  } catch {
13073
12001
  }
13074
12002
  }
@@ -13213,7 +12141,7 @@ class ForkSourceMissingError extends Error {
13213
12141
  }
13214
12142
  }
13215
12143
  function jsonlPath(projectDir, sessionId) {
13216
- return path.join(projectDir, `${sessionId}.jsonl`);
12144
+ return path$1.join(projectDir, `${sessionId}.jsonl`);
13217
12145
  }
13218
12146
  function isUserPrompt(parsed) {
13219
12147
  if (!parsed || parsed.type !== "user") return false;
@@ -13236,10 +12164,10 @@ function userPromptText(parsed) {
13236
12164
  async function discardForkedSession(projectDir, claudeSessionId) {
13237
12165
  try {
13238
12166
  await promises.unlink(jsonlPath(projectDir, claudeSessionId));
13239
- logger.debug(`[CLAUDE FORK] Discarded unused fork ${claudeSessionId}`);
12167
+ persistence.logger.debug(`[CLAUDE FORK] Discarded unused fork ${claudeSessionId}`);
13240
12168
  } catch (error) {
13241
12169
  if (error.code !== "ENOENT") {
13242
- logger.debug(`[CLAUDE FORK] Failed to discard fork ${claudeSessionId}:`, error);
12170
+ persistence.logger.debug(`[CLAUDE FORK] Failed to discard fork ${claudeSessionId}:`, error);
13243
12171
  }
13244
12172
  }
13245
12173
  }
@@ -13255,7 +12183,7 @@ async function forkSession(projectDir, sourceClaudeSessionId) {
13255
12183
  }
13256
12184
  throw error;
13257
12185
  }
13258
- logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}`);
12186
+ persistence.logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}`);
13259
12187
  return newId;
13260
12188
  }
13261
12189
  async function forkAndTruncateSession(projectDir, sourceClaudeSessionId, cutAfterUuid) {
@@ -13277,7 +12205,7 @@ async function forkAndTruncateSession(projectDir, sourceClaudeSessionId, cutAfte
13277
12205
  try {
13278
12206
  parsed = JSON.parse(line);
13279
12207
  } catch {
13280
- logger.debug(`[CLAUDE FORK] Skipped malformed JSON line during truncation copy`);
12208
+ persistence.logger.debug(`[CLAUDE FORK] Skipped malformed JSON line during truncation copy`);
13281
12209
  }
13282
12210
  if (foundMarker && parsed && isUserPrompt(parsed)) {
13283
12211
  break;
@@ -13309,7 +12237,7 @@ async function forkAndTruncateSession(projectDir, sourceClaudeSessionId, cutAfte
13309
12237
  throw new ForkTruncateUuidNotFoundError(cutAfterUuid, src);
13310
12238
  }
13311
12239
  await promises.rename(tempDst, finalDst);
13312
- logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}, cut after ${cutAfterUuid}`);
12240
+ persistence.logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}, cut after ${cutAfterUuid}`);
13313
12241
  return newId;
13314
12242
  }
13315
12243
  async function listClaudeRewindPoints(projectDir, claudeSessionId) {
@@ -13446,11 +12374,11 @@ function errorLogMetadata(error) {
13446
12374
  }
13447
12375
 
13448
12376
  function expandPath(pathValue, sessionPath) {
13449
- const expandedHome = pathValue.replace(/^~(?=\/|$)/, os.homedir());
13450
- if (path.isAbsolute(expandedHome)) {
12377
+ const expandedHome = pathValue.replace(/^~(?=\/|$)/, os$1.homedir());
12378
+ if (path$1.isAbsolute(expandedHome)) {
13451
12379
  return expandedHome;
13452
12380
  }
13453
- return path.resolve(sessionPath, expandedHome);
12381
+ return path$1.resolve(sessionPath, expandedHome);
13454
12382
  }
13455
12383
  function resolvePaths(paths, sessionPath) {
13456
12384
  return paths.map((pathValue) => expandPath(pathValue, sessionPath));
@@ -13472,10 +12400,10 @@ function buildSandboxRuntimeConfig(sandboxConfig, sessionPath) {
13472
12400
  const allowWrite = (() => {
13473
12401
  switch (sandboxConfig.sessionIsolation) {
13474
12402
  case "strict":
13475
- return uniquePaths([path.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
12403
+ return uniquePaths([path$1.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
13476
12404
  case "workspace": {
13477
- const workspaceRoot = sandboxConfig.workspaceRoot ? expandPath(sandboxConfig.workspaceRoot, sessionPath) : path.resolve(sessionPath);
13478
- return uniquePaths([workspaceRoot, path.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
12405
+ const workspaceRoot = sandboxConfig.workspaceRoot ? expandPath(sandboxConfig.workspaceRoot, sessionPath) : path$1.resolve(sessionPath);
12406
+ return uniquePaths([workspaceRoot, path$1.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
13479
12407
  }
13480
12408
  case "custom":
13481
12409
  return uniquePaths([
@@ -13837,9 +12765,9 @@ class CodexAppServerClient {
13837
12765
  command = wrapped.command;
13838
12766
  args = wrapped.args;
13839
12767
  this.sandboxEnabled = true;
13840
- logger.info(`[CodexAppServer] Sandbox enabled`);
12768
+ persistence.logger.info(`[CodexAppServer] Sandbox enabled`);
13841
12769
  } catch (error) {
13842
- logger.warn("[CodexAppServer] Failed to initialize sandbox; continuing without.", error);
12770
+ persistence.logger.warn("[CodexAppServer] Failed to initialize sandbox; continuing without.", error);
13843
12771
  this.sandboxCleanup = null;
13844
12772
  }
13845
12773
  }
@@ -13856,7 +12784,7 @@ class CodexAppServerClient {
13856
12784
  if (this.sandboxEnabled) {
13857
12785
  env.CODEX_SANDBOX = "seatbelt";
13858
12786
  }
13859
- logger.debug(`[CodexAppServer] Spawning: ${command} ${args.join(" ")}`);
12787
+ persistence.logger.debug(`[CodexAppServer] Spawning: ${command} ${args.join(" ")}`);
13860
12788
  const epoch = ++this.processEpoch;
13861
12789
  const proc = spawn.spawn(command, args, {
13862
12790
  stdio: ["pipe", "pipe", "pipe"],
@@ -13865,12 +12793,12 @@ class CodexAppServerClient {
13865
12793
  });
13866
12794
  this.process = proc;
13867
12795
  proc.on("error", (err) => {
13868
- logger.debug("[CodexAppServer] Process error:", err);
12796
+ persistence.logger.debug("[CodexAppServer] Process error:", err);
13869
12797
  });
13870
12798
  proc.on("exit", (code, signal) => {
13871
- logger.debug(`[CodexAppServer] Process exited: code=${code} signal=${signal}`);
12799
+ persistence.logger.debug(`[CodexAppServer] Process exited: code=${code} signal=${signal}`);
13872
12800
  if (this.process !== proc || this.processEpoch !== epoch) {
13873
- logger.debug("[CodexAppServer] Ignoring stale process exit");
12801
+ persistence.logger.debug("[CodexAppServer] Ignoring stale process exit");
13874
12802
  return;
13875
12803
  }
13876
12804
  this.connected = false;
@@ -13884,7 +12812,7 @@ class CodexAppServerClient {
13884
12812
  proc.stderr?.on("data", (chunk) => {
13885
12813
  if (this.process !== proc || this.processEpoch !== epoch) return;
13886
12814
  const text = chunk.toString().trim();
13887
- if (text) logger.debug("[CodexAppServer:stderr]", contentLogMetadata(text));
12815
+ if (text) persistence.logger.debug("[CodexAppServer:stderr]", contentLogMetadata(text));
13888
12816
  });
13889
12817
  this.readline = node_readline.createInterface({ input: proc.stdout });
13890
12818
  this.readline.on("line", (line) => {
@@ -13895,7 +12823,7 @@ class CodexAppServerClient {
13895
12823
  clientInfo: {
13896
12824
  name: "happy-codex",
13897
12825
  title: "Happy Codex Client",
13898
- version: packageJson.version
12826
+ version: persistence.packageJson.version
13899
12827
  },
13900
12828
  capabilities: {
13901
12829
  experimentalApi: true
@@ -13904,14 +12832,14 @@ class CodexAppServerClient {
13904
12832
  await this.request("initialize", initParams);
13905
12833
  this.notify("initialized");
13906
12834
  this.connected = true;
13907
- logger.debug("[CodexAppServer] Connected and initialized");
12835
+ persistence.logger.debug("[CodexAppServer] Connected and initialized");
13908
12836
  }
13909
12837
  async disconnectInternal(opts) {
13910
12838
  if (!this.connected && !this.process) return;
13911
12839
  const proc = this.process;
13912
12840
  const pid = proc?.pid;
13913
12841
  const epoch = this.processEpoch;
13914
- logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
12842
+ persistence.logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
13915
12843
  this.readline?.close();
13916
12844
  this.readline = null;
13917
12845
  try {
@@ -13952,7 +12880,7 @@ class CodexAppServerClient {
13952
12880
  this.sandboxCleanup = null;
13953
12881
  }
13954
12882
  this.sandboxEnabled = false;
13955
- logger.debug("[CodexAppServer] Disconnected");
12883
+ persistence.logger.debug("[CodexAppServer] Disconnected");
13956
12884
  }
13957
12885
  async disconnect() {
13958
12886
  await this.disconnectInternal();
@@ -13990,7 +12918,7 @@ class CodexAppServerClient {
13990
12918
  this._threadId = result.thread.id;
13991
12919
  this._turnId = null;
13992
12920
  this.rememberThreadDefaults(opts);
13993
- logger.debug("[CodexAppServer] Thread started:", this._threadId);
12921
+ persistence.logger.debug("[CodexAppServer] Thread started:", this._threadId);
13994
12922
  return { threadId: result.thread.id, model: result.model };
13995
12923
  }
13996
12924
  async resumeThread(opts) {
@@ -14021,7 +12949,7 @@ class CodexAppServerClient {
14021
12949
  sandbox: opts?.sandbox ?? defaults.sandbox,
14022
12950
  mcpServers: opts?.mcpServers ?? defaults.mcpServers
14023
12951
  });
14024
- logger.debug("[CodexAppServer] Thread resumed:", this._threadId);
12952
+ persistence.logger.debug("[CodexAppServer] Thread resumed:", this._threadId);
14025
12953
  return { threadId: result.thread.id, model: result.model };
14026
12954
  }
14027
12955
  async forkThread(opts) {
@@ -14049,7 +12977,7 @@ class CodexAppServerClient {
14049
12977
  sandbox: opts.sandbox ?? defaults.sandbox,
14050
12978
  mcpServers: opts.mcpServers ?? defaults.mcpServers
14051
12979
  });
14052
- logger.debug("[CodexAppServer] Thread forked:", opts.threadId, "->", this._threadId);
12980
+ persistence.logger.debug("[CodexAppServer] Thread forked:", opts.threadId, "->", this._threadId);
14053
12981
  return { threadId: result.thread.id, model: result.model, thread: result.thread };
14054
12982
  }
14055
12983
  async readThread(opts) {
@@ -14084,7 +13012,7 @@ class CodexAppServerClient {
14084
13012
  await this.resumeThread({ threadId });
14085
13013
  return true;
14086
13014
  } catch (error) {
14087
- logger.warn("[CodexAppServer] Failed to resume thread after reconnect", error);
13015
+ persistence.logger.warn("[CodexAppServer] Failed to resume thread after reconnect", error);
14088
13016
  this._threadId = null;
14089
13017
  this.threadDefaults = null;
14090
13018
  return false;
@@ -14111,7 +13039,7 @@ class CodexAppServerClient {
14111
13039
  const pending = this.pendingTurnCompletion;
14112
13040
  if (!pending) return;
14113
13041
  if (pending.turnId && turnId && pending.turnId !== turnId) {
14114
- logger.debug(
13042
+ persistence.logger.debug(
14115
13043
  `[CodexAppServer] Ignoring ${source} for turn ${turnId}; awaiting ${pending.turnId}`
14116
13044
  );
14117
13045
  return;
@@ -14150,7 +13078,7 @@ class CodexAppServerClient {
14150
13078
  if (!shouldForceRestart) {
14151
13079
  return { hadActiveTurn: true, aborted: false, forcedRestart: false, resumedThread: false };
14152
13080
  }
14153
- logger.warn(`[CodexAppServer] interrupt did not settle turn in ${gracePeriodMs}ms; force-restarting app-server`);
13081
+ persistence.logger.warn(`[CodexAppServer] interrupt did not settle turn in ${gracePeriodMs}ms; force-restarting app-server`);
14154
13082
  const pendingTurnId = this.pendingTurnCompletion?.turnId ?? this._turnId;
14155
13083
  if (this.pendingTurnCompletion) {
14156
13084
  this.eventHandler?.({
@@ -14224,7 +13152,7 @@ class CodexAppServerClient {
14224
13152
  };
14225
13153
  timer = setTimeout(() => {
14226
13154
  if (this.pendingTurnCompletion) {
14227
- logger.warn(`[CodexAppServer] Turn timed out after ${timeoutMs}ms \u2014 treating as abort`);
13155
+ persistence.logger.warn(`[CodexAppServer] Turn timed out after ${timeoutMs}ms \u2014 treating as abort`);
14228
13156
  this.resolvePendingTurn(true);
14229
13157
  }
14230
13158
  }, timeoutMs);
@@ -14243,7 +13171,7 @@ class CodexAppServerClient {
14243
13171
  async interruptTurn() {
14244
13172
  if (!this._threadId) return;
14245
13173
  if (!this._turnId) {
14246
- logger.debug("[CodexAppServer] interruptTurn: no active turnId, skipping");
13174
+ persistence.logger.debug("[CodexAppServer] interruptTurn: no active turnId, skipping");
14247
13175
  return;
14248
13176
  }
14249
13177
  const params = {
@@ -14254,7 +13182,7 @@ class CodexAppServerClient {
14254
13182
  try {
14255
13183
  await this.request("turn/interrupt", params);
14256
13184
  } catch (err) {
14257
- logger.debug("[CodexAppServer] interruptTurn error (may be expected):", err);
13185
+ persistence.logger.debug("[CodexAppServer] interruptTurn error (may be expected):", err);
14258
13186
  } finally {
14259
13187
  this.pendingInterrupt = null;
14260
13188
  }
@@ -14267,7 +13195,7 @@ class CodexAppServerClient {
14267
13195
  return this._threadId !== null;
14268
13196
  }
14269
13197
  clearThreadState() {
14270
- logger.debug(
13198
+ persistence.logger.debug(
14271
13199
  `[CodexAppServer] Clearing thread state: thread=${this._threadId ?? "none"} turn=${this._turnId ?? "none"}`
14272
13200
  );
14273
13201
  this.resolvePendingTurn(true);
@@ -14306,7 +13234,7 @@ class CodexAppServerClient {
14306
13234
  });
14307
13235
  const msg = { jsonrpc: "2.0", id, method, params };
14308
13236
  const line = JSON.stringify(msg) + "\n";
14309
- logger.debug(`[CodexAppServer] \u2192 ${method} (id=${id})`);
13237
+ persistence.logger.debug(`[CodexAppServer] \u2192 ${method} (id=${id})`);
14310
13238
  this.process.stdin.write(line);
14311
13239
  });
14312
13240
  }
@@ -14314,13 +13242,13 @@ class CodexAppServerClient {
14314
13242
  if (!this.process?.stdin?.writable) return;
14315
13243
  const msg = { jsonrpc: "2.0", method, params };
14316
13244
  this.process.stdin.write(JSON.stringify(msg) + "\n");
14317
- logger.debug(`[CodexAppServer] \u2192 ${method} (notification)`);
13245
+ persistence.logger.debug(`[CodexAppServer] \u2192 ${method} (notification)`);
14318
13246
  }
14319
13247
  respond(id, result) {
14320
13248
  if (!this.process?.stdin?.writable) return;
14321
13249
  const msg = { jsonrpc: "2.0", id, result };
14322
13250
  this.process.stdin.write(JSON.stringify(msg) + "\n");
14323
- logger.debug(`[CodexAppServer] \u2192 response (id=${id})`);
13251
+ persistence.logger.debug(`[CodexAppServer] \u2192 response (id=${id})`);
14324
13252
  }
14325
13253
  handleLine(line, sourceEpoch = this.processEpoch) {
14326
13254
  if (sourceEpoch !== this.processEpoch) {
@@ -14331,14 +13259,14 @@ class CodexAppServerClient {
14331
13259
  try {
14332
13260
  msg = JSON.parse(line);
14333
13261
  } catch {
14334
- logger.debug("[CodexAppServer] Non-JSON line received:", contentLogMetadata(line));
13262
+ persistence.logger.debug("[CodexAppServer] Non-JSON line received:", contentLogMetadata(line));
14335
13263
  return;
14336
13264
  }
14337
13265
  if (msg.id != null && (msg.result !== void 0 || msg.error !== void 0)) {
14338
13266
  const pending = this.pending.get(msg.id);
14339
13267
  if (pending) {
14340
13268
  if (pending.epoch !== sourceEpoch) {
14341
- logger.debug(`[CodexAppServer] Ignoring response from stale epoch for id=${msg.id}`);
13269
+ persistence.logger.debug(`[CodexAppServer] Ignoring response from stale epoch for id=${msg.id}`);
14342
13270
  return;
14343
13271
  }
14344
13272
  this.pending.delete(msg.id);
@@ -14352,7 +13280,7 @@ class CodexAppServerClient {
14352
13280
  }
14353
13281
  if (msg.id != null && msg.method) {
14354
13282
  this.handleServerRequest(msg.id, msg.method, msg.params).catch((err) => {
14355
- logger.debug("[CodexAppServer] Error handling server request:", errorLogMetadata(err));
13283
+ persistence.logger.debug("[CodexAppServer] Error handling server request:", errorLogMetadata(err));
14356
13284
  });
14357
13285
  return;
14358
13286
  }
@@ -14360,7 +13288,7 @@ class CodexAppServerClient {
14360
13288
  this.handleNotification(msg.method, msg.params);
14361
13289
  return;
14362
13290
  }
14363
- logger.debug("[CodexAppServer] Unhandled message:", contentLogMetadata(msg));
13291
+ persistence.logger.debug("[CodexAppServer] Unhandled message:", contentLogMetadata(msg));
14364
13292
  }
14365
13293
  /**
14366
13294
  * Map our internal ReviewDecision to the wire format the server expects.
@@ -14472,7 +13400,7 @@ class CodexAppServerClient {
14472
13400
  this.respond(id, { decision: this.mapDecisionToWire(decision, legacy) });
14473
13401
  return;
14474
13402
  }
14475
- logger.debug(`[CodexAppServer] Unknown server request: ${method}`);
13403
+ persistence.logger.debug(`[CodexAppServer] Unknown server request: ${method}`);
14476
13404
  this.respond(id, {});
14477
13405
  }
14478
13406
  async handleApproval(params) {
@@ -14480,7 +13408,7 @@ class CodexAppServerClient {
14480
13408
  try {
14481
13409
  return await this.approvalHandler(params);
14482
13410
  } catch (err) {
14483
- logger.debug("[CodexAppServer] Approval handler error:", err);
13411
+ persistence.logger.debug("[CodexAppServer] Approval handler error:", err);
14484
13412
  return "denied";
14485
13413
  }
14486
13414
  }
@@ -14514,11 +13442,11 @@ class CodexAppServerClient {
14514
13442
  return;
14515
13443
  }
14516
13444
  if (this.handleRawNotification(method, params)) {
14517
- logger.debug(`[CodexAppServer] Raw notification: ${method}`);
13445
+ persistence.logger.debug(`[CodexAppServer] Raw notification: ${method}`);
14518
13446
  return;
14519
13447
  }
14520
13448
  if (method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed") {
14521
- logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
13449
+ persistence.logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
14522
13450
  if (method === "turn/started") {
14523
13451
  const turnId = this.extractTurnId(params);
14524
13452
  if (turnId) {
@@ -14537,10 +13465,10 @@ class CodexAppServerClient {
14537
13465
  return;
14538
13466
  }
14539
13467
  if (method === "mcpServer/startupStatus/updated") {
14540
- logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
13468
+ persistence.logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
14541
13469
  return;
14542
13470
  }
14543
- logger.debug(`[CodexAppServer] Notification: ${method}`);
13471
+ persistence.logger.debug(`[CodexAppServer] Notification: ${method}`);
14544
13472
  }
14545
13473
  }
14546
13474
 
@@ -14819,7 +13747,7 @@ class ApiMachineClient {
14819
13747
  scopePrefix: this.machine.id,
14820
13748
  encryptionKey: this.machine.encryptionKey,
14821
13749
  encryptionVariant: this.machine.encryptionVariant,
14822
- logger: (msg, data) => logger.debug(msg, data)
13750
+ logger: (msg, data) => persistence.logger.debug(msg, data)
14823
13751
  });
14824
13752
  registerCommonHandlers(this.rpcHandlerManager, process.cwd());
14825
13753
  registerFsHandlers(this.rpcHandlerManager);
@@ -14877,7 +13805,7 @@ class ApiMachineClient {
14877
13805
  if (this.relaySocket?.connected) this.relaySocket.emit(event, out);
14878
13806
  }, (n) => {
14879
13807
  sendTerminalNotification({
14880
- baseUrl: configuration.serverUrl,
13808
+ baseUrl: persistence.configuration.serverUrl,
14881
13809
  token: this.token,
14882
13810
  title: n.title,
14883
13811
  message: terminalNotifyMessage(n.event),
@@ -14907,12 +13835,12 @@ class ApiMachineClient {
14907
13835
  * Encrypted with the per-machine key; the server relays without reading.
14908
13836
  */
14909
13837
  pushClipboard(text) {
14910
- const prepared = limits.prepareClipboardText(text);
13838
+ const prepared = persistence.prepareClipboardText(text);
14911
13839
  if (!this.socket?.connected) {
14912
13840
  return { delivered: false, truncated: prepared.truncated, totalBytes: prepared.totalBytes, error: "daemon is not connected to the server" };
14913
13841
  }
14914
13842
  this.socket.emit("clipboard-push", {
14915
- payload: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, prepared.text)),
13843
+ payload: persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, prepared.text)),
14916
13844
  enc: true,
14917
13845
  truncated: prepared.truncated,
14918
13846
  totalBytes: prepared.totalBytes
@@ -14923,7 +13851,7 @@ class ApiMachineClient {
14923
13851
  * as the live output stream) → base64 ciphertext. Used for both live output
14924
13852
  * and the open-terminal snapshot/replay payloads. */
14925
13853
  encTerminalData(dataBase64) {
14926
- return encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, dataBase64));
13854
+ return persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, dataBase64));
14927
13855
  }
14928
13856
  setCliUpdateRetryHandler(handler) {
14929
13857
  this.rpcHandlerManager.registerHandler("cli-update-retry", (params) => handler(params && typeof params === "object" ? params.version : void 0));
@@ -14933,7 +13861,7 @@ class ApiMachineClient {
14933
13861
  setCliUpdateState(state) {
14934
13862
  this.cliUpdateState = state;
14935
13863
  if (!this.socket?.connected) return;
14936
- this.cliUpdatePushChain = this.cliUpdatePushChain.then(() => this.updateDaemonState((current) => withCurrentCliUpdateState(current, this.cliUpdateState))).catch((error) => logger.debug("[API MACHINE] Failed to publish CLI update policy:", error));
13864
+ this.cliUpdatePushChain = this.cliUpdatePushChain.then(() => this.updateDaemonState((current) => withCurrentCliUpdateState(current, this.cliUpdateState))).catch((error) => persistence.logger.debug("[API MACHINE] Failed to publish CLI update policy:", error));
14937
13865
  }
14938
13866
  /**
14939
13867
  * B-276: publish the daemon-context Claude auth preflight into daemonState
@@ -14950,7 +13878,7 @@ class ApiMachineClient {
14950
13878
  status: current?.status ?? "running",
14951
13879
  claudeAuth: state
14952
13880
  }))).then(() => this.machine.daemonState?.claudeAuth?.checkedAt === state.checkedAt && this.machine.daemonState?.claudeAuth?.daemonPid === state.daemonPid).catch((error) => {
14953
- logger.debug("[API MACHINE] Failed to publish Claude auth state:", error);
13881
+ persistence.logger.debug("[API MACHINE] Failed to publish Claude auth state:", error);
14954
13882
  return false;
14955
13883
  });
14956
13884
  this.cliUpdatePushChain = attempt.then(() => void 0);
@@ -14987,17 +13915,17 @@ class ApiMachineClient {
14987
13915
  this.listTrackedSessionIds = listTrackedSessionIds ?? null;
14988
13916
  this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
14989
13917
  const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, importedFromClaudeSessionId, variant, forceNew, permissionMode } = params || {};
14990
- logger.debug("[API MACHINE] Spawning session:", summarizeSpawnSessionForLog(params));
13918
+ persistence.logger.debug("[API MACHINE] Spawning session:", summarizeSpawnSessionForLog(params));
14991
13919
  if (!directory && variant !== "assistant") {
14992
13920
  throw new Error("Directory is required");
14993
13921
  }
14994
13922
  const result = await spawnSession({ directory: directory || "", sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, importedFromClaudeSessionId: typeof importedFromClaudeSessionId === "string" ? importedFromClaudeSessionId : void 0, variant: variant === "assistant" ? "assistant" : void 0, forceNew: forceNew === true, permissionMode: typeof permissionMode === "string" ? permissionMode : void 0 });
14995
13923
  switch (result.type) {
14996
13924
  case "success":
14997
- logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
13925
+ persistence.logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
14998
13926
  return { type: "success", sessionId: result.sessionId };
14999
13927
  case "requestToApproveDirectoryCreation":
15000
- logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
13928
+ persistence.logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
15001
13929
  return { type: "requestToApproveDirectoryCreation", directory: result.directory };
15002
13930
  case "error":
15003
13931
  throw new Error(result.errorMessage);
@@ -15045,7 +13973,7 @@ class ApiMachineClient {
15045
13973
  await discardForkedSession(projectDir, newClaudeSessionId);
15046
13974
  return result;
15047
13975
  }
15048
- logger.debug(`[API MACHINE] Imported Claude conversation ${claudeSessionId} as session ${result.sessionId}`);
13976
+ persistence.logger.debug(`[API MACHINE] Imported Claude conversation ${claudeSessionId} as session ${result.sessionId}`);
15049
13977
  return { type: "success", sessionId: result.sessionId, newClaudeSessionId };
15050
13978
  });
15051
13979
  this.syncResumeSessionRpcRegistration();
@@ -15170,7 +14098,7 @@ class ApiMachineClient {
15170
14098
  if (!success) {
15171
14099
  throw new Error("Session not found or failed to stop");
15172
14100
  }
15173
- logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
14101
+ persistence.logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
15174
14102
  return { message: "Session stopped" };
15175
14103
  });
15176
14104
  this.rpcHandlerManager.registerHandler("claude-fork-session", async (params) => {
@@ -15247,7 +14175,7 @@ class ApiMachineClient {
15247
14175
  const projectDirs = typeof directory === "string" ? [getProjectPath(directory)] : await listClaudeProjectDirs(getClaudeProjectsRoot());
15248
14176
  const excludeIds = [
15249
14177
  ...Array.isArray(exclude) ? exclude.filter((id) => typeof id === "string" && UUID_RE.test(id)) : [],
15250
- ...readTrackedClaudeSessionIds()
14178
+ ...persistence.readTrackedClaudeSessionIds()
15251
14179
  ];
15252
14180
  const result = await listClaudeSessionHistory({
15253
14181
  projectDirs,
@@ -15318,9 +14246,9 @@ class ApiMachineClient {
15318
14246
  }));
15319
14247
  });
15320
14248
  this.rpcHandlerManager.registerHandler("stop-daemon", () => {
15321
- logger.debug("[API MACHINE] Received stop-daemon RPC request");
14249
+ persistence.logger.debug("[API MACHINE] Received stop-daemon RPC request");
15322
14250
  setTimeout(() => {
15323
- logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
14251
+ persistence.logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
15324
14252
  requestShutdown();
15325
14253
  }, 100);
15326
14254
  return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
@@ -15403,7 +14331,7 @@ class ApiMachineClient {
15403
14331
  wireAutoRestoreReport() {
15404
14332
  this.webTerminal.setOnAutoRestoreSummary((line) => {
15405
14333
  sendTerminalNotification({
15406
- baseUrl: configuration.serverUrl,
14334
+ baseUrl: persistence.configuration.serverUrl,
15407
14335
  token: this.token,
15408
14336
  title: "Terminals restored",
15409
14337
  message: line,
@@ -15439,7 +14367,7 @@ class ApiMachineClient {
15439
14367
  try {
15440
14368
  this.mirrorListObserver?.(terminals);
15441
14369
  } catch (err) {
15442
- logger.debug("[API MACHINE] mirror list observer failed:", err);
14370
+ persistence.logger.debug("[API MACHINE] mirror list observer failed:", err);
15443
14371
  }
15444
14372
  this.latestTerminalList = terminals;
15445
14373
  if (!this.socket?.connected) return;
@@ -15458,9 +14386,9 @@ class ApiMachineClient {
15458
14386
  // Read at write time — same freshness rule as the list.
15459
14387
  closedTerminals: this.webTerminal.getClosedTerminals()
15460
14388
  }));
15461
- logger.debug(`[API MACHINE] Pushed terminal list (${list.length} terminals)`);
14389
+ persistence.logger.debug(`[API MACHINE] Pushed terminal list (${list.length} terminals)`);
15462
14390
  }).catch((err) => {
15463
- logger.debug("[API MACHINE] Terminal list push failed:", err);
14391
+ persistence.logger.debug("[API MACHINE] Terminal list push failed:", err);
15464
14392
  });
15465
14393
  }
15466
14394
  /**
@@ -15474,18 +14402,18 @@ class ApiMachineClient {
15474
14402
  const updated = handler(this.machine.metadata);
15475
14403
  const answer = await this.socket.emitWithAck("machine-update-metadata", {
15476
14404
  machineId: this.machine.id,
15477
- metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
14405
+ metadata: persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
15478
14406
  expectedVersion: this.machine.metadataVersion
15479
14407
  });
15480
14408
  if (answer.result === "success") {
15481
- this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
14409
+ this.machine.metadata = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.metadata));
15482
14410
  this.machine.metadataVersion = answer.version;
15483
14411
  rateRefusals = 0;
15484
- logger.debug("[API MACHINE] Metadata updated successfully");
14412
+ persistence.logger.debug("[API MACHINE] Metadata updated successfully");
15485
14413
  } else if (answer.result === "version-mismatch") {
15486
14414
  if (answer.version > this.machine.metadataVersion) {
15487
14415
  this.machine.metadataVersion = answer.version;
15488
- this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
14416
+ this.machine.metadata = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.metadata));
15489
14417
  }
15490
14418
  throw new Error("Metadata version mismatch");
15491
14419
  } else if (answer.result === "error") {
@@ -15496,7 +14424,7 @@ class ApiMachineClient {
15496
14424
  code: answer.error,
15497
14425
  attempt: rateRefusals,
15498
14426
  sleep: delay,
15499
- log: (message) => logger.debug(message)
14427
+ log: (message) => persistence.logger.debug(message)
15500
14428
  });
15501
14429
  }
15502
14430
  throw new Error("Machine metadata update failed");
@@ -15513,18 +14441,18 @@ class ApiMachineClient {
15513
14441
  const updated = handler(this.machine.daemonState);
15514
14442
  const answer = await this.socket.emitWithAck("machine-update-state", {
15515
14443
  machineId: this.machine.id,
15516
- daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
14444
+ daemonState: persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
15517
14445
  expectedVersion: this.machine.daemonStateVersion
15518
14446
  });
15519
14447
  if (answer.result === "success") {
15520
- this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
14448
+ this.machine.daemonState = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.daemonState));
15521
14449
  this.machine.daemonStateVersion = answer.version;
15522
14450
  rateRefusals = 0;
15523
- logger.debug("[API MACHINE] Daemon state updated successfully");
14451
+ persistence.logger.debug("[API MACHINE] Daemon state updated successfully");
15524
14452
  } else if (answer.result === "version-mismatch") {
15525
14453
  if (answer.version > this.machine.daemonStateVersion) {
15526
14454
  this.machine.daemonStateVersion = answer.version;
15527
- this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
14455
+ this.machine.daemonState = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.daemonState));
15528
14456
  }
15529
14457
  throw new Error("Daemon state version mismatch");
15530
14458
  } else if (answer.result === "error") {
@@ -15535,7 +14463,7 @@ class ApiMachineClient {
15535
14463
  code: answer.error,
15536
14464
  attempt: rateRefusals,
15537
14465
  sleep: delay,
15538
- log: (message) => logger.debug(message)
14466
+ log: (message) => persistence.logger.debug(message)
15539
14467
  });
15540
14468
  }
15541
14469
  throw new Error("Daemon state update failed");
@@ -15543,8 +14471,8 @@ class ApiMachineClient {
15543
14471
  });
15544
14472
  }
15545
14473
  connect() {
15546
- const serverUrl = configuration.serverUrl.replace(/^http/, "ws");
15547
- logger.debug(`[API MACHINE] Connecting to ${serverUrl}`);
14474
+ const serverUrl = persistence.configuration.serverUrl.replace(/^http/, "ws");
14475
+ persistence.logger.debug(`[API MACHINE] Connecting to ${serverUrl}`);
15548
14476
  this.socket = this.createControlSocket();
15549
14477
  const controlSocket = this.socket;
15550
14478
  this.bindRealtimeHandlers(controlSocket, "control");
@@ -15553,7 +14481,7 @@ class ApiMachineClient {
15553
14481
  this.activateControlSocket(controlSocket);
15554
14482
  });
15555
14483
  controlSocket.on("disconnect", (reason) => {
15556
- logger.debug(`[API MACHINE] Disconnected from server \u2014 reason: ${reason}`);
14484
+ persistence.logger.debug(`[API MACHINE] Disconnected from server \u2014 reason: ${reason}`);
15557
14485
  this.rpcHandlerManager.onSocketDisconnect(controlSocket);
15558
14486
  if (!this.relaySocket?.connected) this.webTerminal.resetSubscribers();
15559
14487
  this.stopKeepAlive();
@@ -15562,14 +14490,14 @@ class ApiMachineClient {
15562
14490
  this.bindControlDataHandlers(controlSocket);
15563
14491
  }
15564
14492
  createControlSocket(handover) {
15565
- const serverUrl = configuration.serverUrl.replace(/^http/, "ws");
14493
+ const serverUrl = persistence.configuration.serverUrl.replace(/^http/, "ws");
15566
14494
  return socket_ioClient.io(serverUrl, {
15567
14495
  transports: ["websocket"],
15568
14496
  auth: {
15569
14497
  token: this.token,
15570
14498
  clientType: "machine-scoped",
15571
14499
  machineId: this.machine.id,
15572
- happyClient: `cli-daemon/${configuration.currentCliVersion}`,
14500
+ happyClient: `cli-daemon/${persistence.configuration.currentCliVersion}`,
15573
14501
  ...handover ? { handoverEpoch: handover.epoch } : {}
15574
14502
  },
15575
14503
  ...handover ? { query: { vh_slot: handover.candidateSlot }, forceNew: true } : {},
@@ -15579,7 +14507,7 @@ class ApiMachineClient {
15579
14507
  });
15580
14508
  }
15581
14509
  activateControlSocket(socket, rpcAlreadyRegistered = false) {
15582
- logger.debug("[API MACHINE] Connected to server");
14510
+ persistence.logger.debug("[API MACHINE] Connected to server");
15583
14511
  if (this.reconnectInterval) {
15584
14512
  clearInterval(this.reconnectInterval);
15585
14513
  this.reconnectInterval = null;
@@ -15625,25 +14553,25 @@ class ApiMachineClient {
15625
14553
  if (data.body.t === "update-machine" && data.body.machineId === this.machine.id) {
15626
14554
  const update = data.body;
15627
14555
  if (update.metadata) {
15628
- logger.debug("[API MACHINE] Received external metadata update");
15629
- this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.metadata.value));
14556
+ persistence.logger.debug("[API MACHINE] Received external metadata update");
14557
+ this.machine.metadata = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(update.metadata.value));
15630
14558
  this.machine.metadataVersion = update.metadata.version;
15631
14559
  }
15632
14560
  if (update.daemonState) {
15633
- logger.debug("[API MACHINE] Received external daemon state update");
15634
- this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.daemonState.value));
14561
+ persistence.logger.debug("[API MACHINE] Received external daemon state update");
14562
+ this.machine.daemonState = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(update.daemonState.value));
15635
14563
  this.machine.daemonStateVersion = update.daemonState.version;
15636
14564
  }
15637
14565
  } else {
15638
- logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
14566
+ persistence.logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
15639
14567
  }
15640
14568
  });
15641
14569
  socket.on("connect_error", (error) => {
15642
- logger.debug(`[API MACHINE] Connection error: ${error.message}`);
14570
+ persistence.logger.debug(`[API MACHINE] Connection error: ${error.message}`);
15643
14571
  if (this.socket === socket) this.startSmartReconnect();
15644
14572
  });
15645
14573
  socket.io.on("error", (error) => {
15646
- logger.debug("[API MACHINE] Socket error:", error);
14574
+ persistence.logger.debug("[API MACHINE] Socket error:", error);
15647
14575
  });
15648
14576
  socket.on("server-draining", (data) => {
15649
14577
  if (this.socket !== socket) return;
@@ -15713,7 +14641,7 @@ class ApiMachineClient {
15713
14641
  this.rpcHandlerManager.onSocketDisconnect(candidate);
15714
14642
  candidate.close();
15715
14643
  if (previous.connected) previous.emit("release-handover-result", { result: "failed", durationMs: Date.now() - startedAt });
15716
- logger.debug(`[API MACHINE] Release handover failed: ${error instanceof Error ? error.message : error}`);
14644
+ persistence.logger.debug(`[API MACHINE] Release handover failed: ${error instanceof Error ? error.message : error}`);
15717
14645
  }
15718
14646
  }
15719
14647
  startKeepAlive() {
@@ -15724,7 +14652,7 @@ class ApiMachineClient {
15724
14652
  time: Date.now()
15725
14653
  };
15726
14654
  if (process.env.DEBUG) {
15727
- logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
14655
+ persistence.logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
15728
14656
  }
15729
14657
  this.socket.emit("machine-alive", payload);
15730
14658
  void this.refreshRelayConnection();
@@ -15746,15 +14674,15 @@ class ApiMachineClient {
15746
14674
  cliAvailability: newAvailability,
15747
14675
  resumeSupport: { ...newResumeSupport, rpcAvailable: !!this.resumeSessionHandler }
15748
14676
  })).catch((err) => {
15749
- logger.debug("[API MACHINE] Failed to update machine capabilities:", err);
14677
+ persistence.logger.debug("[API MACHINE] Failed to update machine capabilities:", err);
15750
14678
  });
15751
14679
  }
15752
14680
  }, 2e4);
15753
- logger.debug("[API MACHINE] Keep-alive started (20s interval)");
14681
+ persistence.logger.debug("[API MACHINE] Keep-alive started (20s interval)");
15754
14682
  }
15755
14683
  bindRpcRequestHandler(socket, transport) {
15756
14684
  socket.on("rpc-request", async (data, callback) => {
15757
- logger.debugLargeJson(`[API MACHINE] Received RPC request via ${transport}:`, data);
14685
+ persistence.logger.debugLargeJson(`[API MACHINE] Received RPC request via ${transport}:`, data);
15758
14686
  callback(await this.rpcHandlerManager.handleRequest(data));
15759
14687
  });
15760
14688
  }
@@ -15762,7 +14690,7 @@ class ApiMachineClient {
15762
14690
  socket.on("terminal-input", (data) => {
15763
14691
  let payload = data.data;
15764
14692
  if (data.enc) {
15765
- const dec = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(data.data));
14693
+ const dec = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(data.data));
15766
14694
  if (typeof dec !== "string") return;
15767
14695
  payload = dec;
15768
14696
  }
@@ -15773,7 +14701,7 @@ class ApiMachineClient {
15773
14701
  socket.on("session-archive", (data) => {
15774
14702
  if (typeof data?.sessionId !== "string") return;
15775
14703
  const stopped = this.stopSessionHandler?.(data.sessionId) ?? false;
15776
- logger.debug(`[API MACHINE] Server archived session ${data.sessionId}; local process stopped=${stopped}`);
14704
+ persistence.logger.debug(`[API MACHINE] Server archived session ${data.sessionId}; local process stopped=${stopped}`);
15777
14705
  });
15778
14706
  }
15779
14707
  unbindMachineCommandHandlers(socket) {
@@ -15793,12 +14721,12 @@ class ApiMachineClient {
15793
14721
  if (tracked.length === 0) return;
15794
14722
  try {
15795
14723
  for (let offset = 0; offset < tracked.length; offset += 500) {
15796
- const response = await fetch(`${configuration.serverUrl}/v1/sessions/archive-status`, {
14724
+ const response = await fetch(`${persistence.configuration.serverUrl}/v1/sessions/archive-status`, {
15797
14725
  method: "POST",
15798
14726
  headers: {
15799
14727
  "Authorization": `Bearer ${this.token}`,
15800
14728
  "Content-Type": "application/json",
15801
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
14729
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
15802
14730
  },
15803
14731
  body: JSON.stringify({ sessionIds: tracked.slice(offset, offset + 500) })
15804
14732
  });
@@ -15811,14 +14739,14 @@ class ApiMachineClient {
15811
14739
  }
15812
14740
  }
15813
14741
  } catch (error) {
15814
- logger.debug(`[API MACHINE] Archived-session reconciliation failed: ${error instanceof Error ? error.message : error}`);
14742
+ persistence.logger.debug(`[API MACHINE] Archived-session reconciliation failed: ${error instanceof Error ? error.message : error}`);
15815
14743
  }
15816
14744
  }
15817
14745
  async refreshRelayConnection() {
15818
14746
  if (this.relayRefreshInFlight) return this.relayRefreshInFlight;
15819
14747
  this.relayRefreshInFlight = (async () => {
15820
14748
  const selected = await discoverAndClaimRelay({
15821
- controlUrl: configuration.serverUrl,
14749
+ controlUrl: persistence.configuration.serverUrl,
15822
14750
  token: this.token,
15823
14751
  machineId: this.machine.id,
15824
14752
  connectedRelayId: this.relaySocket?.connected ? this.relayAssignment?.relayId : void 0,
@@ -15832,7 +14760,7 @@ class ApiMachineClient {
15832
14760
  const previousUrl = this.relayAssignment?.url;
15833
14761
  this.relayAssignment = selected.assignment;
15834
14762
  const probe = selected.probes.find((item) => item.relayId === selected.assignment.relayId);
15835
- logger.debug(`[API MACHINE] Regional relay selected: ${selected.assignment.relayId} (${selected.assignment.region}), RTT ${probe ? Math.round(probe.rttMs) : "unknown"}ms`);
14763
+ persistence.logger.debug(`[API MACHINE] Regional relay selected: ${selected.assignment.relayId} (${selected.assignment.region}), RTT ${probe ? Math.round(probe.rttMs) : "unknown"}ms`);
15836
14764
  if (this.relaySocket && previousUrl === selected.assignment.url) {
15837
14765
  this.relaySocket.auth = { token: selected.assignment.token };
15838
14766
  if (this.relaySocket.connected) return;
@@ -15856,7 +14784,7 @@ class ApiMachineClient {
15856
14784
  this.rpcHandlerManager.onSocketConnect(relaySocket);
15857
14785
  this.syncResumeSessionRpcRegistration();
15858
14786
  this.syncRestartSessionRpcRegistration();
15859
- logger.debug(`[API MACHINE] Connected to regional relay ${selected.assignment.relayId}`);
14787
+ persistence.logger.debug(`[API MACHINE] Connected to regional relay ${selected.assignment.relayId}`);
15860
14788
  });
15861
14789
  relaySocket.on("disconnect", () => {
15862
14790
  this.relaySwitchTracker = null;
@@ -15877,14 +14805,14 @@ class ApiMachineClient {
15877
14805
  return;
15878
14806
  }
15879
14807
  if (!shouldReconnect()) {
15880
- logger.debug("[API MACHINE] Still not ready to reconnect");
14808
+ persistence.logger.debug("[API MACHINE] Still not ready to reconnect");
15881
14809
  return;
15882
14810
  }
15883
- logger.debug("[API MACHINE] Attempting reconnect");
14811
+ persistence.logger.debug("[API MACHINE] Attempting reconnect");
15884
14812
  this.socket.connect();
15885
14813
  }, 3e3);
15886
14814
  if (shouldReconnect()) {
15887
- logger.debug("[API MACHINE] Network up + lid open \u2014 reconnecting in 1s");
14815
+ persistence.logger.debug("[API MACHINE] Network up + lid open \u2014 reconnecting in 1s");
15888
14816
  setTimeout(() => {
15889
14817
  if (!this.socket.connected) this.socket.connect();
15890
14818
  }, 1e3);
@@ -15894,11 +14822,11 @@ class ApiMachineClient {
15894
14822
  if (this.keepAliveInterval) {
15895
14823
  clearInterval(this.keepAliveInterval);
15896
14824
  this.keepAliveInterval = null;
15897
- logger.debug("[API MACHINE] Keep-alive stopped");
14825
+ persistence.logger.debug("[API MACHINE] Keep-alive stopped");
15898
14826
  }
15899
14827
  }
15900
14828
  shutdown() {
15901
- logger.debug("[API MACHINE] Shutting down");
14829
+ persistence.logger.debug("[API MACHINE] Shutting down");
15902
14830
  this.webTerminal.stopListTracking();
15903
14831
  this.stopKeepAlive();
15904
14832
  if (this.reconnectInterval) {
@@ -15907,13 +14835,13 @@ class ApiMachineClient {
15907
14835
  }
15908
14836
  if (this.socket) {
15909
14837
  this.socket.close();
15910
- logger.debug("[API MACHINE] Socket closed");
14838
+ persistence.logger.debug("[API MACHINE] Socket closed");
15911
14839
  }
15912
14840
  if (this.relaySocket) {
15913
14841
  this.rpcHandlerManager.onSocketDisconnect(this.relaySocket);
15914
14842
  this.relaySocket.close();
15915
14843
  this.relaySocket = null;
15916
- logger.debug("[API MACHINE] Regional relay socket closed");
14844
+ persistence.logger.debug("[API MACHINE] Regional relay socket closed");
15917
14845
  }
15918
14846
  }
15919
14847
  }
@@ -15983,24 +14911,24 @@ class PushNotificationClient {
15983
14911
  headers: {
15984
14912
  "Authorization": `Bearer ${this.token}`,
15985
14913
  "Content-Type": "application/json",
15986
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
14914
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
15987
14915
  }
15988
14916
  }
15989
14917
  );
15990
- logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
14918
+ persistence.logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
15991
14919
  response.data.tokens.forEach((token, index) => {
15992
- logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
14920
+ persistence.logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
15993
14921
  });
15994
14922
  return response.data.tokens;
15995
14923
  } catch (error) {
15996
- logger.debug(`[PUSH] [ERROR] Failed to fetch push tokens (attempt ${attempt}/${maxAttempts}):`, error);
14924
+ persistence.logger.debug(`[PUSH] [ERROR] Failed to fetch push tokens (attempt ${attempt}/${maxAttempts}):`, error);
15997
14925
  if (attempt < maxAttempts) {
15998
14926
  const delay = 1e3 * Math.pow(2, attempt - 1);
15999
14927
  await new Promise((resolve) => setTimeout(resolve, delay));
16000
14928
  }
16001
14929
  }
16002
14930
  }
16003
- logger.debug("[PUSH] [ERROR] All push token fetch attempts failed");
14931
+ persistence.logger.debug("[PUSH] [ERROR] All push token fetch attempts failed");
16004
14932
  return [];
16005
14933
  }
16006
14934
  /**
@@ -16008,7 +14936,7 @@ class PushNotificationClient {
16008
14936
  * @param messages - Array of push messages to send
16009
14937
  */
16010
14938
  async sendPushNotifications(messages) {
16011
- logger.debug(`Sending ${messages.length} push notifications`);
14939
+ persistence.logger.debug(`Sending ${messages.length} push notifications`);
16012
14940
  const validMessages = messages.filter((message) => {
16013
14941
  if (Array.isArray(message.to)) {
16014
14942
  return message.to.every((token) => expoServerSdk.Expo.isExpoPushToken(token));
@@ -16016,7 +14944,7 @@ class PushNotificationClient {
16016
14944
  return expoServerSdk.Expo.isExpoPushToken(message.to);
16017
14945
  });
16018
14946
  if (validMessages.length === 0) {
16019
- logger.debug("No valid Expo push tokens found");
14947
+ persistence.logger.debug("No valid Expo push tokens found");
16020
14948
  return;
16021
14949
  }
16022
14950
  const chunks = this.expo.chunkPushNotifications(validMessages);
@@ -16030,7 +14958,7 @@ class PushNotificationClient {
16030
14958
  const errors = ticketChunk.filter((ticket) => ticket.status === "error");
16031
14959
  if (errors.length > 0) {
16032
14960
  const errorDetails = errors.map((e) => ({ message: e.message, details: e.details }));
16033
- logger.debug("[PUSH] Some notifications failed:", errorDetails);
14961
+ persistence.logger.debug("[PUSH] Some notifications failed:", errorDetails);
16034
14962
  }
16035
14963
  if (errors.length === ticketChunk.length) {
16036
14964
  throw new Error("All push notifications in chunk failed");
@@ -16039,7 +14967,7 @@ class PushNotificationClient {
16039
14967
  } catch (error) {
16040
14968
  const elapsed = Date.now() - startTime;
16041
14969
  if (elapsed >= timeout) {
16042
- logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
14970
+ persistence.logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
16043
14971
  break;
16044
14972
  }
16045
14973
  attempt++;
@@ -16047,13 +14975,13 @@ class PushNotificationClient {
16047
14975
  const remainingTime = timeout - elapsed;
16048
14976
  const waitTime = Math.min(delay, remainingTime);
16049
14977
  if (waitTime > 0) {
16050
- logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
14978
+ persistence.logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
16051
14979
  await new Promise((resolve) => setTimeout(resolve, waitTime));
16052
14980
  }
16053
14981
  }
16054
14982
  }
16055
14983
  }
16056
- logger.debug(`Push notifications sent successfully`);
14984
+ persistence.logger.debug(`Push notifications sent successfully`);
16057
14985
  }
16058
14986
  /**
16059
14987
  * Send a push notification to all registered devices for the user
@@ -16062,25 +14990,25 @@ class PushNotificationClient {
16062
14990
  * @param data - Additional data to send with the notification
16063
14991
  */
16064
14992
  sendToAllDevices(title, body, data) {
16065
- logger.debug("[PUSH] sendToAllDevices called:", {
14993
+ persistence.logger.debug("[PUSH] sendToAllDevices called:", {
16066
14994
  title: contentLogMetadata(title),
16067
14995
  body: contentLogMetadata(body),
16068
14996
  dataKeyCount: data ? Object.keys(data).length : 0
16069
14997
  });
16070
14998
  (async () => {
16071
14999
  try {
16072
- logger.debug("[PUSH] Fetching push tokens...");
15000
+ persistence.logger.debug("[PUSH] Fetching push tokens...");
16073
15001
  const tokens = await this.fetchPushTokens();
16074
- logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
15002
+ persistence.logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
16075
15003
  tokens.forEach((token, index) => {
16076
- logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
15004
+ persistence.logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
16077
15005
  });
16078
15006
  if (tokens.length === 0) {
16079
- logger.debug("No push tokens found for user");
15007
+ persistence.logger.debug("No push tokens found for user");
16080
15008
  return;
16081
15009
  }
16082
15010
  const messages = tokens.map((token, index) => {
16083
- logger.debug(`[PUSH] Creating message ${index + 1} for token`);
15011
+ persistence.logger.debug(`[PUSH] Creating message ${index + 1} for token`);
16084
15012
  return {
16085
15013
  to: token.token,
16086
15014
  title,
@@ -16093,11 +15021,11 @@ class PushNotificationClient {
16093
15021
  priority: "high"
16094
15022
  };
16095
15023
  });
16096
- logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
15024
+ persistence.logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
16097
15025
  await this.sendPushNotifications(messages);
16098
- logger.debug("[PUSH] Push notifications sent successfully");
15026
+ persistence.logger.debug("[PUSH] Push notifications sent successfully");
16099
15027
  } catch (error) {
16100
- logger.debug("[PUSH] Error sending to all devices:", error);
15028
+ persistence.logger.debug("[PUSH] Error sending to all devices:", error);
16101
15029
  }
16102
15030
  })();
16103
15031
  }
@@ -16119,7 +15047,7 @@ class PushNotificationClient {
16119
15047
  };
16120
15048
  const sessionId = typeof params.data?.sessionId === "string" ? params.data.sessionId : null;
16121
15049
  if (!sessionId) {
16122
- logger.debug("[PUSH] sendSessionNotification: missing sessionId, falling back to direct send");
15050
+ persistence.logger.debug("[PUSH] sendSessionNotification: missing sessionId, falling back to direct send");
16123
15051
  this.sendToAllDevices(title, body, payloadData);
16124
15052
  return;
16125
15053
  }
@@ -16137,14 +15065,14 @@ class PushNotificationClient {
16137
15065
  headers: {
16138
15066
  "Authorization": `Bearer ${this.token}`,
16139
15067
  "Content-Type": "application/json",
16140
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
15068
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
16141
15069
  },
16142
15070
  timeout: 15e3
16143
15071
  }
16144
15072
  );
16145
- logger.debug(`[PUSH] sendSessionNotification dispatched via server (kind=${params.kind})`);
15073
+ persistence.logger.debug(`[PUSH] sendSessionNotification dispatched via server (kind=${params.kind})`);
16146
15074
  } catch (error) {
16147
- logger.debug("[PUSH] sendSessionNotification failed:", error);
15075
+ persistence.logger.debug("[PUSH] sendSessionNotification failed:", error);
16148
15076
  }
16149
15077
  })();
16150
15078
  }
@@ -16162,7 +15090,7 @@ function startOfflineReconnection(config) {
16162
15090
  validateStatus: (status) => status < 500,
16163
15091
  // 4xx = server is up, 5xx = server error
16164
15092
  headers: {
16165
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
15093
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
16166
15094
  }
16167
15095
  });
16168
15096
  };
@@ -16178,16 +15106,16 @@ function startOfflineReconnection(config) {
16178
15106
  if (cancelled) return;
16179
15107
  reconnected = true;
16180
15108
  config.onNotify("\u2705 Reconnected! Session syncing in background.");
16181
- logger.debug("[OfflineReconnection] Successfully reconnected");
15109
+ persistence.logger.debug("[OfflineReconnection] Successfully reconnected");
16182
15110
  } catch (e) {
16183
15111
  if (axios.isAxiosError(e) && e.response?.status === 401) {
16184
- logger.debug("[OfflineReconnection] Authentication error, stopping retries");
15112
+ persistence.logger.debug("[OfflineReconnection] Authentication error, stopping retries");
16185
15113
  config.onNotify("\u274C Authentication failed. Please re-authenticate with `very-happy auth`.");
16186
15114
  return;
16187
15115
  }
16188
15116
  failureCount++;
16189
15117
  const delay = retryDelayMs(failureCount);
16190
- logger.debug(`[OfflineReconnection] Attempt ${failureCount} failed, retrying in ${delay}ms`);
15118
+ persistence.logger.debug(`[OfflineReconnection] Attempt ${failureCount} failed, retrying in ${delay}ms`);
16191
15119
  if (!cancelled) {
16192
15120
  timeoutId = setTimeout(attemptReconnect, delay);
16193
15121
  }
@@ -16281,7 +15209,7 @@ const connectionState = new OfflineState();
16281
15209
 
16282
15210
  function accountBoxPublicKeyFromCredentials(credential) {
16283
15211
  if (credential.encryption.type === "legacy") {
16284
- return libsodiumPublicKeyFromSecretKey(credential.encryption.secret);
15212
+ return persistence.libsodiumPublicKeyFromSecretKey(credential.encryption.secret);
16285
15213
  }
16286
15214
  return credential.encryption.publicKey;
16287
15215
  }
@@ -16348,7 +15276,7 @@ class NotificationProducer {
16348
15276
  const now = Date.now();
16349
15277
  const last = this.lastEmittedAt.get(repeatKey);
16350
15278
  if (last !== void 0 && now - last < DEDUP_WINDOW_MS) {
16351
- logger.debug(`[notif] Suppressing duplicate ${notifType} for ${this.sessionId}`);
15279
+ persistence.logger.debug(`[notif] Suppressing duplicate ${notifType} for ${this.sessionId}`);
16352
15280
  return;
16353
15281
  }
16354
15282
  this.lastEmittedAt.set(repeatKey, now);
@@ -16361,10 +15289,10 @@ class NotificationProducer {
16361
15289
  let enc;
16362
15290
  try {
16363
15291
  const plaintext = new TextEncoder().encode(JSON.stringify(body));
16364
- const bundle = libsodiumEncryptForPublicKey(plaintext, this.accountBoxPubKey);
16365
- enc = encodeBase64(bundle);
15292
+ const bundle = persistence.libsodiumEncryptForPublicKey(plaintext, this.accountBoxPubKey);
15293
+ enc = persistence.encodeBase64(bundle);
16366
15294
  } catch (err) {
16367
- logger.debug("[notif] Failed to encrypt notification body:", err);
15295
+ persistence.logger.debug("[notif] Failed to encrypt notification body:", err);
16368
15296
  return;
16369
15297
  }
16370
15298
  void this.post(notifType, enc, repeatKey);
@@ -16372,7 +15300,7 @@ class NotificationProducer {
16372
15300
  async post(notifType, enc, repeatKey) {
16373
15301
  try {
16374
15302
  await axios.post(
16375
- `${configuration.serverUrl}/v1/feed`,
15303
+ `${persistence.configuration.serverUrl}/v1/feed`,
16376
15304
  {
16377
15305
  notifType,
16378
15306
  sessionId: this.sessionId,
@@ -16383,14 +15311,14 @@ class NotificationProducer {
16383
15311
  headers: {
16384
15312
  "Authorization": `Bearer ${this.token}`,
16385
15313
  "Content-Type": "application/json",
16386
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15314
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16387
15315
  },
16388
15316
  timeout: 5e3
16389
15317
  }
16390
15318
  );
16391
- logger.debug(`[notif] Posted ${notifType} for session ${this.sessionId}`);
15319
+ persistence.logger.debug(`[notif] Posted ${notifType} for session ${this.sessionId}`);
16392
15320
  } catch (err) {
16393
- logger.debug(`[notif] Failed to post ${notifType}:`, err);
15321
+ persistence.logger.debug(`[notif] Failed to post ${notifType}:`, err);
16394
15322
  }
16395
15323
  }
16396
15324
  /** Short, human-friendly label for the project/session for use in titles. */
@@ -16428,7 +15356,7 @@ class ApiClient {
16428
15356
  pushClient;
16429
15357
  constructor(credential) {
16430
15358
  this.credential = credential;
16431
- this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
15359
+ this.pushClient = new PushNotificationClient(credential.token, persistence.configuration.serverUrl);
16432
15360
  }
16433
15361
  /**
16434
15362
  * Create a new session or load existing one with the given tag
@@ -16438,9 +15366,9 @@ class ApiClient {
16438
15366
  let encryptionKey;
16439
15367
  let encryptionVariant;
16440
15368
  if (this.credential.encryption.type === "dataKey") {
16441
- encryptionKey = getRandomBytes(32);
15369
+ encryptionKey = persistence.getRandomBytes(32);
16442
15370
  encryptionVariant = "dataKey";
16443
- let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
15371
+ let encryptedDataKey = persistence.libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
16444
15372
  dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
16445
15373
  dataEncryptionKey.set([0], 0);
16446
15374
  dataEncryptionKey.set(encryptedDataKey, 1);
@@ -16450,38 +15378,38 @@ class ApiClient {
16450
15378
  }
16451
15379
  try {
16452
15380
  const response = await axios.post(
16453
- `${configuration.serverUrl}/v1/sessions`,
15381
+ `${persistence.configuration.serverUrl}/v1/sessions`,
16454
15382
  {
16455
15383
  tag: opts.tag,
16456
- metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
16457
- agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
16458
- dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null
15384
+ metadata: persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.metadata)),
15385
+ agentState: opts.state ? persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
15386
+ dataEncryptionKey: dataEncryptionKey ? persistence.encodeBase64(dataEncryptionKey) : null
16459
15387
  },
16460
15388
  {
16461
15389
  headers: {
16462
15390
  "Authorization": `Bearer ${this.credential.token}`,
16463
15391
  "Content-Type": "application/json",
16464
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15392
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16465
15393
  },
16466
15394
  timeout: 6e4
16467
15395
  // 1 minute timeout for very bad network connections
16468
15396
  }
16469
15397
  );
16470
- logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
15398
+ persistence.logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
16471
15399
  let raw = response.data.session;
16472
15400
  let session = {
16473
15401
  id: raw.id,
16474
15402
  seq: raw.seq,
16475
- metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
15403
+ metadata: persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.metadata)),
16476
15404
  metadataVersion: raw.metadataVersion,
16477
- agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
15405
+ agentState: raw.agentState ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.agentState)) : null,
16478
15406
  agentStateVersion: raw.agentStateVersion,
16479
15407
  encryptionKey,
16480
15408
  encryptionVariant
16481
15409
  };
16482
15410
  return session;
16483
15411
  } catch (error) {
16484
- logger.debug("[API] [ERROR] Failed to get or create session:", error);
15412
+ persistence.logger.debug("[API] [ERROR] Failed to get or create session:", error);
16485
15413
  if (error && typeof error === "object" && "code" in error) {
16486
15414
  const errorCode = error.code;
16487
15415
  if (isNetworkError(errorCode)) {
@@ -16489,7 +15417,7 @@ class ApiClient {
16489
15417
  operation: "Session creation",
16490
15418
  caller: "api.getOrCreateSession",
16491
15419
  errorCode,
16492
- url: `${configuration.serverUrl}/v1/sessions`
15420
+ url: `${persistence.configuration.serverUrl}/v1/sessions`
16493
15421
  });
16494
15422
  return null;
16495
15423
  }
@@ -16499,7 +15427,7 @@ class ApiClient {
16499
15427
  connectionState.fail({
16500
15428
  operation: "Session creation",
16501
15429
  errorCode: "404",
16502
- url: `${configuration.serverUrl}/v1/sessions`
15430
+ url: `${persistence.configuration.serverUrl}/v1/sessions`
16503
15431
  });
16504
15432
  return null;
16505
15433
  }
@@ -16509,7 +15437,7 @@ class ApiClient {
16509
15437
  connectionState.fail({
16510
15438
  operation: "Session creation",
16511
15439
  errorCode: String(status),
16512
- url: `${configuration.serverUrl}/v1/sessions`,
15440
+ url: `${persistence.configuration.serverUrl}/v1/sessions`,
16513
15441
  details: ["Server encountered an error, will retry automatically"]
16514
15442
  });
16515
15443
  return null;
@@ -16529,7 +15457,7 @@ class ApiClient {
16529
15457
  if (this.credential.encryption.type === "dataKey") {
16530
15458
  encryptionVariant = "dataKey";
16531
15459
  encryptionKey = this.credential.encryption.machineKey;
16532
- let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
15460
+ let encryptedDataKey = persistence.libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
16533
15461
  dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
16534
15462
  dataEncryptionKey.set([0], 0);
16535
15463
  dataEncryptionKey.set(encryptedDataKey, 1);
@@ -16548,32 +15476,32 @@ class ApiClient {
16548
15476
  });
16549
15477
  try {
16550
15478
  const response = await axios.post(
16551
- `${configuration.serverUrl}/v1/machines`,
15479
+ `${persistence.configuration.serverUrl}/v1/machines`,
16552
15480
  {
16553
15481
  id: opts.machineId,
16554
- metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
16555
- daemonState: opts.daemonState ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
16556
- dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : void 0
15482
+ metadata: persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.metadata)),
15483
+ daemonState: opts.daemonState ? persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
15484
+ dataEncryptionKey: dataEncryptionKey ? persistence.encodeBase64(dataEncryptionKey) : void 0
16557
15485
  },
16558
15486
  {
16559
15487
  headers: {
16560
15488
  "Authorization": `Bearer ${this.credential.token}`,
16561
15489
  "Content-Type": "application/json",
16562
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15490
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16563
15491
  },
16564
15492
  timeout: 6e4
16565
15493
  // 1 minute timeout for very bad network connections
16566
15494
  }
16567
15495
  );
16568
15496
  const raw = response.data.machine;
16569
- logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
15497
+ persistence.logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
16570
15498
  const machine = {
16571
15499
  id: raw.id,
16572
15500
  encryptionKey,
16573
15501
  encryptionVariant,
16574
- metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
15502
+ metadata: raw.metadata ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.metadata)) : null,
16575
15503
  metadataVersion: raw.metadataVersion || 0,
16576
- daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
15504
+ daemonState: raw.daemonState ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.daemonState)) : null,
16577
15505
  daemonStateVersion: raw.daemonStateVersion || 0
16578
15506
  };
16579
15507
  return machine;
@@ -16583,7 +15511,7 @@ class ApiClient {
16583
15511
  operation: "Machine registration",
16584
15512
  caller: "api.getOrCreateMachine",
16585
15513
  errorCode: error.code,
16586
- url: `${configuration.serverUrl}/v1/machines`
15514
+ url: `${persistence.configuration.serverUrl}/v1/machines`
16587
15515
  });
16588
15516
  return createMinimalMachine();
16589
15517
  }
@@ -16614,7 +15542,7 @@ class ApiClient {
16614
15542
  connectionState.fail({
16615
15543
  operation: "Machine registration",
16616
15544
  errorCode: "429",
16617
- url: `${configuration.serverUrl}/v1/machines`,
15545
+ url: `${persistence.configuration.serverUrl}/v1/machines`,
16618
15546
  details: ["Relay write quota is cooling down; realtime connection will retry automatically"]
16619
15547
  });
16620
15548
  return createMinimalMachine();
@@ -16623,7 +15551,7 @@ class ApiClient {
16623
15551
  connectionState.fail({
16624
15552
  operation: "Machine registration",
16625
15553
  errorCode: String(status),
16626
- url: `${configuration.serverUrl}/v1/machines`,
15554
+ url: `${persistence.configuration.serverUrl}/v1/machines`,
16627
15555
  details: ["Server encountered an error, will retry automatically"]
16628
15556
  });
16629
15557
  return createMinimalMachine();
@@ -16632,7 +15560,7 @@ class ApiClient {
16632
15560
  connectionState.fail({
16633
15561
  operation: "Machine registration",
16634
15562
  errorCode: "404",
16635
- url: `${configuration.serverUrl}/v1/machines`
15563
+ url: `${persistence.configuration.serverUrl}/v1/machines`
16636
15564
  });
16637
15565
  return createMinimalMachine();
16638
15566
  }
@@ -16671,11 +15599,11 @@ class ApiClient {
16671
15599
  async kvGet(key) {
16672
15600
  try {
16673
15601
  const response = await axios.get(
16674
- `${configuration.serverUrl}/v1/kv/${encodeURIComponent(key)}`,
15602
+ `${persistence.configuration.serverUrl}/v1/kv/${encodeURIComponent(key)}`,
16675
15603
  {
16676
15604
  headers: {
16677
15605
  "Authorization": `Bearer ${this.credential.token}`,
16678
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15606
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16679
15607
  },
16680
15608
  timeout: 5e3,
16681
15609
  validateStatus: (s) => s === 200 || s === 404
@@ -16684,7 +15612,7 @@ class ApiClient {
16684
15612
  if (response.status === 404) return null;
16685
15613
  return response.data;
16686
15614
  } catch (error) {
16687
- logger.debug(`[API] kvGet(${key}) failed:`, error);
15615
+ persistence.logger.debug(`[API] kvGet(${key}) failed:`, error);
16688
15616
  return null;
16689
15617
  }
16690
15618
  }
@@ -16695,7 +15623,7 @@ class ApiClient {
16695
15623
  async registerVendorToken(vendor, apiKey) {
16696
15624
  try {
16697
15625
  const response = await axios.post(
16698
- `${configuration.serverUrl}/v1/connect/${vendor}/register`,
15626
+ `${persistence.configuration.serverUrl}/v1/connect/${vendor}/register`,
16699
15627
  {
16700
15628
  token: JSON.stringify(apiKey)
16701
15629
  },
@@ -16703,7 +15631,7 @@ class ApiClient {
16703
15631
  headers: {
16704
15632
  "Authorization": `Bearer ${this.credential.token}`,
16705
15633
  "Content-Type": "application/json",
16706
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15634
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16707
15635
  },
16708
15636
  timeout: 5e3
16709
15637
  }
@@ -16711,9 +15639,9 @@ class ApiClient {
16711
15639
  if (response.status !== 200 && response.status !== 201) {
16712
15640
  throw new Error(`Server returned status ${response.status}`);
16713
15641
  }
16714
- logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
15642
+ persistence.logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
16715
15643
  } catch (error) {
16716
- logger.debug(`[API] [ERROR] Failed to register vendor token:`, redactLogValue(error));
15644
+ persistence.logger.debug(`[API] [ERROR] Failed to register vendor token:`, persistence.redactLogValue(error));
16717
15645
  throw new Error(`Failed to register vendor token: ${error instanceof Error ? error.message : "Unknown error"}`);
16718
15646
  }
16719
15647
  }
@@ -16724,24 +15652,24 @@ class ApiClient {
16724
15652
  async getVendorToken(vendor) {
16725
15653
  try {
16726
15654
  const response = await axios.get(
16727
- `${configuration.serverUrl}/v1/connect/${vendor}/token`,
15655
+ `${persistence.configuration.serverUrl}/v1/connect/${vendor}/token`,
16728
15656
  {
16729
15657
  headers: {
16730
15658
  "Authorization": `Bearer ${this.credential.token}`,
16731
15659
  "Content-Type": "application/json",
16732
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15660
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16733
15661
  },
16734
15662
  timeout: 5e3
16735
15663
  }
16736
15664
  );
16737
15665
  if (response.status === 404) {
16738
- logger.debug(`[API] No vendor token found for ${vendor}`);
15666
+ persistence.logger.debug(`[API] No vendor token found for ${vendor}`);
16739
15667
  return null;
16740
15668
  }
16741
15669
  if (response.status !== 200) {
16742
15670
  throw new Error(`Server returned status ${response.status}`);
16743
15671
  }
16744
- logger.debug(`[API] Raw vendor token response:`, {
15672
+ persistence.logger.debug(`[API] Raw vendor token response:`, {
16745
15673
  status: response.status,
16746
15674
  dataKeys: Object.keys(response.data || {}),
16747
15675
  hasToken: "token" in (response.data || {}),
@@ -16753,37 +15681,37 @@ class ApiClient {
16753
15681
  try {
16754
15682
  tokenData = JSON.parse(response.data.token);
16755
15683
  } catch (parseError) {
16756
- logger.debug(`[API] Failed to parse token as JSON, using as string:`, parseError);
15684
+ persistence.logger.debug(`[API] Failed to parse token as JSON, using as string:`, parseError);
16757
15685
  tokenData = response.data.token;
16758
15686
  }
16759
15687
  } else if (response.data.token !== null) {
16760
15688
  tokenData = response.data.token;
16761
15689
  } else {
16762
- logger.debug(`[API] Token is null for ${vendor}, treating as not found`);
15690
+ persistence.logger.debug(`[API] Token is null for ${vendor}, treating as not found`);
16763
15691
  return null;
16764
15692
  }
16765
15693
  } else if (response.data && typeof response.data === "object") {
16766
15694
  if (response.data.token === null && Object.keys(response.data).length === 1) {
16767
- logger.debug(`[API] Response contains only null token for ${vendor}, treating as not found`);
15695
+ persistence.logger.debug(`[API] Response contains only null token for ${vendor}, treating as not found`);
16768
15696
  return null;
16769
15697
  }
16770
15698
  tokenData = response.data;
16771
15699
  }
16772
15700
  if (tokenData === null || tokenData && typeof tokenData === "object" && tokenData.token === null && Object.keys(tokenData).length === 1) {
16773
- logger.debug(`[API] Token data is null for ${vendor}`);
15701
+ persistence.logger.debug(`[API] Token data is null for ${vendor}`);
16774
15702
  return null;
16775
15703
  }
16776
- logger.debug(`[API] Vendor token for ${vendor} retrieved successfully`, {
15704
+ persistence.logger.debug(`[API] Vendor token for ${vendor} retrieved successfully`, {
16777
15705
  tokenDataType: typeof tokenData,
16778
15706
  tokenDataKeys: tokenData && typeof tokenData === "object" ? Object.keys(tokenData) : "not an object"
16779
15707
  });
16780
15708
  return tokenData;
16781
15709
  } catch (error) {
16782
15710
  if (error.response?.status === 404) {
16783
- logger.debug(`[API] No vendor token found for ${vendor}`);
15711
+ persistence.logger.debug(`[API] No vendor token found for ${vendor}`);
16784
15712
  return null;
16785
15713
  }
16786
- logger.debug(`[API] [ERROR] Failed to get vendor token:`, redactLogValue(error));
15714
+ persistence.logger.debug(`[API] [ERROR] Failed to get vendor token:`, persistence.redactLogValue(error));
16787
15715
  return null;
16788
15716
  }
16789
15717
  }
@@ -16801,19 +15729,19 @@ class ApiClient {
16801
15729
  async deactivateSession(sessionId) {
16802
15730
  try {
16803
15731
  const response = await axios.post(
16804
- `${configuration.serverUrl}/v1/sessions/${sessionId}/archive`,
15732
+ `${persistence.configuration.serverUrl}/v1/sessions/${sessionId}/archive`,
16805
15733
  {},
16806
15734
  {
16807
15735
  headers: {
16808
15736
  "Authorization": `Bearer ${this.credential.token}`,
16809
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15737
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16810
15738
  },
16811
15739
  timeout: 3e3
16812
15740
  }
16813
15741
  );
16814
15742
  return response.status >= 200 && response.status < 300;
16815
15743
  } catch (error) {
16816
- logger.debug("[API] deactivateSession failed:", error);
15744
+ persistence.logger.debug("[API] deactivateSession failed:", error);
16817
15745
  return false;
16818
15746
  }
16819
15747
  }
@@ -16832,11 +15760,11 @@ class ApiClient {
16832
15760
  for (let attempt = 0; attempt < attempts; attempt += 1) {
16833
15761
  try {
16834
15762
  const response = await axios.get(
16835
- `${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
15763
+ `${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
16836
15764
  {
16837
15765
  headers: {
16838
15766
  "Authorization": `Bearer ${this.credential.token}`,
16839
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15767
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16840
15768
  },
16841
15769
  timeout: opts?.timeoutMs ?? 4e3,
16842
15770
  validateStatus: () => true
@@ -16850,17 +15778,17 @@ class ApiClient {
16850
15778
  if (!row || typeof row.seq !== "number" || typeof row.metadata !== "string") {
16851
15779
  return { ok: false, reason: "unsupported" };
16852
15780
  }
16853
- const metadata = decrypt(encryptionKey, encryptionVariant, decodeBase64(row.metadata));
15781
+ const metadata = persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(row.metadata));
16854
15782
  if (!metadata) return { ok: false, reason: "unavailable" };
16855
- const agentState = row.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(row.agentState)) : null;
15783
+ const agentState = row.agentState ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(row.agentState)) : null;
16856
15784
  return {
16857
15785
  ok: true,
16858
15786
  session: { seq: row.seq, metadata, metadataVersion: row.metadataVersion, agentState, agentStateVersion: row.agentStateVersion }
16859
15787
  };
16860
15788
  }
16861
- logger.debug(`[API] getSession ${sessionId}: status ${response.status} (attempt ${attempt + 1}/${attempts})`);
15789
+ persistence.logger.debug(`[API] getSession ${sessionId}: status ${response.status} (attempt ${attempt + 1}/${attempts})`);
16862
15790
  } catch (error) {
16863
- logger.debug(`[API] getSession ${sessionId} failed (attempt ${attempt + 1}/${attempts}):`, error);
15791
+ persistence.logger.debug(`[API] getSession ${sessionId} failed (attempt ${attempt + 1}/${attempts}):`, error);
16864
15792
  }
16865
15793
  const delayMs = delays[Math.min(attempt, delays.length - 1)];
16866
15794
  if (attempt < attempts - 1 && delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
@@ -16873,12 +15801,12 @@ class ApiClient {
16873
15801
  async reactivateSession(sessionId) {
16874
15802
  try {
16875
15803
  const response = await axios.post(
16876
- `${configuration.serverUrl}/v1/sessions/${sessionId}/unarchive`,
15804
+ `${persistence.configuration.serverUrl}/v1/sessions/${sessionId}/unarchive`,
16877
15805
  {},
16878
15806
  {
16879
15807
  headers: {
16880
15808
  "Authorization": `Bearer ${this.credential.token}`,
16881
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15809
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16882
15810
  },
16883
15811
  timeout: 3e3,
16884
15812
  validateStatus: (status) => status >= 200 && status < 300 || status === 404
@@ -16886,7 +15814,7 @@ class ApiClient {
16886
15814
  );
16887
15815
  return response.status >= 200 && response.status < 300 || response.status === 404;
16888
15816
  } catch (error) {
16889
- logger.debug("[API] reactivateSession failed:", error);
15817
+ persistence.logger.debug("[API] reactivateSession failed:", error);
16890
15818
  return false;
16891
15819
  }
16892
15820
  }
@@ -16970,67 +15898,36 @@ exports.MAX_CHAT_ATTACHMENT_SOURCE_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES;
16970
15898
  exports.MIRROR_BACKFILL_LINES_DEFAULT = MIRROR_BACKFILL_LINES_DEFAULT;
16971
15899
  exports.RawJSONLinesSchema = RawJSONLinesSchema;
16972
15900
  exports.STREAM_DELTA_MAX_CHARS = STREAM_DELTA_MAX_CHARS;
16973
- exports.SandboxConfigSchema = SandboxConfigSchema;
16974
15901
  exports.TITLE_PROMPT_PREFIX = TITLE_PROMPT_PREFIX;
16975
- exports.acquireDaemonLock = acquireDaemonLock;
16976
15902
  exports.api = api;
16977
- exports.clearCredentials = clearCredentials;
16978
- exports.clearDaemonState = clearDaemonState;
16979
- exports.clearMachineId = clearMachineId;
16980
15903
  exports.commandOnPath = commandOnPath;
16981
15904
  exports.compareExactVersions = compareExactVersions;
16982
- exports.configuration = configuration;
16983
15905
  exports.connectionState = connectionState;
16984
15906
  exports.contentLogMetadata = contentLogMetadata;
16985
15907
  exports.createEnvelope = createEnvelope;
16986
- exports.credentialRelayProblem = credentialRelayProblem;
16987
15908
  exports.decideBackfill = decideBackfill;
16988
15909
  exports.decideMirrorBinding = decideMirrorBinding;
16989
- exports.decodeBase64 = decodeBase64;
16990
- exports.decrypt = decrypt;
16991
- exports.decryptLegacy = decryptLegacy;
16992
- exports.decryptWithDataKey = decryptWithDataKey;
16993
15910
  exports.delay = delay;
16994
- exports.deletePersistedSessions = deletePersistedSessions;
16995
15911
  exports.deriveLocalCliUpdateSummary = deriveLocalCliUpdateSummary;
16996
15912
  exports.detectCLIAvailability = detectCLIAvailability;
16997
15913
  exports.detectResumeSupport = detectResumeSupport;
16998
- exports.encodeBase64 = encodeBase64;
16999
- exports.encodeBase64Url = encodeBase64Url;
17000
- exports.encrypt = encrypt;
17001
- exports.ensurePrivateDirectory = ensurePrivateDirectory;
17002
- exports.ensurePrivateDirectorySync = ensurePrivateDirectorySync;
17003
15914
  exports.errorLogMetadata = errorLogMetadata;
17004
15915
  exports.extractCompleteLines = extractCompleteLines;
17005
15916
  exports.fetchCliUpdateState = fetchCliUpdateState;
17006
- exports.getLatestDaemonLog = getLatestDaemonLog;
17007
15917
  exports.getLocalHappyAgentCredentialPath = getLocalHappyAgentCredentialPath;
17008
15918
  exports.getProjectPath = getProjectPath;
17009
- exports.hardenPrivateFile = hardenPrivateFile;
17010
- exports.hardenPrivateFileSync = hardenPrivateFileSync;
17011
15919
  exports.initializeSandbox = initializeSandbox;
17012
15920
  exports.isClaudeSessionId = isClaudeSessionId;
17013
15921
  exports.isValidSessionId = isValidSessionId;
17014
15922
  exports.isValidTerminalId = isValidTerminalId;
17015
15923
  exports.listVhTerminals = listVhTerminals;
17016
- exports.logger = logger;
17017
15924
  exports.mirrorLineKey = mirrorLineKey;
17018
15925
  exports.mirrorLocalId = mirrorLocalId;
17019
- exports.packageJson = packageJson;
17020
15926
  exports.parseExactVersion = parseExactVersion;
17021
15927
  exports.parseTerminalHookPayload = parseTerminalHookPayload;
17022
- exports.persistSession = persistSession;
17023
- exports.persistence = persistence;
17024
15928
  exports.projectPath = projectPath;
17025
- exports.readCredentials = readCredentials;
17026
- exports.readCredentialsForConfiguredRelay = readCredentialsForConfiguredRelay;
17027
- exports.readDaemonState = readDaemonState;
17028
15929
  exports.readLocalHappyAgentCredentials = readLocalHappyAgentCredentials;
17029
- exports.readPersistedSessions = readPersistedSessions;
17030
- exports.readSettings = readSettings;
17031
- exports.readSettingsOutcome = readSettingsOutcome;
17032
15930
  exports.readVhTerminal = readVhTerminal;
17033
- exports.releaseDaemonLock = releaseDaemonLock;
17034
15931
  exports.resolveCliUpdateCheckInterval = resolveCliUpdateCheckInterval;
17035
15932
  exports.scrubTmuxClientEnv = scrubTmuxClientEnv;
17036
15933
  exports.sendToVhTerminal = sendToVhTerminal;
@@ -17039,10 +15936,4 @@ exports.startOfflineReconnection = startOfflineReconnection;
17039
15936
  exports.streamKeyOf = streamKeyOf;
17040
15937
  exports.summarizeSpawnSessionForLog = summarizeSpawnSessionForLog;
17041
15938
  exports.tmuxArgs = tmuxArgs;
17042
- exports.updateSettings = updateSettings;
17043
15939
  exports.wrapCommand = wrapCommand;
17044
- exports.writeCredentialsDataKey = writeCredentialsDataKey;
17045
- exports.writeCredentialsLegacy = writeCredentialsLegacy;
17046
- exports.writeDaemonState = writeDaemonState;
17047
- exports.writePrivateFileSync = writePrivateFileSync;
17048
- exports.writeSettings = writeSettings;