very-happy-cli 0.2.124 → 0.2.126

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-BzGpBAZ0.mjs} +2 -1
  2. package/dist/{AcpBackend-au0veuIF.cjs → AcpBackend-C9eu6CDu.cjs} +30 -29
  3. package/dist/{AcpSessionManager-p1tj7FxF.mjs → AcpSessionManager-B223yT78.mjs} +1 -1
  4. package/dist/{AcpSessionManager-BCWeBKDd.cjs → AcpSessionManager-DQ1e_0gc.cjs} +9 -9
  5. package/dist/agentGuidance-BR0OFumQ.mjs +224 -0
  6. package/dist/agentGuidance-Tm8Z5HYp.cjs +237 -0
  7. package/dist/codex/happyMcpStdioBridge.cjs +29 -6
  8. package/dist/codex/happyMcpStdioBridge.mjs +25 -2
  9. package/dist/{config-DwMic6AC.mjs → config-Bjd1nG0P.mjs} +11 -11
  10. package/dist/{config-GzzqE5Ev.cjs → config-C0mofvCH.cjs} +11 -11
  11. package/dist/{index-B3-daYaX.mjs → index-BP56asuS.mjs} +8 -8
  12. package/dist/{index-DgN9P02i.cjs → index-CMbKpntk.cjs} +53 -19
  13. package/dist/{index-Ba6Fu6MJ.mjs → index-CV6sAa4a.mjs} +49 -15
  14. package/dist/{index-DI_VC3vJ.cjs → index-Cw9Zj1Zr.cjs} +2191 -1864
  15. package/dist/{index-SQKnWyz-.mjs → index-D4eCRu9x.mjs} +2046 -1719
  16. package/dist/{index-C69uF6Up.cjs → index-HW-KdG6W.cjs} +8 -8
  17. package/dist/index.cjs +5 -5
  18. package/dist/index.mjs +5 -5
  19. package/dist/{installTerminalHooks-CvMt8cBI.cjs → installTerminalHooks-CNp1LExn.cjs} +9 -9
  20. package/dist/{installTerminalHooks-Df4hzzdM.mjs → installTerminalHooks-D-CNSalI.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-DG1OQnCb.cjs +1198 -0
  26. package/dist/limits-DlbYYtOw.mjs +1129 -0
  27. package/dist/{mcp-DnyLwSPq.cjs → mcp-BtZoIIIM.cjs} +11 -10
  28. package/dist/{mcp-C0oW5u-b.mjs → mcp-D3oV_dH-.mjs} +7 -6
  29. package/dist/resources-CVl7NdOB.cjs +83 -0
  30. package/dist/resources-D-mn7ptO.mjs +80 -0
  31. package/dist/{runGemini-mqLjz-M7.mjs → runGemini-BJ8bU1pP.mjs} +13 -13
  32. package/dist/{runGemini-_FHrxuHN.cjs → runGemini-BfCSOPL1.cjs} +30 -30
  33. package/dist/{runOpenClaw-C5VZQ6Na.cjs → runOpenClaw-CBlHXEAT.cjs} +24 -24
  34. package/dist/{runOpenClaw-Bs03yQHe.mjs → runOpenClaw-DTgOpJnO.mjs} +9 -9
  35. package/dist/{send-BaLjQPZQ.mjs → send-9duZlpGX.mjs} +10 -10
  36. package/dist/{send-BD2gTVoP.cjs → send-CDK1bZFj.cjs} +10 -10
  37. package/dist/{sessions-B108j_Xj.cjs → sessions-CNkLCorF.cjs} +11 -11
  38. package/dist/{sessions-ggBePfSY.mjs → sessions-jAFOkKvW.mjs} +10 -10
  39. package/dist/{spawn-CORFRb9D.mjs → spawn-BG5oHi_f.mjs} +21 -21
  40. package/dist/{spawn-iI8w8sD7.cjs → spawn-J-A5WGZ7.cjs} +21 -21
  41. package/dist/teams-Cuqsk7Ty.mjs +189 -0
  42. package/dist/teams-MgWeyutG.cjs +191 -0
  43. package/dist/{types-CZIwVSN2.cjs → types-P5deH8Ls.cjs} +464 -1571
  44. package/dist/{types-gDOmwOKw.mjs → types-n_VWu9qe.mjs} +78 -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-DG1OQnCb.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,36 @@ 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 TeamPermissionModeSchema = z.z.enum(["default", "bypassPermissions"]);
458
+ const id = z.z.string().min(1).max(128);
459
+ const text = z.z.string().min(1).max(32e3);
460
+ const TeamActionSchema = z.z.discriminatedUnion("type", [
461
+ z.z.object({ type: z.z.literal("archive") }),
462
+ z.z.object({ type: z.z.literal("set-permission-mode"), permissionMode: TeamPermissionModeSchema }),
463
+ 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() }),
464
+ z.z.object({ type: z.z.literal("schedule-pause"), scheduleId: id, version: z.z.number().int().positive() }),
465
+ z.z.object({ type: z.z.literal("schedule-resume"), scheduleId: id, version: z.z.number().int().positive() }),
466
+ z.z.object({ type: z.z.literal("schedule-cancel"), scheduleId: id, version: z.z.number().int().positive() }),
467
+ z.z.object({ type: z.z.literal("reconcile-operation"), operationId: id, claimId: id, note: z.z.string().trim().min(1).max(4e3) }),
468
+ z.z.object({ type: z.z.literal("join"), name: id, sessionId: id, botId: id.optional() }),
469
+ 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() }),
470
+ z.z.object({ type: z.z.literal("message"), taskId: id, body: text, recipientBotId: id.optional() }),
471
+ z.z.object({ type: z.z.literal("submit"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), result: text }),
472
+ z.z.object({ type: z.z.literal("accept"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive() }),
473
+ z.z.object({ type: z.z.literal("return"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), reason: text }),
474
+ z.z.object({ type: z.z.literal("cancel"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), reason: text }),
475
+ z.z.object({ type: z.z.literal("handoff"), taskId: id, attemptId: id, goalVersion: z.z.number().int().positive(), assigneeBotId: id }),
476
+ z.z.object({ type: z.z.literal("claim-operation"), operationId: id, machineId: id }),
477
+ z.z.object({ type: z.z.literal("complete-operation"), operationId: id, machineId: id, claimId: id, sessionId: id.optional() }),
478
+ z.z.object({ type: z.z.literal("fail-operation"), operationId: id, machineId: id, claimId: id, error: text, unknown: z.z.boolean().optional() }),
479
+ z.z.object({ type: z.z.literal("session-event"), sessionId: id, event: z.z.enum(["idle", "blocked", "exited"]) }),
480
+ z.z.object({ type: z.z.literal("message-delivered"), messageId: id, recipientBotId: id, generation: z.z.number().int().positive(), sessionId: id })
481
+ ]);
482
+ z.z.object({ requestId: id, action: TeamActionSchema });
483
+ z.z.object({ name: id, machineId: id, requestId: id.optional() });
1051
484
 
1052
485
  z.z.object({
486
+ teamsVersion: z.z.number().int().positive().optional(),
1053
487
  host: z.z.string(),
1054
488
  platform: z.z.string(),
1055
489
  happyCliVersion: z.z.string(),
@@ -1324,125 +758,6 @@ const AgentMessageSchema = z.z.object({
1324
758
  });
1325
759
  z.z.union([UserMessageSchema, AgentMessageSchema]);
1326
760
 
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
761
  async function delay(ms) {
1447
762
  return new Promise((resolve) => setTimeout(resolve, ms));
1448
763
  }
@@ -1474,7 +789,7 @@ function createBackoff(opts) {
1474
789
  }
1475
790
  let backoff = createBackoff({
1476
791
  onError: (e, failuresCount) => {
1477
- logger.debug(`[BACKOFF] retry ${failuresCount}:`, e?.message || e);
792
+ persistence.logger.debug(`[BACKOFF] retry ${failuresCount}:`, e?.message || e);
1478
793
  }
1479
794
  });
1480
795
 
@@ -1572,7 +887,7 @@ class RpcHandlerManager {
1572
887
  this.scopePrefix = config.scopePrefix;
1573
888
  this.encryptionKey = config.encryptionKey;
1574
889
  this.encryptionVariant = config.encryptionVariant;
1575
- this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
890
+ this.logger = config.logger || ((msg, data) => persistence.logger.debug(msg, data));
1576
891
  }
1577
892
  /**
1578
893
  * Register an RPC handler for a specific method
@@ -1600,14 +915,14 @@ class RpcHandlerManager {
1600
915
  if (!handler) {
1601
916
  this.logger("[RPC] [ERROR] Method not found", { method: request.method });
1602
917
  const errorResponse = { error: "Method not found" };
1603
- const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
918
+ const encryptedError = persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
1604
919
  return encryptedError;
1605
920
  }
1606
- const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
921
+ const decryptedParams = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(request.params));
1607
922
  this.logger("[RPC] Calling handler", { method: request.method });
1608
923
  const result = await handler(decryptedParams);
1609
924
  this.logger("[RPC] Handler returned", { method: request.method, hasResult: result !== void 0 });
1610
- const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
925
+ const encryptedResponse = persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, result));
1611
926
  this.logger("[RPC] Sending encrypted response", { method: request.method, responseLength: encryptedResponse.length });
1612
927
  return encryptedResponse;
1613
928
  } catch (error) {
@@ -1615,7 +930,7 @@ class RpcHandlerManager {
1615
930
  const errorResponse = {
1616
931
  error: error instanceof Error ? error.message : "Unknown error"
1617
932
  };
1618
- return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
933
+ return persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
1619
934
  }
1620
935
  }
1621
936
  onSocketConnect(socket) {
@@ -1708,14 +1023,14 @@ class RpcHandlerManager {
1708
1023
  }
1709
1024
  }
1710
1025
 
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))));
1026
+ 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-P5deH8Ls.cjs', document.baseURI).href))));
1712
1027
  function projectPath() {
1713
- const path = path$1.resolve(__dirname$1, "..");
1714
- return path;
1028
+ const path$1 = path.resolve(__dirname$1, "..");
1029
+ return path$1;
1715
1030
  }
1716
1031
 
1717
1032
  function run$1(args, options) {
1718
- const RUNNER_PATH = path$1.resolve(path$1.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
1033
+ const RUNNER_PATH = path.resolve(path.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
1719
1034
  return new Promise((resolve2, reject) => {
1720
1035
  const child = spawn.spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
1721
1036
  stdio: ["pipe", "pipe", "pipe"],
@@ -1744,9 +1059,9 @@ function run$1(args, options) {
1744
1059
  }
1745
1060
 
1746
1061
  function getBinaryPath() {
1747
- const platformName = os$1.platform();
1062
+ const platformName = os.platform();
1748
1063
  const binaryName = platformName === "win32" ? "difft.exe" : "difft";
1749
- return path$1.resolve(path$1.join(projectPath(), "tools", "unpacked", binaryName));
1064
+ return path.resolve(path.join(projectPath(), "tools", "unpacked", binaryName));
1750
1065
  }
1751
1066
  function run(args, options) {
1752
1067
  const binaryPath = getBinaryPath();
@@ -1783,9 +1098,9 @@ function run(args, options) {
1783
1098
  }
1784
1099
 
1785
1100
  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) {
1101
+ const resolvedTarget = path.resolve(workingDirectory, targetPath);
1102
+ const resolvedWorkingDir = path.resolve(workingDirectory);
1103
+ if (!resolvedTarget.startsWith(resolvedWorkingDir + path.sep) && resolvedTarget !== resolvedWorkingDir) {
1789
1104
  return {
1790
1105
  valid: false,
1791
1106
  resolvedPath: resolvedTarget,
@@ -1858,9 +1173,9 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1858
1173
  const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
1859
1174
  await Promise.all(entries.map(async (entry) => {
1860
1175
  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(() => {
1176
+ const path$1 = path.join(dir, entry.name);
1177
+ const fileStat = await fs.stat(path$1).catch(() => null);
1178
+ if (fileStat && fileStat.mtimeMs < cutoff) await fs.unlink(path$1).catch(() => {
1864
1179
  });
1865
1180
  }));
1866
1181
  };
@@ -1868,12 +1183,12 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1868
1183
  let tempPath = null;
1869
1184
  try {
1870
1185
  const buffer = decodeUploadBase64(data.content, MAX_USER_UPLOAD_BYTES);
1871
- const dir = path$1.join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1186
+ const dir = path.join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1872
1187
  await fs.mkdir(dir, { recursive: true });
1873
1188
  const name = safeFileName(data.name);
1874
1189
  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`);
1190
+ const target = path.join(dir, uniqueFileName(name, token));
1191
+ tempPath = path.join(dir, `.${name}.legacy_${crypto.randomUUID().replace(/-/g, "")}.part`);
1877
1192
  await fs.writeFile(tempPath, buffer, { flag: "wx" });
1878
1193
  await fs.rename(tempPath, target);
1879
1194
  tempPath = null;
@@ -1894,12 +1209,12 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1894
1209
  }
1895
1210
  if (active.has(uploadId)) throw new Error("Upload id is already active");
1896
1211
  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));
1212
+ const dir = path.join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1898
1213
  await fs.mkdir(dir, { recursive: true });
1899
1214
  await sweepExpiredFiles(dir);
1900
1215
  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`);
1216
+ const targetPath = path.join(dir, uniqueFileName(name, crypto.randomUUID().replace(/-/g, "").slice(0, 12)));
1217
+ const tempPath = path.join(dir, `.${name}.${uploadId}.part`);
1903
1218
  await fs.writeFile(tempPath, Buffer.alloc(0), { flag: "wx" });
1904
1219
  active.set(uploadId, { tempPath, targetPath, totalSize: data.totalSize, received: 0, touchedAt: now() });
1905
1220
  return { success: true, size: 0 };
@@ -1945,7 +1260,7 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1945
1260
  const execAsync = util.promisify(child_process.exec);
1946
1261
  function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
1947
1262
  rpcHandlerManager.registerHandler("bash", async (data) => {
1948
- logger.debug("Shell command request:", data.command);
1263
+ persistence.logger.debug("Shell command request:", data.command);
1949
1264
  if (data.cwd && data.cwd !== "/") {
1950
1265
  const validation = validatePath(data.cwd, workingDirectory);
1951
1266
  if (!validation.valid) {
@@ -1961,16 +1276,16 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
1961
1276
  windowsHide: true
1962
1277
  // Prevent cmd.exe popup on Windows for every RPC bash call
1963
1278
  };
1964
- logger.debug("Shell command executing...", { cwd: options.cwd, timeout: options.timeout });
1279
+ persistence.logger.debug("Shell command executing...", { cwd: options.cwd, timeout: options.timeout });
1965
1280
  const { stdout, stderr } = await execAsync(data.command, options);
1966
- logger.debug("Shell command executed, processing result...");
1281
+ persistence.logger.debug("Shell command executed, processing result...");
1967
1282
  const result = {
1968
1283
  success: true,
1969
1284
  stdout: stdout ? stdout.toString() : "",
1970
1285
  stderr: stderr ? stderr.toString() : "",
1971
1286
  exitCode: 0
1972
1287
  };
1973
- logger.debug("Shell command result:", {
1288
+ persistence.logger.debug("Shell command result:", {
1974
1289
  success: true,
1975
1290
  exitCode: 0,
1976
1291
  stdoutLen: result.stdout.length,
@@ -1987,7 +1302,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
1987
1302
  exitCode: typeof execError.code === "number" ? execError.code : -1,
1988
1303
  error: "Command timed out"
1989
1304
  };
1990
- logger.debug("Shell command timed out:", {
1305
+ persistence.logger.debug("Shell command timed out:", {
1991
1306
  success: false,
1992
1307
  exitCode: result2.exitCode,
1993
1308
  error: "Command timed out"
@@ -2001,7 +1316,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2001
1316
  exitCode: typeof execError.code === "number" ? execError.code : 1,
2002
1317
  error: execError.message || "Command failed"
2003
1318
  };
2004
- logger.debug("Shell command failed:", {
1319
+ persistence.logger.debug("Shell command failed:", {
2005
1320
  success: false,
2006
1321
  exitCode: result.exitCode,
2007
1322
  error: result.error,
@@ -2012,7 +1327,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2012
1327
  }
2013
1328
  });
2014
1329
  rpcHandlerManager.registerHandler("readFile", async (data) => {
2015
- logger.debug("Read file request:", data.path);
1330
+ persistence.logger.debug("Read file request:", data.path);
2016
1331
  const validation = validatePath(data.path, workingDirectory);
2017
1332
  if (!validation.valid) {
2018
1333
  return { success: false, error: validation.error };
@@ -2022,12 +1337,12 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2022
1337
  const content = buffer.toString("base64");
2023
1338
  return { success: true, content };
2024
1339
  } catch (error) {
2025
- logger.debug("Failed to read file:", error);
1340
+ persistence.logger.debug("Failed to read file:", error);
2026
1341
  return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
2027
1342
  }
2028
1343
  });
2029
1344
  rpcHandlerManager.registerHandler("writeFile", async (data) => {
2030
- logger.debug("Write file request:", data.path);
1345
+ persistence.logger.debug("Write file request:", data.path);
2031
1346
  const validation = validatePath(data.path, workingDirectory);
2032
1347
  if (!validation.valid) {
2033
1348
  return { success: false, error: validation.error };
@@ -2072,15 +1387,15 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2072
1387
  const hash = crypto.createHash("sha256").update(buffer).digest("hex");
2073
1388
  return { success: true, hash };
2074
1389
  } catch (error) {
2075
- logger.debug("Failed to write file:", error);
1390
+ persistence.logger.debug("Failed to write file:", error);
2076
1391
  return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
2077
1392
  }
2078
1393
  });
2079
- const uploadHandlers = createUploadFileHandlers(configuration.happyHomeDir);
1394
+ const uploadHandlers = createUploadFileHandlers(persistence.configuration.happyHomeDir);
2080
1395
  rpcHandlerManager.registerHandler("uploadFile", uploadHandlers.uploadFile);
2081
1396
  rpcHandlerManager.registerHandler("uploadFileChunk", uploadHandlers.uploadFileChunk);
2082
1397
  rpcHandlerManager.registerHandler("listDirectory", async (data) => {
2083
- logger.debug("List directory request:", data.path);
1398
+ persistence.logger.debug("List directory request:", data.path);
2084
1399
  const validation = validatePath(data.path, workingDirectory);
2085
1400
  if (!validation.valid) {
2086
1401
  return { success: false, error: validation.error };
@@ -2090,7 +1405,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2090
1405
  const entries = await fs.readdir(directoryPath, { withFileTypes: true });
2091
1406
  const directoryEntries = await Promise.all(
2092
1407
  entries.map(async (entry) => {
2093
- const fullPath = path$1.join(directoryPath, entry.name);
1408
+ const fullPath = path.join(directoryPath, entry.name);
2094
1409
  let type = "other";
2095
1410
  let size;
2096
1411
  let modified;
@@ -2104,7 +1419,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2104
1419
  size = stats.size;
2105
1420
  modified = stats.mtime.getTime();
2106
1421
  } catch (error) {
2107
- logger.debug(`Failed to stat ${fullPath}:`, error);
1422
+ persistence.logger.debug(`Failed to stat ${fullPath}:`, error);
2108
1423
  }
2109
1424
  return {
2110
1425
  name: entry.name,
@@ -2121,36 +1436,36 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2121
1436
  });
2122
1437
  return { success: true, entries: directoryEntries };
2123
1438
  } catch (error) {
2124
- logger.debug("Failed to list directory:", error);
1439
+ persistence.logger.debug("Failed to list directory:", error);
2125
1440
  return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
2126
1441
  }
2127
1442
  });
2128
1443
  rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
2129
- logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
1444
+ persistence.logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
2130
1445
  const validation = validatePath(data.path, workingDirectory);
2131
1446
  if (!validation.valid) {
2132
1447
  return { success: false, error: validation.error };
2133
1448
  }
2134
- async function buildTree(path, name, currentDepth) {
1449
+ async function buildTree(path$1, name, currentDepth) {
2135
1450
  try {
2136
- const stats = await fs.stat(path);
1451
+ const stats = await fs.stat(path$1);
2137
1452
  const node = {
2138
1453
  name,
2139
- path,
1454
+ path: path$1,
2140
1455
  type: stats.isDirectory() ? "directory" : "file",
2141
1456
  size: stats.size,
2142
1457
  modified: stats.mtime.getTime()
2143
1458
  };
2144
1459
  if (stats.isDirectory() && currentDepth < data.maxDepth) {
2145
- const entries = await fs.readdir(path, { withFileTypes: true });
1460
+ const entries = await fs.readdir(path$1, { withFileTypes: true });
2146
1461
  const children = [];
2147
1462
  await Promise.all(
2148
1463
  entries.map(async (entry) => {
2149
1464
  if (entry.isSymbolicLink()) {
2150
- logger.debug(`Skipping symlink: ${path$1.join(path, entry.name)}`);
1465
+ persistence.logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
2151
1466
  return;
2152
1467
  }
2153
- const childPath = path$1.join(path, entry.name);
1468
+ const childPath = path.join(path$1, entry.name);
2154
1469
  const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
2155
1470
  if (childNode) {
2156
1471
  children.push(childNode);
@@ -2166,7 +1481,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2166
1481
  }
2167
1482
  return node;
2168
1483
  } catch (error) {
2169
- logger.debug(`Failed to process ${path}:`, error instanceof Error ? error.message : String(error));
1484
+ persistence.logger.debug(`Failed to process ${path$1}:`, error instanceof Error ? error.message : String(error));
2170
1485
  return null;
2171
1486
  }
2172
1487
  }
@@ -2182,12 +1497,12 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2182
1497
  }
2183
1498
  return { success: true, tree };
2184
1499
  } catch (error) {
2185
- logger.debug("Failed to get directory tree:", error);
1500
+ persistence.logger.debug("Failed to get directory tree:", error);
2186
1501
  return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
2187
1502
  }
2188
1503
  });
2189
1504
  rpcHandlerManager.registerHandler("ripgrep", async (data) => {
2190
- logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
1505
+ persistence.logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
2191
1506
  if (data.cwd) {
2192
1507
  const validation = validatePath(data.cwd, workingDirectory);
2193
1508
  if (!validation.valid) {
@@ -2204,7 +1519,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2204
1519
  stderr: result.stderr.toString()
2205
1520
  };
2206
1521
  } catch (error) {
2207
- logger.debug("Failed to run ripgrep:", error);
1522
+ persistence.logger.debug("Failed to run ripgrep:", error);
2208
1523
  return {
2209
1524
  success: false,
2210
1525
  error: error instanceof Error ? error.message : "Failed to run ripgrep"
@@ -2212,7 +1527,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2212
1527
  }
2213
1528
  });
2214
1529
  rpcHandlerManager.registerHandler("difftastic", async (data) => {
2215
- logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
1530
+ persistence.logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
2216
1531
  if (data.cwd) {
2217
1532
  const validation = validatePath(data.cwd, workingDirectory);
2218
1533
  if (!validation.valid) {
@@ -2229,7 +1544,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2229
1544
  stderr: result.stderr.toString()
2230
1545
  };
2231
1546
  } catch (error) {
2232
- logger.debug("Failed to run difftastic:", error);
1547
+ persistence.logger.debug("Failed to run difftastic:", error);
2233
1548
  return {
2234
1549
  success: false,
2235
1550
  error: error instanceof Error ? error.message : "Failed to run difftastic"
@@ -2312,7 +1627,7 @@ function calculateCost(usage, modelId) {
2312
1627
  }
2313
1628
 
2314
1629
  function hasNetworkConnectivity() {
2315
- const interfaces = os$1.networkInterfaces();
1630
+ const interfaces = os.networkInterfaces();
2316
1631
  for (const name of Object.keys(interfaces)) {
2317
1632
  for (const iface of interfaces[name] || []) {
2318
1633
  if (!iface.internal && iface.family === "IPv4") return true;
@@ -3326,13 +2641,13 @@ class ApiSessionClient extends node_events.EventEmitter {
3326
2641
  scopePrefix: this.sessionId,
3327
2642
  encryptionKey: this.encryptionKey,
3328
2643
  encryptionVariant: this.encryptionVariant,
3329
- logger: (msg, data) => logger.debug(msg, data)
2644
+ logger: (msg, data) => persistence.logger.debug(msg, data)
3330
2645
  });
3331
2646
  registerCommonHandlers(this.rpcHandlerManager, this.metadata.path);
3332
2647
  this.socket = this.createControlSocket();
3333
2648
  const controlSocket = this.socket;
3334
2649
  this.socket.on("connect", () => {
3335
- logger.debug("Socket connected successfully");
2650
+ persistence.logger.debug("Socket connected successfully");
3336
2651
  if (this.reconnectInterval) {
3337
2652
  clearInterval(this.reconnectInterval);
3338
2653
  this.reconnectInterval = null;
@@ -3345,15 +2660,15 @@ class ApiSessionClient extends node_events.EventEmitter {
3345
2660
  callback(await this.rpcHandlerManager.handleRequest(data));
3346
2661
  });
3347
2662
  this.socket.on("disconnect", (reason) => {
3348
- logger.debug(`[API] Socket disconnected: ${reason}`);
2663
+ persistence.logger.debug(`[API] Socket disconnected: ${reason}`);
3349
2664
  this.rpcHandlerManager.onSocketDisconnect(controlSocket);
3350
2665
  this.startSmartReconnect();
3351
2666
  });
3352
2667
  this.socket.on("connect_error", (error) => {
3353
- logger.debug("[API] Socket connection error:", error);
2668
+ persistence.logger.debug("[API] Socket connection error:", error);
3354
2669
  this.rpcHandlerManager.onSocketDisconnect(controlSocket);
3355
2670
  if (error.message === "Session archived") {
3356
- logger.debug("[API] Archived session rejected during reconnect; exiting");
2671
+ persistence.logger.debug("[API] Archived session rejected during reconnect; exiting");
3357
2672
  this.emit("archived");
3358
2673
  return;
3359
2674
  }
@@ -3361,14 +2676,14 @@ class ApiSessionClient extends node_events.EventEmitter {
3361
2676
  });
3362
2677
  this.socket.on("session-archive", ({ sessionId }) => {
3363
2678
  if (sessionId !== this.sessionId) return;
3364
- logger.debug("[SOCKET] Server archived this session; exiting...");
2679
+ persistence.logger.debug("[SOCKET] Server archived this session; exiting...");
3365
2680
  this.emit("archived");
3366
2681
  });
3367
2682
  this.socket.on("update", (data) => {
3368
2683
  try {
3369
- logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
2684
+ persistence.logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
3370
2685
  if (!data.body) {
3371
- logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
2686
+ persistence.logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
3372
2687
  return;
3373
2688
  }
3374
2689
  if (data.body.t === "new-message") {
@@ -3386,41 +2701,41 @@ class ApiSessionClient extends node_events.EventEmitter {
3386
2701
  this.lastSeq = messageSeq;
3387
2702
  return;
3388
2703
  }
3389
- const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
3390
- logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
2704
+ const body = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(data.body.message.content.c));
2705
+ persistence.logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
3391
2706
  this.routeIncomingMessage(body, localId);
3392
2707
  if (localId) this.rememberRoutedInbound(localId);
3393
2708
  this.lastSeq = messageSeq;
3394
2709
  } else if (data.body.t === "update-session") {
3395
2710
  if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
3396
- this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
2711
+ this.metadata = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(data.body.metadata.value));
3397
2712
  this.metadataVersion = data.body.metadata.version;
3398
2713
  const meta = this.metadata;
3399
2714
  if (meta?.lifecycleState === "archiveRequested" || meta?.lifecycleState === "archived") {
3400
2715
  if (this.ignoreArchiveSignal) {
3401
- logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}) but suppressed for reconnect`);
2716
+ persistence.logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}) but suppressed for reconnect`);
3402
2717
  this.ignoreArchiveSignal = false;
3403
2718
  } else {
3404
- logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}), exiting...`);
2719
+ persistence.logger.debug(`[SOCKET] Session archived (${meta.lifecycleState}), exiting...`);
3405
2720
  this.emit("archived");
3406
2721
  }
3407
2722
  }
3408
2723
  }
3409
2724
  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;
2725
+ this.agentState = data.body.agentState.value ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(data.body.agentState.value)) : null;
3411
2726
  this.agentStateVersion = data.body.agentState.version;
3412
2727
  }
3413
2728
  } else if (data.body.t === "update-machine") {
3414
- logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
2729
+ persistence.logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
3415
2730
  } else {
3416
2731
  this.emit("message", data.body);
3417
2732
  }
3418
2733
  } catch (error) {
3419
- logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
2734
+ persistence.logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
3420
2735
  }
3421
2736
  });
3422
2737
  this.socket.on("error", (error) => {
3423
- logger.debug("[API] Socket error:", error);
2738
+ persistence.logger.debug("[API] Socket error:", error);
3424
2739
  });
3425
2740
  this.socket.on("server-draining", (data) => {
3426
2741
  const parsed = ReleaseDrainNoticeSchema.safeParse(data);
@@ -3430,12 +2745,12 @@ class ApiSessionClient extends node_events.EventEmitter {
3430
2745
  this.socket.connect();
3431
2746
  }
3432
2747
  createControlSocket(handover) {
3433
- return socket_ioClient.io(configuration.serverUrl, {
2748
+ return socket_ioClient.io(persistence.configuration.serverUrl, {
3434
2749
  auth: {
3435
2750
  token: this.token,
3436
2751
  clientType: "session-scoped",
3437
2752
  sessionId: this.sessionId,
3438
- happyClient: `cli-coding-session/${configuration.currentCliVersion}`,
2753
+ happyClient: `cli-coding-session/${persistence.configuration.currentCliVersion}`,
3439
2754
  ...handover ? { handoverEpoch: handover.epoch } : {}
3440
2755
  },
3441
2756
  ...handover ? { query: { vh_slot: handover.candidateSlot }, forceNew: true } : {},
@@ -3516,7 +2831,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3516
2831
  this.rpcHandlerManager.onSocketDisconnect(candidate);
3517
2832
  candidate.close();
3518
2833
  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}`);
2834
+ persistence.logger.debug(`[API] Release handover failed: ${error instanceof Error ? error.message : error}`);
3520
2835
  }
3521
2836
  }
3522
2837
  onUserMessage(callback) {
@@ -3550,7 +2865,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3550
2865
  * presigned URL that does not accept extra headers.
3551
2866
  */
3552
2867
  async downloadAttachment(ref) {
3553
- const requestUrl = `${configuration.serverUrl}/v1/sessions/${this.sessionId}/attachments/request-download`;
2868
+ const requestUrl = `${persistence.configuration.serverUrl}/v1/sessions/${this.sessionId}/attachments/request-download`;
3554
2869
  const requestRes = await axios.post(
3555
2870
  requestUrl,
3556
2871
  { ref },
@@ -3563,7 +2878,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3563
2878
  if (typeof downloadUrl !== "string") {
3564
2879
  throw new Error("request-download returned no downloadUrl");
3565
2880
  }
3566
- const isServerUrl = downloadUrl.startsWith(configuration.serverUrl);
2881
+ const isServerUrl = downloadUrl.startsWith(persistence.configuration.serverUrl);
3567
2882
  const headers = {};
3568
2883
  if (isServerUrl) {
3569
2884
  headers["Authorization"] = `Bearer ${this.token}`;
@@ -3585,7 +2900,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3585
2900
  async downloadAndDecryptAttachment(ref) {
3586
2901
  const encrypted = await this.downloadAttachment(ref);
3587
2902
  const key = await this.getBlobKey();
3588
- const decrypted = decryptBlob(encrypted, key);
2903
+ const decrypted = persistence.decryptBlob(encrypted, key);
3589
2904
  return decrypted;
3590
2905
  }
3591
2906
  /**
@@ -3614,7 +2929,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3614
2929
  return {
3615
2930
  "Authorization": `Bearer ${this.token}`,
3616
2931
  "Content-Type": "application/json",
3617
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
2932
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
3618
2933
  };
3619
2934
  }
3620
2935
  routeIncomingMessage(message, sourceLocalId) {
@@ -3630,7 +2945,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3630
2945
  }
3631
2946
  const fileResult = FileEventMessageSchema.safeParse(message);
3632
2947
  if (fileResult.success) {
3633
- logger.debug(`[API] Received file event: ${fileResult.data.content.data.ev.name} (ref: ${fileResult.data.content.data.ev.ref})`);
2948
+ persistence.logger.debug(`[API] Received file event: ${fileResult.data.content.data.ev.name} (ref: ${fileResult.data.content.data.ev.ref})`);
3634
2949
  if (this.pendingFileEventCallback) {
3635
2950
  this.pendingFileEventCallback(fileResult.data);
3636
2951
  } else {
@@ -3644,12 +2959,12 @@ class ApiSessionClient extends node_events.EventEmitter {
3644
2959
  const skipRouting = this.skipInitialMessages;
3645
2960
  if (skipRouting) {
3646
2961
  this.skipInitialMessages = false;
3647
- logger.debug("[API] Reconnect mode: skipping existing messages, advancing lastSeq");
2962
+ persistence.logger.debug("[API] Reconnect mode: skipping existing messages, advancing lastSeq");
3648
2963
  }
3649
2964
  let afterSeq = this.lastSeq;
3650
2965
  while (true) {
3651
2966
  const response = await axios.get(
3652
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
2967
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3653
2968
  {
3654
2969
  params: {
3655
2970
  after_seq: afterSeq,
@@ -3676,11 +2991,11 @@ class ApiSessionClient extends node_events.EventEmitter {
3676
2991
  continue;
3677
2992
  }
3678
2993
  try {
3679
- const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(message.content.c));
2994
+ const body = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(message.content.c));
3680
2995
  this.routeIncomingMessage(body, message.localId);
3681
2996
  if (message.localId) this.rememberRoutedInbound(message.localId);
3682
2997
  } catch (error) {
3683
- logger.debug("[API] Failed to decrypt fetched message", {
2998
+ persistence.logger.debug("[API] Failed to decrypt fetched message", {
3684
2999
  sessionId: this.sessionId,
3685
3000
  seq: message.seq,
3686
3001
  error
@@ -3690,7 +3005,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3690
3005
  this.lastSeq = Math.max(this.lastSeq, maxSeq);
3691
3006
  const hasMore = !!response.data.hasMore;
3692
3007
  if (hasMore && maxSeq === afterSeq) {
3693
- logger.debug("[API] fetchMessages pagination stalled, stopping to avoid infinite loop", {
3008
+ persistence.logger.debug("[API] fetchMessages pagination stalled, stopping to avoid infinite loop", {
3694
3009
  sessionId: this.sessionId,
3695
3010
  afterSeq
3696
3011
  });
@@ -3709,7 +3024,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3709
3024
  const batchSize = Math.min(this.pendingOutbox.length, ApiSessionClient.MAX_OUTBOX_BATCH_SIZE);
3710
3025
  const batch = this.pendingOutbox.slice(0, batchSize);
3711
3026
  const response = await axios.post(
3712
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3027
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3713
3028
  {
3714
3029
  messages: batch
3715
3030
  },
@@ -3744,7 +3059,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3744
3059
  if (!machineId || this.relaySocket?.connected) return;
3745
3060
  try {
3746
3061
  const response = await fetch(
3747
- `${configuration.serverUrl}/v1/relays/sessions/${encodeURIComponent(this.sessionId)}/machines/${encodeURIComponent(machineId)}`,
3062
+ `${persistence.configuration.serverUrl}/v1/relays/sessions/${encodeURIComponent(this.sessionId)}/machines/${encodeURIComponent(machineId)}`,
3748
3063
  { headers: this.authHeaders(), cache: "no-store" }
3749
3064
  );
3750
3065
  if (!response.ok) return;
@@ -3763,7 +3078,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3763
3078
  relaySocket.on("connect", () => {
3764
3079
  if (this.relaySocket !== relaySocket) return;
3765
3080
  this.rpcHandlerManager.onSocketConnect(relaySocket);
3766
- logger.debug(`[API] Session connected to regional relay ${assignment.relayId}`);
3081
+ persistence.logger.debug(`[API] Session connected to regional relay ${assignment.relayId}`);
3767
3082
  });
3768
3083
  relaySocket.on("rpc-request", async (data, callback) => {
3769
3084
  callback(await this.rpcHandlerManager.handleRequest(data));
@@ -3785,7 +3100,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3785
3100
  for (const item of batch) this.rememberDirectInbound(item.localId);
3786
3101
  try {
3787
3102
  const persisted = await axios.post(
3788
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3103
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3789
3104
  { messages: batch },
3790
3105
  { headers: this.authHeaders(), timeout: 6e4 }
3791
3106
  );
@@ -3797,7 +3112,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3797
3112
  this.lastSeq = stored.reduce((max, message) => Math.max(max, message.seq), this.lastSeq);
3798
3113
  for (const item of batch) {
3799
3114
  if (this.routedInboundLocalIds.has(item.localId)) continue;
3800
- const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(item.content));
3115
+ const body = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(item.content));
3801
3116
  this.routeIncomingMessage(body, item.localId);
3802
3117
  this.rememberRoutedInbound(item.localId);
3803
3118
  }
@@ -3811,7 +3126,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3811
3126
  if (this.relaySocket === relaySocket) this.rpcHandlerManager.onSocketDisconnect(relaySocket);
3812
3127
  });
3813
3128
  } catch (error) {
3814
- logger.debug(`[API] Session regional relay unavailable: ${error instanceof Error ? error.message : error}`);
3129
+ persistence.logger.debug(`[API] Session regional relay unavailable: ${error instanceof Error ? error.message : error}`);
3815
3130
  }
3816
3131
  }
3817
3132
  pushCommittedMessagesToRelay(batch, messages) {
@@ -3831,7 +3146,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3831
3146
  }
3832
3147
  }
3833
3148
  enqueueMessage(content, invalidate = true, localId) {
3834
- const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
3149
+ const encrypted = persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, content));
3835
3150
  this.pendingOutbox.push({
3836
3151
  content: encrypted,
3837
3152
  // Deterministic localIds (terminal mirror, B-105) ride the server's
@@ -3873,7 +3188,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3873
3188
  while (!reachedBoundary && pages < MAX_SCAN_PAGES) {
3874
3189
  pages += 1;
3875
3190
  const response = await axios.get(
3876
- `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3191
+ `${persistence.configuration.serverUrl}/v3/sessions/${encodeURIComponent(this.sessionId)}/messages`,
3877
3192
  {
3878
3193
  params: { before_seq: beforeSeq, limit: 100 },
3879
3194
  headers: this.authHeaders(),
@@ -3885,9 +3200,9 @@ class ApiSessionClient extends node_events.EventEmitter {
3885
3200
  const records = messages.map((message) => {
3886
3201
  let body = null;
3887
3202
  try {
3888
- body = message.content?.t === "encrypted" ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(message.content.c)) : null;
3203
+ body = message.content?.t === "encrypted" ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(message.content.c)) : null;
3889
3204
  } catch (error) {
3890
- logger.debug("[API] Failed to decrypt during undelivered scan:", error);
3205
+ persistence.logger.debug("[API] Failed to decrypt during undelivered scan:", error);
3891
3206
  }
3892
3207
  return { seq: message.seq, localId: message.localId, body };
3893
3208
  });
@@ -3900,11 +3215,11 @@ class ApiSessionClient extends node_events.EventEmitter {
3900
3215
  beforeSeq = oldest;
3901
3216
  }
3902
3217
  } catch (error) {
3903
- logger.debug("[API] cancelUndeliveredQueuedInputs scan failed:", error);
3218
+ persistence.logger.debug("[API] cancelUndeliveredQueuedInputs scan failed:", error);
3904
3219
  return;
3905
3220
  }
3906
3221
  if (!reachedBoundary) {
3907
- logger.debug("[API] cancelUndeliveredQueuedInputs found no turn boundary; leaving messages as-is");
3222
+ persistence.logger.debug("[API] cancelUndeliveredQueuedInputs found no turn boundary; leaving messages as-is");
3908
3223
  return;
3909
3224
  }
3910
3225
  undelivered.reverse();
@@ -3926,7 +3241,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3926
3241
  const eventId = typeof body.uuid === "string" ? body.uuid : typeof body.message.id === "string" ? body.message.id : void 0;
3927
3242
  this.sendUsageData(body.message.usage, body.message.model, eventId);
3928
3243
  } catch (error) {
3929
- logger.debug("[SOCKET] Failed to send usage data:", error);
3244
+ persistence.logger.debug("[SOCKET] Failed to send usage data:", error);
3930
3245
  }
3931
3246
  }
3932
3247
  if (body.type === "summary" && "summary" in body && "leafUuid" in body) {
@@ -3993,7 +3308,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3993
3308
  sentFrom: "cli"
3994
3309
  }
3995
3310
  };
3996
- logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
3311
+ persistence.logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
3997
3312
  this.enqueueMessage(content);
3998
3313
  }
3999
3314
  sendSessionEvent(event, id) {
@@ -4012,7 +3327,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4012
3327
  */
4013
3328
  keepAlive(thinking, mode) {
4014
3329
  if (process.env.DEBUG) {
4015
- logger.debug(`[API] Sending keep alive message: ${thinking}`);
3330
+ persistence.logger.debug(`[API] Sending keep alive message: ${thinking}`);
4016
3331
  }
4017
3332
  this.socket.volatile.emit("session-alive", {
4018
3333
  sid: this.sessionId,
@@ -4041,11 +3356,11 @@ class ApiSessionClient extends node_events.EventEmitter {
4041
3356
  // The frame object, NOT a JSON string: `encrypt` serializes
4042
3357
  // internally, so pre-stringifying would double-encode and
4043
3358
  // escape every quote in the streamed text.
4044
- payload: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, frame)),
3359
+ payload: persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, frame)),
4045
3360
  enc: true
4046
3361
  });
4047
3362
  } catch (error) {
4048
- logger.debug(`[API] Failed to relay stream frame: ${error}`);
3363
+ persistence.logger.debug(`[API] Failed to relay stream frame: ${error}`);
4049
3364
  }
4050
3365
  }
4051
3366
  /**
@@ -4062,12 +3377,12 @@ class ApiSessionClient extends node_events.EventEmitter {
4062
3377
  * Returns delivery info for the MCP tool to report back to the model.
4063
3378
  */
4064
3379
  pushClipboard(text) {
4065
- const prepared = limits.prepareClipboardText(text);
3380
+ const prepared = persistence.prepareClipboardText(text);
4066
3381
  if (!this.socket.connected) {
4067
3382
  return { delivered: false, truncated: prepared.truncated, totalBytes: prepared.totalBytes };
4068
3383
  }
4069
3384
  this.socket.emit("clipboard-push", {
4070
- payload: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, prepared.text)),
3385
+ payload: persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, prepared.text)),
4071
3386
  enc: true,
4072
3387
  truncated: prepared.truncated,
4073
3388
  totalBytes: prepared.totalBytes
@@ -4093,7 +3408,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4093
3408
  return { delivered: false, error: "not connected to the server" };
4094
3409
  }
4095
3410
  this.socket.emit("file-preview-push", {
4096
- payload: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, path)),
3411
+ payload: persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, path)),
4097
3412
  enc: true,
4098
3413
  mode
4099
3414
  });
@@ -4133,7 +3448,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4133
3448
  output: this.claudeUsageTotals.costOutput
4134
3449
  }
4135
3450
  };
4136
- logger.debugLargeJson("[SOCKET] Sending usage data:", usageReport);
3451
+ persistence.logger.debugLargeJson("[SOCKET] Sending usage data:", usageReport);
4137
3452
  this.socket.emit("usage-report", usageReport);
4138
3453
  }
4139
3454
  /** Save a provider's cumulative session snapshot under one upsert key. */
@@ -4148,7 +3463,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4148
3463
  // The Web treats this sentinel as unknown, not as real $0.00.
4149
3464
  cost: { total: 0 }
4150
3465
  };
4151
- logger.debugLargeJson("[SOCKET] Sending agent usage snapshot:", usageReport);
3466
+ persistence.logger.debugLargeJson("[SOCKET] Sending agent usage snapshot:", usageReport);
4152
3467
  this.socket.emit("usage-report", usageReport);
4153
3468
  return true;
4154
3469
  }
@@ -4181,15 +3496,15 @@ class ApiSessionClient extends node_events.EventEmitter {
4181
3496
  let rateRefusals = 0;
4182
3497
  await backoff(async () => {
4183
3498
  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)) });
3499
+ 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
3500
  if (answer.result === "success") {
4186
- this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
3501
+ this.metadata = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.metadata));
4187
3502
  this.metadataVersion = answer.version;
4188
3503
  rateRefusals = 0;
4189
3504
  } else if (answer.result === "version-mismatch") {
4190
3505
  if (answer.version > this.metadataVersion) {
4191
3506
  this.metadataVersion = answer.version;
4192
- this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
3507
+ this.metadata = persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.metadata));
4193
3508
  }
4194
3509
  throw new Error("Metadata version mismatch");
4195
3510
  } else if (answer.result === "error") {
@@ -4200,7 +3515,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4200
3515
  code: answer.error,
4201
3516
  attempt: rateRefusals,
4202
3517
  sleep: delay,
4203
- log: (message) => logger.debug(message)
3518
+ log: (message) => persistence.logger.debug(message)
4204
3519
  });
4205
3520
  }
4206
3521
  throw new Error("Metadata update failed");
@@ -4213,21 +3528,21 @@ class ApiSessionClient extends node_events.EventEmitter {
4213
3528
  * @param handler - Handler function that returns the updated agent state
4214
3529
  */
4215
3530
  updateAgentState(handler) {
4216
- logger.debugLargeJson("Updating agent state", this.agentState);
3531
+ persistence.logger.debugLargeJson("Updating agent state", this.agentState);
4217
3532
  this.agentStateLock.inLock(async () => {
4218
3533
  let rateRefusals = 0;
4219
3534
  await backoff(async () => {
4220
3535
  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 });
3536
+ 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
3537
  if (answer.result === "success") {
4223
- this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
3538
+ this.agentState = answer.agentState ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.agentState)) : null;
4224
3539
  this.agentStateVersion = answer.version;
4225
3540
  rateRefusals = 0;
4226
- logger.debug("Agent state updated", this.agentState);
3541
+ persistence.logger.debug("Agent state updated", this.agentState);
4227
3542
  } else if (answer.result === "version-mismatch") {
4228
3543
  if (answer.version > this.agentStateVersion) {
4229
3544
  this.agentStateVersion = answer.version;
4230
- this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
3545
+ this.agentState = answer.agentState ? persistence.decrypt(this.encryptionKey, this.encryptionVariant, persistence.decodeBase64(answer.agentState)) : null;
4231
3546
  }
4232
3547
  throw new Error("Agent state version mismatch");
4233
3548
  } else if (answer.result === "error") {
@@ -4238,7 +3553,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4238
3553
  code: answer.error,
4239
3554
  attempt: rateRefusals,
4240
3555
  sleep: delay,
4241
- log: (message) => logger.debug(message)
3556
+ log: (message) => persistence.logger.debug(message)
4242
3557
  });
4243
3558
  }
4244
3559
  throw new Error("Agent state update failed");
@@ -4267,7 +3582,7 @@ class ApiSessionClient extends node_events.EventEmitter {
4267
3582
  });
4268
3583
  }
4269
3584
  async close() {
4270
- logger.debug("[API] socket.close() called");
3585
+ persistence.logger.debug("[API] socket.close() called");
4271
3586
  this.sendSync.stop();
4272
3587
  this.receiveSync.stop();
4273
3588
  if (this.reconnectInterval) {
@@ -4287,14 +3602,14 @@ class ApiSessionClient extends node_events.EventEmitter {
4287
3602
  return;
4288
3603
  }
4289
3604
  if (!shouldReconnect()) {
4290
- logger.debug("[API] Still not ready to reconnect");
3605
+ persistence.logger.debug("[API] Still not ready to reconnect");
4291
3606
  return;
4292
3607
  }
4293
- logger.debug("[API] Attempting reconnect");
3608
+ persistence.logger.debug("[API] Attempting reconnect");
4294
3609
  this.socket.connect();
4295
3610
  }, 3e3);
4296
3611
  if (shouldReconnect()) {
4297
- logger.debug("[API] Network up + lid open \u2014 reconnecting in 1s");
3612
+ persistence.logger.debug("[API] Network up + lid open \u2014 reconnecting in 1s");
4298
3613
  setTimeout(() => {
4299
3614
  if (!this.socket.connected) this.socket.connect();
4300
3615
  }, 1e3);
@@ -4445,12 +3760,12 @@ function normalizeFsPath(input, homeDir) {
4445
3760
  if (p === "~") {
4446
3761
  p = homeDir;
4447
3762
  } else if (p.startsWith("~/")) {
4448
- p = path.join(homeDir, p.slice(2));
3763
+ p = path$1.join(homeDir, p.slice(2));
4449
3764
  }
4450
- if (!path.isAbsolute(p)) {
4451
- p = path.join(homeDir, p);
3765
+ if (!path$1.isAbsolute(p)) {
3766
+ p = path$1.join(homeDir, p);
4452
3767
  }
4453
- return path.resolve(p);
3768
+ return path$1.resolve(p);
4454
3769
  }
4455
3770
  function isBinaryContent(bytes) {
4456
3771
  const n = Math.min(bytes.length, BINARY_SNIFF_BYTES);
@@ -4490,13 +3805,13 @@ function fsError(error) {
4490
3805
  if (error instanceof Error) return error;
4491
3806
  return new Error("fs-failed");
4492
3807
  }
4493
- async function fsList(inputPath, homeDir = os.homedir()) {
4494
- const path$1 = normalizeFsPath(inputPath, homeDir);
3808
+ async function fsList(inputPath, homeDir = os$1.homedir()) {
3809
+ const path = normalizeFsPath(inputPath, homeDir);
4495
3810
  let dirents;
4496
3811
  try {
4497
- const st = await promises.stat(path$1);
3812
+ const st = await promises.stat(path);
4498
3813
  if (!st.isDirectory()) throw new Error("not-a-directory");
4499
- dirents = await promises.readdir(path$1, { withFileTypes: true });
3814
+ dirents = await promises.readdir(path, { withFileTypes: true });
4500
3815
  } catch (error) {
4501
3816
  throw fsError(error);
4502
3817
  }
@@ -4504,7 +3819,7 @@ async function fsList(inputPath, homeDir = os.homedir()) {
4504
3819
  dirents.map(async (d) => {
4505
3820
  const entry = { name: d.name, type: entryTypeOf(d) };
4506
3821
  try {
4507
- const s = await promises.lstat(path.join(path$1, d.name));
3822
+ const s = await promises.lstat(path$1.join(path, d.name));
4508
3823
  entry.size = s.size;
4509
3824
  entry.mtimeMs = Math.round(s.mtimeMs);
4510
3825
  } catch {
@@ -4515,12 +3830,12 @@ async function fsList(inputPath, homeDir = os.homedir()) {
4515
3830
  entries.sort(compareFsEntries);
4516
3831
  const truncated = entries.length > FS_LIST_MAX_ENTRIES;
4517
3832
  return {
4518
- path: path$1,
3833
+ path,
4519
3834
  entries: truncated ? entries.slice(0, FS_LIST_MAX_ENTRIES) : entries,
4520
3835
  truncated
4521
3836
  };
4522
3837
  }
4523
- async function fsRead(inputPath, options = {}, homeDir = os.homedir()) {
3838
+ async function fsRead(inputPath, options = {}, homeDir = os$1.homedir()) {
4524
3839
  const path = normalizeFsPath(inputPath, homeDir);
4525
3840
  const limit = clampReadLimit(options.maxBytes);
4526
3841
  const offset = clampReadOffset(options.offset);
@@ -4552,400 +3867,15 @@ async function fsRead(inputPath, options = {}, homeDir = os.homedir()) {
4552
3867
  }
4553
3868
  function registerFsHandlers(rpcHandlerManager) {
4554
3869
  rpcHandlerManager.registerHandler("fs-list", async (params) => {
4555
- logger.debug("[FS RPC] fs-list", params?.path);
3870
+ persistence.logger.debug("[FS RPC] fs-list", params?.path);
4556
3871
  return fsList(params?.path);
4557
3872
  });
4558
3873
  rpcHandlerManager.registerHandler("fs-read", async (params) => {
4559
- logger.debug("[FS RPC] fs-read", params?.path);
3874
+ persistence.logger.debug("[FS RPC] fs-read", params?.path);
4560
3875
  return fsRead(params?.path, { maxBytes: params?.maxBytes, allowBinary: params?.allowBinary === true, offset: params?.offset });
4561
3876
  });
4562
3877
  }
4563
3878
 
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
3879
  const TODO_LIST_MAX_ITEMS = 500;
4950
3880
  const TODO_TITLE_MAX_CHARS = 500;
4951
3881
  const FIELD_MAX_CHARS = 200;
@@ -5011,7 +3941,7 @@ function buildProviderArgv(config, verb, operand) {
5011
3941
  const DEFAULT_TIMEOUT_MS = 2e4;
5012
3942
  const MAX_STDOUT_BYTES = 2 * 1024 * 1024;
5013
3943
  async function readProviderConfig() {
5014
- const settings = await readSettings();
3944
+ const settings = await persistence.readSettings();
5015
3945
  const raw = settings?.todoProvider;
5016
3946
  if (!raw || typeof raw !== "object") return null;
5017
3947
  const cfg = raw;
@@ -5090,7 +4020,7 @@ function registerTodoHandlers(manager) {
5090
4020
  if ("error" in parsed) {
5091
4021
  throw new Error(`bad-output: ${parsed.error}`);
5092
4022
  }
5093
- logger.debug(`[todo] list \u2192 ${parsed.items.length} items (dropped ${parsed.dropped})`);
4023
+ persistence.logger.debug(`[todo] list \u2192 ${parsed.items.length} items (dropped ${parsed.dropped})`);
5094
4024
  return { ...parsed, machineConfigured: true };
5095
4025
  });
5096
4026
  manager.registerHandler("todo-complete", async (params) => {
@@ -5304,14 +4234,14 @@ function scrubTmuxClientEnv(env) {
5304
4234
  }
5305
4235
 
5306
4236
  function getClaudeConfigDir() {
5307
- return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
4237
+ return process.env.CLAUDE_CONFIG_DIR || path$1.join(os$1.homedir(), ".claude");
5308
4238
  }
5309
4239
  function getClaudeProjectsRoot() {
5310
- return path.join(getClaudeConfigDir(), "projects");
4240
+ return path$1.join(getClaudeConfigDir(), "projects");
5311
4241
  }
5312
4242
  function getProjectPath(workingDirectory) {
5313
- const projectId = path.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
5314
- return path.join(getClaudeProjectsRoot(), projectId);
4243
+ const projectId = path$1.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
4244
+ return path$1.join(getClaudeProjectsRoot(), projectId);
5315
4245
  }
5316
4246
 
5317
4247
  var xtermHeadless = {};
@@ -9656,18 +8586,18 @@ class ControlClient {
9656
8586
  });
9657
8587
  this.child.stdout.on("data", (chunk) => this.consume(this.decoder.push(chunk)));
9658
8588
  this.child.stderr.on("data", (chunk) => {
9659
- logger.debug(`[CONTROL ${tmuxSession}] stderr: ${chunk.toString("utf8").trim()}`);
8589
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] stderr: ${chunk.toString("utf8").trim()}`);
9660
8590
  });
9661
8591
  this.child.stdin.on("error", (e) => {
9662
- logger.debug(`[CONTROL ${tmuxSession}] stdin error (session gone?): ${e}`);
8592
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] stdin error (session gone?): ${e}`);
9663
8593
  });
9664
8594
  this.child.stdout.on("error", (e) => {
9665
- logger.debug(`[CONTROL ${tmuxSession}] stdout error: ${e}`);
8595
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] stdout error: ${e}`);
9666
8596
  });
9667
8597
  this.child.stderr.on("error", () => {
9668
8598
  });
9669
8599
  this.child.on("error", (e) => {
9670
- logger.debug(`[CONTROL ${tmuxSession}] spawn error: ${e}`);
8600
+ persistence.logger.debug(`[CONTROL ${tmuxSession}] spawn error: ${e}`);
9671
8601
  this.failAll(new Error(`control client failed: ${e}`));
9672
8602
  });
9673
8603
  this.child.on("exit", (code, signal) => {
@@ -9704,7 +8634,7 @@ class ControlClient {
9704
8634
  if (!ev.solicited) continue;
9705
8635
  const pending = this.queue.shift();
9706
8636
  if (!pending) {
9707
- logger.debug(`[CONTROL ${this.tmuxSession}] unmatched block cmd=${ev.cmdNum}`);
8637
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] unmatched block cmd=${ev.cmdNum}`);
9708
8638
  continue;
9709
8639
  }
9710
8640
  clearTimeout(pending.timer);
@@ -9713,7 +8643,7 @@ class ControlClient {
9713
8643
  try {
9714
8644
  pending.onBlock?.(ev.body);
9715
8645
  } catch (e) {
9716
- logger.debug(`[CONTROL ${this.tmuxSession}] onBlock threw: ${e}`);
8646
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] onBlock threw: ${e}`);
9717
8647
  }
9718
8648
  pending.resolve(ev.body);
9719
8649
  }
@@ -9723,7 +8653,7 @@ class ControlClient {
9723
8653
  this.handlers.onNotification(ev.name, ev.args);
9724
8654
  continue;
9725
8655
  }
9726
- logger.debug(`[CONTROL ${this.tmuxSession}] protocol error ${ev.reason}: ${ev.detail}`);
8656
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] protocol error ${ev.reason}: ${ev.detail}`);
9727
8657
  }
9728
8658
  }
9729
8659
  failAll(err) {
@@ -9784,7 +8714,7 @@ class ControlClient {
9784
8714
  try {
9785
8715
  this.child.stdin.write(text);
9786
8716
  } catch (e) {
9787
- logger.debug(`[CONTROL ${this.tmuxSession}] write failed (session gone?): ${e}`);
8717
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] write failed (session gone?): ${e}`);
9788
8718
  }
9789
8719
  }
9790
8720
  /** SIGTERM → grace → SIGKILL (rule 3). Resolves once the child is gone. */
@@ -9794,7 +8724,7 @@ class ControlClient {
9794
8724
  this.stopping = true;
9795
8725
  return new Promise((resolve) => {
9796
8726
  const killer = setTimeout(() => {
9797
- logger.debug(`[CONTROL ${this.tmuxSession}] SIGTERM did not land in ${graceMs}ms \u2014 SIGKILL`);
8727
+ persistence.logger.debug(`[CONTROL ${this.tmuxSession}] SIGTERM did not land in ${graceMs}ms \u2014 SIGKILL`);
9798
8728
  try {
9799
8729
  this.child.kill("SIGKILL");
9800
8730
  } catch {
@@ -10389,14 +9319,14 @@ function sendTerminalNotification(opts) {
10389
9319
  headers: {
10390
9320
  "Authorization": `Bearer ${opts.token}`,
10391
9321
  "Content-Type": "application/json",
10392
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
9322
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
10393
9323
  },
10394
9324
  timeout: 15e3
10395
9325
  }
10396
9326
  ).then(() => {
10397
- logger.debug(`[WEB TERMINAL] notify sent (event=${opts.event}, link=${opts.link})`);
9327
+ persistence.logger.debug(`[WEB TERMINAL] notify sent (event=${opts.event}, link=${opts.link})`);
10398
9328
  }).catch((error) => {
10399
- logger.debug("[WEB TERMINAL] notify failed:", error);
9329
+ persistence.logger.debug("[WEB TERMINAL] notify failed:", error);
10400
9330
  });
10401
9331
  }
10402
9332
 
@@ -10543,7 +9473,7 @@ function pruneTombstones(map, now, ttlMs = TOMBSTONE_TTL_MS) {
10543
9473
  return out;
10544
9474
  }
10545
9475
  function tombstoneFile() {
10546
- return path.join(configuration.happyHomeDir, "terminal-tombstones.json");
9476
+ return path$1.join(persistence.configuration.happyHomeDir, "terminal-tombstones.json");
10547
9477
  }
10548
9478
  function loadTombstones() {
10549
9479
  try {
@@ -10555,14 +9485,14 @@ function loadTombstones() {
10555
9485
  }
10556
9486
  function saveTombstones(map) {
10557
9487
  try {
10558
- node_fs.mkdirSync(configuration.happyHomeDir, { recursive: true });
9488
+ node_fs.mkdirSync(persistence.configuration.happyHomeDir, { recursive: true });
10559
9489
  node_fs.writeFileSync(tombstoneFile(), JSON.stringify(pruneTombstones(map, Date.now())));
10560
9490
  } catch (e) {
10561
- logger.debug(`[WEB TERMINAL] tombstone save failed: ${e}`);
9491
+ persistence.logger.debug(`[WEB TERMINAL] tombstone save failed: ${e}`);
10562
9492
  }
10563
9493
  }
10564
9494
  function closedTerminalsFile() {
10565
- return path.join(configuration.happyHomeDir, "closed-terminals.json");
9495
+ return path$1.join(persistence.configuration.happyHomeDir, "closed-terminals.json");
10566
9496
  }
10567
9497
  function loadClosedTerminals() {
10568
9498
  try {
@@ -10573,14 +9503,14 @@ function loadClosedTerminals() {
10573
9503
  }
10574
9504
  function saveClosedTerminals(list) {
10575
9505
  try {
10576
- node_fs.mkdirSync(configuration.happyHomeDir, { recursive: true });
9506
+ node_fs.mkdirSync(persistence.configuration.happyHomeDir, { recursive: true });
10577
9507
  node_fs.writeFileSync(closedTerminalsFile(), JSON.stringify(list));
10578
9508
  } catch (e) {
10579
- logger.debug(`[WEB TERMINAL] closed-terminals save failed: ${e}`);
9509
+ persistence.logger.debug(`[WEB TERMINAL] closed-terminals save failed: ${e}`);
10580
9510
  }
10581
9511
  }
10582
9512
  function liveTerminalsFile() {
10583
- return path.join(configuration.happyHomeDir, "live-terminals.json");
9513
+ return path$1.join(persistence.configuration.happyHomeDir, "live-terminals.json");
10584
9514
  }
10585
9515
  function loadLiveSnapshot() {
10586
9516
  try {
@@ -10591,10 +9521,10 @@ function loadLiveSnapshot() {
10591
9521
  }
10592
9522
  function saveLiveSnapshot(map) {
10593
9523
  try {
10594
- node_fs.mkdirSync(configuration.happyHomeDir, { recursive: true });
9524
+ node_fs.mkdirSync(persistence.configuration.happyHomeDir, { recursive: true });
10595
9525
  node_fs.writeFileSync(liveTerminalsFile(), JSON.stringify(serializeLiveSnapshot(map)));
10596
9526
  } catch (e) {
10597
- logger.debug(`[WEB TERMINAL] live-terminals save failed: ${e}`);
9527
+ persistence.logger.debug(`[WEB TERMINAL] live-terminals save failed: ${e}`);
10598
9528
  }
10599
9529
  }
10600
9530
  const TERMINAL_OPEN_TIMEOUT = "terminal-open-timeout";
@@ -10855,7 +9785,7 @@ function tmuxSupportsEnvFlag() {
10855
9785
  }
10856
9786
  const PASTE_FILE_TTL_MS = 5e3;
10857
9787
  function pasteSpoolDir() {
10858
- const dir = path.join(configuration.happyHomeDir, "paste-spool");
9788
+ const dir = path$1.join(persistence.configuration.happyHomeDir, "paste-spool");
10859
9789
  try {
10860
9790
  node_fs.mkdirSync(dir, { recursive: true, mode: 448 });
10861
9791
  } catch {
@@ -10906,7 +9836,7 @@ function ptyEnv() {
10906
9836
  if (typeof v === "string") env[k] = v;
10907
9837
  }
10908
9838
  scrubTmuxClientEnv(env);
10909
- const local = `${os.homedir()}/.local/bin`;
9839
+ const local = `${os$1.homedir()}/.local/bin`;
10910
9840
  if (!(env.PATH || "").split(":").includes(local)) {
10911
9841
  env.PATH = `${local}:${env.PATH || ""}`;
10912
9842
  }
@@ -11382,7 +10312,7 @@ class WebTerminalManager {
11382
10312
  try {
11383
10313
  this.emit("terminal-activity", { terminals: updates });
11384
10314
  } catch (e) {
11385
- logger.debug(`[WEB TERMINAL] activity emit failed: ${e}`);
10315
+ persistence.logger.debug(`[WEB TERMINAL] activity emit failed: ${e}`);
11386
10316
  }
11387
10317
  }
11388
10318
  /** Seed the change signature from a list the caller already delivered by
@@ -11472,7 +10402,7 @@ class WebTerminalManager {
11472
10402
  this.lastListSignature = sig;
11473
10403
  cb(list);
11474
10404
  } catch (e) {
11475
- logger.debug(`[WEB TERMINAL] list track tick failed: ${e}`);
10405
+ persistence.logger.debug(`[WEB TERMINAL] list track tick failed: ${e}`);
11476
10406
  }
11477
10407
  }
11478
10408
  /** Closed-record diff (B-084): a terminal id that was in the last observed
@@ -11500,7 +10430,7 @@ class WebTerminalManager {
11500
10430
  next.set(id, info);
11501
10431
  continue;
11502
10432
  }
11503
- persisted ??= readPersistedSessions();
10433
+ persisted ??= persistence.readPersistedSessions();
11504
10434
  const mirror = pickMirrorForTerminal(persisted, id);
11505
10435
  this.closedTerminals = appendClosedTerminal(this.closedTerminals, {
11506
10436
  id,
@@ -11518,11 +10448,11 @@ class WebTerminalManager {
11518
10448
  closedAt: now
11519
10449
  });
11520
10450
  changed = true;
11521
- logger.debug(`[WEB TERMINAL] recorded closed terminal ${id}`);
10451
+ persistence.logger.debug(`[WEB TERMINAL] recorded closed terminal ${id}`);
11522
10452
  try {
11523
10453
  this.onTerminalClosedCb?.(id);
11524
10454
  } catch (e) {
11525
- logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
10455
+ persistence.logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
11526
10456
  }
11527
10457
  }
11528
10458
  const pruned = pruneClosedAgainstLive(this.closedTerminals, liveIds);
@@ -11554,7 +10484,7 @@ class WebTerminalManager {
11554
10484
  for (const [id, info] of snapshot) {
11555
10485
  if (liveIds.has(id)) continue;
11556
10486
  if (this.closedTerminals.some((r) => r.id === id)) continue;
11557
- persisted ??= readPersistedSessions();
10487
+ persisted ??= persistence.readPersistedSessions();
11558
10488
  const mirror = pickMirrorForTerminal(persisted, id);
11559
10489
  this.closedTerminals = appendClosedTerminal(this.closedTerminals, {
11560
10490
  id,
@@ -11583,7 +10513,7 @@ class WebTerminalManager {
11583
10513
  }
11584
10514
  if (recorded > 0) {
11585
10515
  saveClosedTerminals(this.closedTerminals);
11586
- logger.debug(`[WEB TERMINAL] daemon gap: archived ${recorded} terminal(s) from the persisted snapshot`);
10516
+ persistence.logger.debug(`[WEB TERMINAL] daemon gap: archived ${recorded} terminal(s) from the persisted snapshot`);
11587
10517
  }
11588
10518
  if (candidates.length > 0) void this.autoRestore(candidates, liveIds);
11589
10519
  }
@@ -11601,7 +10531,7 @@ class WebTerminalManager {
11601
10531
  this.autoRestoreDone = true;
11602
10532
  try {
11603
10533
  if (!isTmuxAvailable()) return;
11604
- const settings = await readSettings();
10534
+ const settings = await persistence.readSettings();
11605
10535
  const config = resolveAutoRestoreConfig(settings);
11606
10536
  const selection = selectAutoRestore(candidates, {
11607
10537
  now: Date.now(),
@@ -11617,7 +10547,7 @@ class WebTerminalManager {
11617
10547
  });
11618
10548
  for (const plan of selection.plans) {
11619
10549
  if (this.shuttingDown) {
11620
- logger.debug("[WEB TERMINAL] auto-restore aborted: daemon shutting down");
10550
+ persistence.logger.debug("[WEB TERMINAL] auto-restore aborted: daemon shutting down");
11621
10551
  return;
11622
10552
  }
11623
10553
  const ok = this.restoreOneTerminal(plan);
@@ -11630,15 +10560,15 @@ class WebTerminalManager {
11630
10560
  }
11631
10561
  const line = autoRestoreSummary(selection);
11632
10562
  if (line) {
11633
- logger.info(`[WEB TERMINAL] auto-restore: ${line}`);
10563
+ persistence.logger.info(`[WEB TERMINAL] auto-restore: ${line}`);
11634
10564
  try {
11635
10565
  this.onAutoRestoreSummaryCb?.(line);
11636
10566
  } catch (e) {
11637
- logger.debug(`[WEB TERMINAL] auto-restore report failed: ${e}`);
10567
+ persistence.logger.debug(`[WEB TERMINAL] auto-restore report failed: ${e}`);
11638
10568
  }
11639
10569
  }
11640
10570
  } catch (e) {
11641
- logger.debug(`[WEB TERMINAL] auto-restore failed: ${e}`);
10571
+ persistence.logger.debug(`[WEB TERMINAL] auto-restore failed: ${e}`);
11642
10572
  }
11643
10573
  }
11644
10574
  /**
@@ -11684,17 +10614,17 @@ class WebTerminalManager {
11684
10614
  return false;
11685
10615
  }
11686
10616
  },
11687
- conversationExists: (cwd, claudeSessionId) => node_fs.existsSync(path.join(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
10617
+ conversationExists: (cwd, claudeSessionId) => node_fs.existsSync(path$1.join(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
11688
10618
  // B-273: only consulted for records that were attach terminals.
11689
10619
  // Unbounded: the panel's 50-row cap must not hide a restore target.
11690
10620
  userSessions: record.attachTmux ? this.listUserTmuxSessions(Infinity) : [],
11691
10621
  attachSocket: process.env[VH_TMUX_SOCKET_ENV],
11692
- homeDir: os.homedir()
10622
+ homeDir: os$1.homedir()
11693
10623
  });
11694
10624
  if (plan.kind === "already-live") return { type: "success", terminalId };
11695
10625
  if (plan.kind === "error") return { type: "error", reason: plan.reason };
11696
10626
  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)" : ""}`);
10627
+ persistence.logger.info(`[WEB TERMINAL] restored closed terminal ${terminalId} in ${plan.cwd}${plan.attachTmux ? ` (attached tmux session ${plan.attachTmux})` : plan.command ? " (conversation resumed)" : ""}`);
11698
10628
  this.kickListRefresh();
11699
10629
  return { type: "success", terminalId };
11700
10630
  }
@@ -11716,7 +10646,7 @@ class WebTerminalManager {
11716
10646
  "-e",
11717
10647
  `VH_TERMINAL_ID=${plan.terminalId}`,
11718
10648
  "-e",
11719
- `VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
10649
+ `VH_HAPPY_HOME_DIR=${persistence.configuration.happyHomeDir}`
11720
10650
  ] : [];
11721
10651
  const { cols, rows } = coldCreateGeometry(plan);
11722
10652
  const created = node_child_process.spawnSync(
@@ -11730,7 +10660,7 @@ class WebTerminalManager {
11730
10660
  { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
11731
10661
  ).status === 0;
11732
10662
  if (!live) {
11733
- logger.debug(`[WEB TERMINAL] auto-restore: tmux create failed for ${plan.terminalId}`);
10663
+ persistence.logger.debug(`[WEB TERMINAL] auto-restore: tmux create failed for ${plan.terminalId}`);
11734
10664
  return false;
11735
10665
  }
11736
10666
  if (plan.title) {
@@ -11767,7 +10697,7 @@ class WebTerminalManager {
11767
10697
  node_child_process.spawnSync("tmux", tmuxArgs(argv), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
11768
10698
  }
11769
10699
  }
11770
- logger.info(`[WEB TERMINAL] cold-created ${plan.terminalId} in ${plan.cwd}`);
10700
+ persistence.logger.info(`[WEB TERMINAL] cold-created ${plan.terminalId} in ${plan.cwd}`);
11771
10701
  return true;
11772
10702
  }
11773
10703
  /** Mirror the in-memory cache to disk, but only when something a restart
@@ -11801,7 +10731,7 @@ class WebTerminalManager {
11801
10731
  try {
11802
10732
  this.onNotify({ terminalId: item.id, title: item.title || "Terminal", event });
11803
10733
  } catch (e) {
11804
- logger.debug(`[WEB TERMINAL] notify callback failed: ${e}`);
10734
+ persistence.logger.debug(`[WEB TERMINAL] notify callback failed: ${e}`);
11805
10735
  }
11806
10736
  }
11807
10737
  }
@@ -11815,7 +10745,7 @@ class WebTerminalManager {
11815
10745
  this.snapshots.sweep(now);
11816
10746
  for (const [id, session] of [...this.terminals]) {
11817
10747
  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)`);
10748
+ persistence.logger.debug(`[WEB TERMINAL] reaping orphaned idle session ${id} (idle ${Math.round((now - session.lastTouch) / 6e4)}m)`);
11819
10749
  this.detach(id);
11820
10750
  }
11821
10751
  }
@@ -11841,8 +10771,8 @@ class WebTerminalManager {
11841
10771
  }
11842
10772
  const victimId = unwatchedVictim ?? anyVictim;
11843
10773
  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}`);
10774
+ if (!unwatchedVictim) persistence.logger.debug(`[WEB TERMINAL] pty cap reached with all sessions claiming watchers; force-evicting LRU ${victimId}`);
10775
+ else persistence.logger.debug(`[WEB TERMINAL] pty cap reached (${this.terminals.size}); evicting LRU ${victimId}`);
11846
10776
  this.detach(victimId);
11847
10777
  }
11848
10778
  }
@@ -11867,7 +10797,7 @@ class WebTerminalManager {
11867
10797
  async open(opts) {
11868
10798
  const cols = Math.max(2, Math.floor(opts.cols ?? 80));
11869
10799
  const rows = Math.max(2, Math.floor(opts.rows ?? 24));
11870
- const cwd = opts.cwd && opts.cwd.length > 0 ? opts.cwd : os.homedir();
10800
+ const cwd = opts.cwd && opts.cwd.length > 0 ? opts.cwd : os$1.homedir();
11871
10801
  const id = opts.terminalId && /^[a-zA-Z0-9_-]{1,64}$/.test(opts.terminalId) ? opts.terminalId : node_crypto.randomBytes(5).toString("hex");
11872
10802
  const lines = opts.streamMode === "lines";
11873
10803
  const existing = this.terminals.get(id);
@@ -11878,7 +10808,7 @@ class WebTerminalManager {
11878
10808
  this.applyResize(existing, cols, rows);
11879
10809
  if (opts.fromSeq !== void 0 && existing.ringCovers(opts.fromSeq)) {
11880
10810
  const chunks = existing.chunksAfter(opts.fromSeq);
11881
- logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=replay, seq=${existing.seq})`);
10811
+ persistence.logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=replay, seq=${existing.seq})`);
11882
10812
  return {
11883
10813
  terminalId: id,
11884
10814
  tmuxSession: existing.tmuxSession,
@@ -11897,7 +10827,7 @@ class WebTerminalManager {
11897
10827
  return { ...await this.linesSnapshotResponse(id, existing), ...echo2 };
11898
10828
  }
11899
10829
  const state = existing.subscribeState(opts.fromSeq);
11900
- logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=${state.mode}, seq=${state.seq})`);
10830
+ persistence.logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=${state.mode}, seq=${state.seq})`);
11901
10831
  return { terminalId: id, tmuxSession: existing.tmuxSession, ...state, ...echo2 };
11902
10832
  }
11903
10833
  if (opts.attachOnly || opts.resub) {
@@ -11914,7 +10844,7 @@ class WebTerminalManager {
11914
10844
  { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11915
10845
  ).status === 0;
11916
10846
  if (!alive) {
11917
- logger.debug(`[WEB TERMINAL] refused stale-client resurrection of tombstoned ${id}`);
10847
+ persistence.logger.debug(`[WEB TERMINAL] refused stale-client resurrection of tombstoned ${id}`);
11918
10848
  throw new Error("terminal-gone");
11919
10849
  }
11920
10850
  }
@@ -11936,7 +10866,7 @@ class WebTerminalManager {
11936
10866
  "-e",
11937
10867
  `VH_TERMINAL_ID=${id}`,
11938
10868
  "-e",
11939
- `VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
10869
+ `VH_HAPPY_HOME_DIR=${persistence.configuration.happyHomeDir}`
11940
10870
  ] : [];
11941
10871
  const attachOnly = !!(opts.attachOnly || opts.resub);
11942
10872
  const attachTarget = !attachOnly && opts.attachTmux ? this.resolveAttachTarget(opts.attachTmux) : void 0;
@@ -12022,7 +10952,7 @@ class WebTerminalManager {
12022
10952
  ).status === 0;
12023
10953
  if (!alive) {
12024
10954
  if (attachTarget) throw new Error("tmux-unavailable");
12025
- logger.info(`[WEB TERMINAL] tmux session creation unavailable; using direct PTY for ${id}`);
10955
+ persistence.logger.info(`[WEB TERMINAL] tmux session creation unavailable; using direct PTY for ${id}`);
12026
10956
  tmuxSession = void 0;
12027
10957
  }
12028
10958
  }
@@ -12060,11 +10990,11 @@ class WebTerminalManager {
12060
10990
  created.announcedRows = size.rows;
12061
10991
  const marker = created.ingest(geometryMarker(size.cols, size.rows));
12062
10992
  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})`);
10993
+ persistence.logger.debug(`[WEB TERMINAL] ${id} pane geometry \u2192 ${size.cols}x${size.rows} (seq=${marker.seq})`);
12064
10994
  return;
12065
10995
  }
12066
10996
  if (name === "exit") {
12067
- logger.debug(`[WEB TERMINAL] control client for ${id} got %exit (${args2})`);
10997
+ persistence.logger.debug(`[WEB TERMINAL] control client for ${id} got %exit (${args2})`);
12068
10998
  }
12069
10999
  },
12070
11000
  onExit: (code) => {
@@ -12113,7 +11043,7 @@ class WebTerminalManager {
12113
11043
  if (attachedTmux) session.attachedTmux = attachedTmux;
12114
11044
  this.terminals.set(id, session);
12115
11045
  session.onTitleChange(() => this.kickListRefresh());
12116
- logger.debug(`[WEB TERMINAL] opened ${id} (${tmuxSession ? "control" : "pty"}) ${cols}x${rows} cwd=${cwd}`);
11046
+ persistence.logger.debug(`[WEB TERMINAL] opened ${id} (${tmuxSession ? "control" : "pty"}) ${cols}x${rows} cwd=${cwd}`);
12117
11047
  this.kickListRefresh();
12118
11048
  const echo = attachedTmux ? { attachedTmux } : {};
12119
11049
  if (session.transport.kind !== "control") {
@@ -12147,7 +11077,7 @@ class WebTerminalManager {
12147
11077
  } catch {
12148
11078
  }
12149
11079
  }
12150
- logger.debug(`[WEB TERMINAL] ${tmuxSession} attaches user tmux session ${attachTarget.id} (${attachTarget.name})`);
11080
+ persistence.logger.debug(`[WEB TERMINAL] ${tmuxSession} attaches user tmux session ${attachTarget.id} (${attachTarget.name})`);
12151
11081
  return { id: attachTarget.id, name: attachTarget.name };
12152
11082
  }
12153
11083
  const startup = normalizeStartupCommand(startupCommand);
@@ -12156,7 +11086,7 @@ class WebTerminalManager {
12156
11086
  for (const a of startupInjectionArgs(tmuxSession, startup)) {
12157
11087
  node_child_process.spawnSync("tmux", tmuxArgs(a), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
12158
11088
  }
12159
- logger.debug(`[WEB TERMINAL] injected startup command into new session ${tmuxSession}`);
11089
+ persistence.logger.debug(`[WEB TERMINAL] injected startup command into new session ${tmuxSession}`);
12160
11090
  } catch {
12161
11091
  }
12162
11092
  }
@@ -12206,7 +11136,7 @@ class WebTerminalManager {
12206
11136
  );
12207
11137
  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
11138
  if (r.status !== 0 || typeof r.stdout !== "string") {
12209
- logger.debug(`[WEB TERMINAL] list-tmux-sessions failed: ${JSON.stringify(this.lastUserSessionsProbe)}`);
11139
+ persistence.logger.debug(`[WEB TERMINAL] list-tmux-sessions failed: ${JSON.stringify(this.lastUserSessionsProbe)}`);
12210
11140
  return [];
12211
11141
  }
12212
11142
  return parseUserSessions(r.stdout, max);
@@ -12253,7 +11183,7 @@ class WebTerminalManager {
12253
11183
  try {
12254
11184
  bodies = await client.send(requests, CAPTURE_TIMEOUT_MS);
12255
11185
  } catch (e) {
12256
- logger.debug(`[WEB TERMINAL] capture batch failed for ${session.id}: ${e}`);
11186
+ persistence.logger.debug(`[WEB TERMINAL] capture batch failed for ${session.id}: ${e}`);
12257
11187
  if (fresh) {
12258
11188
  this.terminals.delete(session.id);
12259
11189
  session.dispose();
@@ -12277,7 +11207,7 @@ class WebTerminalManager {
12277
11207
  }
12278
11208
  /** Build the lines-mode open response from a completed capture. */
12279
11209
  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}`);
11210
+ 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
11211
  return {
12282
11212
  terminalId: id,
12283
11213
  tmuxSession: session.tmuxSession,
@@ -12332,7 +11262,7 @@ class WebTerminalManager {
12332
11262
  const target = session.paneId ?? `=${session.tmuxSession}:`;
12333
11263
  const { commands, dropped } = encodeTerminalWrite(text, target, { normalizeKeyNames: true });
12334
11264
  if (dropped) {
12335
- logger.debug(`[WEB TERMINAL] dropped terminal auto-reply for ${terminalId}`);
11265
+ persistence.logger.debug(`[WEB TERMINAL] dropped terminal auto-reply for ${terminalId}`);
12336
11266
  return;
12337
11267
  }
12338
11268
  if (commands.length === 0) return;
@@ -12393,7 +11323,7 @@ class WebTerminalManager {
12393
11323
  try {
12394
11324
  session.transport.pty.resize(c, r);
12395
11325
  } catch (e) {
12396
- logger.debug(`[WEB TERMINAL] resize ${session.id} failed: ${e}`);
11326
+ persistence.logger.debug(`[WEB TERMINAL] resize ${session.id} failed: ${e}`);
12397
11327
  }
12398
11328
  } else if (c !== session.cols || r !== session.rows) {
12399
11329
  session.transport.client.sendFireAndForget([
@@ -12414,7 +11344,7 @@ class WebTerminalManager {
12414
11344
  if (!session) return;
12415
11345
  session.subscribers = Math.max(0, session.subscribers - 1);
12416
11346
  session.lastTouch = Date.now();
12417
- logger.debug(`[WEB TERMINAL] unsubscribed ${terminalId} (subs=${session.subscribers})`);
11347
+ persistence.logger.debug(`[WEB TERMINAL] unsubscribed ${terminalId} (subs=${session.subscribers})`);
12418
11348
  }
12419
11349
  /** Detach the pty (its single tmux client) without killing the tmux session.
12420
11350
  * Used by the reaper / cap eviction. Reopening the id respawns a pty and
@@ -12426,7 +11356,7 @@ class WebTerminalManager {
12426
11356
  this.snapshots.drop(terminalId);
12427
11357
  this.captureInFlight.delete(terminalId);
12428
11358
  session.dispose();
12429
- logger.debug(`[WEB TERMINAL] detached ${terminalId} (tmux session survives)`);
11359
+ persistence.logger.debug(`[WEB TERMINAL] detached ${terminalId} (tmux session survives)`);
12430
11360
  }
12431
11361
  /** Stop every live session (test teardown / daemon shutdown). Control
12432
11362
  * clients get the SIGTERM→SIGKILL discipline, so no tmux child is left
@@ -12450,20 +11380,20 @@ class WebTerminalManager {
12450
11380
  const killed = node_child_process.spawnSync("tmux", tmuxArgs(["kill-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
12451
11381
  const verified = node_child_process.spawnSync("tmux", tmuxArgs(["has-session", "-t", target]), { stdio: "ignore", env: ptyEnv() });
12452
11382
  if (!tmuxKillVerified(killed, verified)) {
12453
- logger.debug(`[WEB TERMINAL] failed to verify tmux session removal ${target}`);
11383
+ persistence.logger.debug(`[WEB TERMINAL] failed to verify tmux session removal ${target}`);
12454
11384
  return false;
12455
11385
  }
12456
11386
  if (opts?.alsoAttached && info?.attachTmux) {
12457
11387
  const matches = this.listUserTmuxSessions(Infinity).filter((s) => s.name === info.attachTmux);
12458
11388
  if (matches.length === 1) {
12459
11389
  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}`);
11390
+ persistence.logger.info(`[WEB TERMINAL] killed attached user tmux session ${matches[0].id} (${info.attachTmux}) with ${terminalId}`);
12461
11391
  } else {
12462
- logger.debug(`[WEB TERMINAL] alsoAttached: no unique session named ${info.attachTmux} (${matches.length} matches)`);
11392
+ persistence.logger.debug(`[WEB TERMINAL] alsoAttached: no unique session named ${info.attachTmux} (${matches.length} matches)`);
12463
11393
  }
12464
11394
  }
12465
11395
  if (info) {
12466
- const mirror = pickMirrorForTerminal(readPersistedSessions(), terminalId);
11396
+ const mirror = pickMirrorForTerminal(persistence.readPersistedSessions(), terminalId);
12467
11397
  this.recordClosed({
12468
11398
  id: terminalId,
12469
11399
  title: info.title,
@@ -12482,13 +11412,13 @@ class WebTerminalManager {
12482
11412
  try {
12483
11413
  this.onTerminalClosedCb?.(terminalId);
12484
11414
  } catch (e) {
12485
- logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
11415
+ persistence.logger.debug(`[WEB TERMINAL] terminal-closed callback failed: ${e}`);
12486
11416
  }
12487
11417
  this.detach(terminalId);
12488
11418
  this.tombstones[terminalId] = Date.now();
12489
11419
  saveTombstones(this.tombstones);
12490
11420
  this.notifyTracker.remove(terminalId);
12491
- logger.debug(`[WEB TERMINAL] killed session vh-${terminalId}`);
11421
+ persistence.logger.debug(`[WEB TERMINAL] killed session vh-${terminalId}`);
12492
11422
  this.kickListRefresh();
12493
11423
  return true;
12494
11424
  }
@@ -12526,7 +11456,7 @@ class WebTerminalManager {
12526
11456
  { encoding: "utf8", env }
12527
11457
  );
12528
11458
  if (r.status !== 0 || !r.stdout) return [];
12529
- const hostname = os.hostname();
11459
+ const hostname = os$1.hostname();
12530
11460
  const out = [];
12531
11461
  for (const line of r.stdout.split("\n")) {
12532
11462
  const s = parseSessionListLine(line);
@@ -12758,7 +11688,7 @@ class WebTerminalManager {
12758
11688
 
12759
11689
  const TMUX_TIMEOUT_MS = 3e3;
12760
11690
  const VH_LIST_SESSIONS_FORMAT = LIST_SESSIONS_FORMAT;
12761
- function parseVhTerminals(stdout, hostname = os.hostname()) {
11691
+ function parseVhTerminals(stdout, hostname = os$1.hostname()) {
12762
11692
  const out = [];
12763
11693
  for (const line of stdout.split("\n")) {
12764
11694
  const s = parseSessionListLine(line);
@@ -12897,7 +11827,7 @@ function decideBackfill(messages, maxLines) {
12897
11827
  }
12898
11828
 
12899
11829
  function detectCLIAvailability() {
12900
- const isWindows = os$1.platform() === "win32";
11830
+ const isWindows = os.platform() === "win32";
12901
11831
  if (isWindows) {
12902
11832
  return detectWindows();
12903
11833
  }
@@ -12922,14 +11852,14 @@ function commandExistsWindows(name) {
12922
11852
  const SAFE_COMMAND_NAME = /^[A-Za-z0-9._-]+$/;
12923
11853
  function commandOnPath(name) {
12924
11854
  if (!SAFE_COMMAND_NAME.test(name)) return false;
12925
- return os$1.platform() === "win32" ? commandExistsWindows(name) : commandExists(name);
11855
+ return os.platform() === "win32" ? commandExistsWindows(name) : commandExists(name);
12926
11856
  }
12927
11857
  function detectPosix() {
12928
11858
  const claude = commandExists("claude");
12929
11859
  const codex = commandExists("codex");
12930
11860
  const gemini = commandExists("gemini");
12931
11861
  const openclawCommand = commandExists("openclaw");
12932
- const openclawConfig = fs$1.existsSync(path$1.join(os$1.homedir(), ".openclaw", "openclaw.json"));
11862
+ const openclawConfig = fs$1.existsSync(path.join(os.homedir(), ".openclaw", "openclaw.json"));
12933
11863
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12934
11864
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12935
11865
  const pi = commandExists("pi") && commandExists("pi-acp");
@@ -12941,7 +11871,7 @@ function detectWindows() {
12941
11871
  const codex = checkCommand("codex");
12942
11872
  const gemini = checkCommand("gemini");
12943
11873
  const openclawCommand = checkCommand("openclaw");
12944
- const openclawConfig = fs$1.existsSync(path$1.join(process.env.USERPROFILE || os$1.homedir(), ".openclaw", "openclaw.json"));
11874
+ const openclawConfig = fs$1.existsSync(path.join(process.env.USERPROFILE || os.homedir(), ".openclaw", "openclaw.json"));
12945
11875
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12946
11876
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12947
11877
  const pi = checkCommand("pi") && checkCommand("pi-acp");
@@ -12984,10 +11914,10 @@ function deriveContentKeyPair(secret) {
12984
11914
  secretKey: keyPair.secretKey
12985
11915
  };
12986
11916
  }
12987
- function getLocalHappyAgentCredentialPath(happyHomeDir = configuration.happyHomeDir) {
12988
- return path.join(happyHomeDir, "agent.key");
11917
+ function getLocalHappyAgentCredentialPath(happyHomeDir = persistence.configuration.happyHomeDir) {
11918
+ return path$1.join(happyHomeDir, "agent.key");
12989
11919
  }
12990
- function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDir, serverUrl = configuration.serverUrl) {
11920
+ function readLocalHappyAgentCredentials(happyHomeDir = persistence.configuration.happyHomeDir, serverUrl = persistence.configuration.serverUrl) {
12991
11921
  const credentialPath = getLocalHappyAgentCredentialPath(happyHomeDir);
12992
11922
  if (!node_fs.existsSync(credentialPath)) {
12993
11923
  return null;
@@ -12995,8 +11925,8 @@ function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDi
12995
11925
  try {
12996
11926
  const parsed = AgentCredentialsSchema.parse(JSON.parse(node_fs.readFileSync(credentialPath, "utf8")));
12997
11927
  if (!parsed.authServerUrl) return null;
12998
- if (credentialRelayProblem(parsed.authServerUrl, serverUrl)) return null;
12999
- const secret = decodeBase64(parsed.secret);
11928
+ if (persistence.credentialRelayProblem(parsed.authServerUrl, serverUrl)) return null;
11929
+ const secret = persistence.decodeBase64(parsed.secret);
13000
11930
  return {
13001
11931
  token: parsed.token,
13002
11932
  authServerUrl: parsed.authServerUrl,
@@ -13007,10 +11937,10 @@ function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDi
13007
11937
  return null;
13008
11938
  }
13009
11939
  }
13010
- function hasLocalHappyAgentAuth(happyHomeDir = configuration.happyHomeDir) {
11940
+ function hasLocalHappyAgentAuth(happyHomeDir = persistence.configuration.happyHomeDir) {
13011
11941
  return readLocalHappyAgentCredentials(happyHomeDir) !== null;
13012
11942
  }
13013
- function detectResumeSupport(happyHomeDir = configuration.happyHomeDir) {
11943
+ function detectResumeSupport(happyHomeDir = persistence.configuration.happyHomeDir) {
13014
11944
  const happyAgentAuthenticated = hasLocalHappyAgentAuth(happyHomeDir);
13015
11945
  return {
13016
11946
  rpcAvailable: happyAgentAuthenticated,
@@ -13043,7 +11973,7 @@ async function listClaudeProjectDirs(projectsRoot) {
13043
11973
  }
13044
11974
  const dirs = [];
13045
11975
  for (const name of names) {
13046
- const full = path.join(projectsRoot, name);
11976
+ const full = path$1.join(projectsRoot, name);
13047
11977
  try {
13048
11978
  if ((await promises.stat(full)).isDirectory()) dirs.push(full);
13049
11979
  } catch {
@@ -13064,11 +11994,11 @@ async function collectCandidates(projectDirs, exclude) {
13064
11994
  if (!name.endsWith(".jsonl")) continue;
13065
11995
  const id = name.slice(0, -".jsonl".length);
13066
11996
  if (!UUID_RE$1.test(id) || exclude.has(id.toLowerCase())) continue;
13067
- const path$1 = path.join(dir, name);
11997
+ const path = path$1.join(dir, name);
13068
11998
  try {
13069
- const s = await promises.stat(path$1);
11999
+ const s = await promises.stat(path);
13070
12000
  if (!s.isFile() || s.size === 0) continue;
13071
- out.push({ path: path$1, id, updatedAt: s.mtimeMs, sizeBytes: s.size });
12001
+ out.push({ path, id, updatedAt: s.mtimeMs, sizeBytes: s.size });
13072
12002
  } catch {
13073
12003
  }
13074
12004
  }
@@ -13213,7 +12143,7 @@ class ForkSourceMissingError extends Error {
13213
12143
  }
13214
12144
  }
13215
12145
  function jsonlPath(projectDir, sessionId) {
13216
- return path.join(projectDir, `${sessionId}.jsonl`);
12146
+ return path$1.join(projectDir, `${sessionId}.jsonl`);
13217
12147
  }
13218
12148
  function isUserPrompt(parsed) {
13219
12149
  if (!parsed || parsed.type !== "user") return false;
@@ -13236,10 +12166,10 @@ function userPromptText(parsed) {
13236
12166
  async function discardForkedSession(projectDir, claudeSessionId) {
13237
12167
  try {
13238
12168
  await promises.unlink(jsonlPath(projectDir, claudeSessionId));
13239
- logger.debug(`[CLAUDE FORK] Discarded unused fork ${claudeSessionId}`);
12169
+ persistence.logger.debug(`[CLAUDE FORK] Discarded unused fork ${claudeSessionId}`);
13240
12170
  } catch (error) {
13241
12171
  if (error.code !== "ENOENT") {
13242
- logger.debug(`[CLAUDE FORK] Failed to discard fork ${claudeSessionId}:`, error);
12172
+ persistence.logger.debug(`[CLAUDE FORK] Failed to discard fork ${claudeSessionId}:`, error);
13243
12173
  }
13244
12174
  }
13245
12175
  }
@@ -13255,7 +12185,7 @@ async function forkSession(projectDir, sourceClaudeSessionId) {
13255
12185
  }
13256
12186
  throw error;
13257
12187
  }
13258
- logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}`);
12188
+ persistence.logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}`);
13259
12189
  return newId;
13260
12190
  }
13261
12191
  async function forkAndTruncateSession(projectDir, sourceClaudeSessionId, cutAfterUuid) {
@@ -13277,7 +12207,7 @@ async function forkAndTruncateSession(projectDir, sourceClaudeSessionId, cutAfte
13277
12207
  try {
13278
12208
  parsed = JSON.parse(line);
13279
12209
  } catch {
13280
- logger.debug(`[CLAUDE FORK] Skipped malformed JSON line during truncation copy`);
12210
+ persistence.logger.debug(`[CLAUDE FORK] Skipped malformed JSON line during truncation copy`);
13281
12211
  }
13282
12212
  if (foundMarker && parsed && isUserPrompt(parsed)) {
13283
12213
  break;
@@ -13309,7 +12239,7 @@ async function forkAndTruncateSession(projectDir, sourceClaudeSessionId, cutAfte
13309
12239
  throw new ForkTruncateUuidNotFoundError(cutAfterUuid, src);
13310
12240
  }
13311
12241
  await promises.rename(tempDst, finalDst);
13312
- logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}, cut after ${cutAfterUuid}`);
12242
+ persistence.logger.debug(`[CLAUDE FORK] Forked ${sourceClaudeSessionId} -> ${newId}, cut after ${cutAfterUuid}`);
13313
12243
  return newId;
13314
12244
  }
13315
12245
  async function listClaudeRewindPoints(projectDir, claudeSessionId) {
@@ -13446,11 +12376,11 @@ function errorLogMetadata(error) {
13446
12376
  }
13447
12377
 
13448
12378
  function expandPath(pathValue, sessionPath) {
13449
- const expandedHome = pathValue.replace(/^~(?=\/|$)/, os.homedir());
13450
- if (path.isAbsolute(expandedHome)) {
12379
+ const expandedHome = pathValue.replace(/^~(?=\/|$)/, os$1.homedir());
12380
+ if (path$1.isAbsolute(expandedHome)) {
13451
12381
  return expandedHome;
13452
12382
  }
13453
- return path.resolve(sessionPath, expandedHome);
12383
+ return path$1.resolve(sessionPath, expandedHome);
13454
12384
  }
13455
12385
  function resolvePaths(paths, sessionPath) {
13456
12386
  return paths.map((pathValue) => expandPath(pathValue, sessionPath));
@@ -13472,10 +12402,10 @@ function buildSandboxRuntimeConfig(sandboxConfig, sessionPath) {
13472
12402
  const allowWrite = (() => {
13473
12403
  switch (sandboxConfig.sessionIsolation) {
13474
12404
  case "strict":
13475
- return uniquePaths([path.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
12405
+ return uniquePaths([path$1.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
13476
12406
  case "workspace": {
13477
- const workspaceRoot = sandboxConfig.workspaceRoot ? expandPath(sandboxConfig.workspaceRoot, sessionPath) : path.resolve(sessionPath);
13478
- return uniquePaths([workspaceRoot, path.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
12407
+ const workspaceRoot = sandboxConfig.workspaceRoot ? expandPath(sandboxConfig.workspaceRoot, sessionPath) : path$1.resolve(sessionPath);
12408
+ return uniquePaths([workspaceRoot, path$1.resolve(sessionPath), ...extraWritePaths, ...sharedAgentStatePaths]);
13479
12409
  }
13480
12410
  case "custom":
13481
12411
  return uniquePaths([
@@ -13837,9 +12767,9 @@ class CodexAppServerClient {
13837
12767
  command = wrapped.command;
13838
12768
  args = wrapped.args;
13839
12769
  this.sandboxEnabled = true;
13840
- logger.info(`[CodexAppServer] Sandbox enabled`);
12770
+ persistence.logger.info(`[CodexAppServer] Sandbox enabled`);
13841
12771
  } catch (error) {
13842
- logger.warn("[CodexAppServer] Failed to initialize sandbox; continuing without.", error);
12772
+ persistence.logger.warn("[CodexAppServer] Failed to initialize sandbox; continuing without.", error);
13843
12773
  this.sandboxCleanup = null;
13844
12774
  }
13845
12775
  }
@@ -13856,7 +12786,7 @@ class CodexAppServerClient {
13856
12786
  if (this.sandboxEnabled) {
13857
12787
  env.CODEX_SANDBOX = "seatbelt";
13858
12788
  }
13859
- logger.debug(`[CodexAppServer] Spawning: ${command} ${args.join(" ")}`);
12789
+ persistence.logger.debug(`[CodexAppServer] Spawning: ${command} ${args.join(" ")}`);
13860
12790
  const epoch = ++this.processEpoch;
13861
12791
  const proc = spawn.spawn(command, args, {
13862
12792
  stdio: ["pipe", "pipe", "pipe"],
@@ -13865,12 +12795,12 @@ class CodexAppServerClient {
13865
12795
  });
13866
12796
  this.process = proc;
13867
12797
  proc.on("error", (err) => {
13868
- logger.debug("[CodexAppServer] Process error:", err);
12798
+ persistence.logger.debug("[CodexAppServer] Process error:", err);
13869
12799
  });
13870
12800
  proc.on("exit", (code, signal) => {
13871
- logger.debug(`[CodexAppServer] Process exited: code=${code} signal=${signal}`);
12801
+ persistence.logger.debug(`[CodexAppServer] Process exited: code=${code} signal=${signal}`);
13872
12802
  if (this.process !== proc || this.processEpoch !== epoch) {
13873
- logger.debug("[CodexAppServer] Ignoring stale process exit");
12803
+ persistence.logger.debug("[CodexAppServer] Ignoring stale process exit");
13874
12804
  return;
13875
12805
  }
13876
12806
  this.connected = false;
@@ -13884,7 +12814,7 @@ class CodexAppServerClient {
13884
12814
  proc.stderr?.on("data", (chunk) => {
13885
12815
  if (this.process !== proc || this.processEpoch !== epoch) return;
13886
12816
  const text = chunk.toString().trim();
13887
- if (text) logger.debug("[CodexAppServer:stderr]", contentLogMetadata(text));
12817
+ if (text) persistence.logger.debug("[CodexAppServer:stderr]", contentLogMetadata(text));
13888
12818
  });
13889
12819
  this.readline = node_readline.createInterface({ input: proc.stdout });
13890
12820
  this.readline.on("line", (line) => {
@@ -13895,7 +12825,7 @@ class CodexAppServerClient {
13895
12825
  clientInfo: {
13896
12826
  name: "happy-codex",
13897
12827
  title: "Happy Codex Client",
13898
- version: packageJson.version
12828
+ version: persistence.packageJson.version
13899
12829
  },
13900
12830
  capabilities: {
13901
12831
  experimentalApi: true
@@ -13904,14 +12834,14 @@ class CodexAppServerClient {
13904
12834
  await this.request("initialize", initParams);
13905
12835
  this.notify("initialized");
13906
12836
  this.connected = true;
13907
- logger.debug("[CodexAppServer] Connected and initialized");
12837
+ persistence.logger.debug("[CodexAppServer] Connected and initialized");
13908
12838
  }
13909
12839
  async disconnectInternal(opts) {
13910
12840
  if (!this.connected && !this.process) return;
13911
12841
  const proc = this.process;
13912
12842
  const pid = proc?.pid;
13913
12843
  const epoch = this.processEpoch;
13914
- logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
12844
+ persistence.logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
13915
12845
  this.readline?.close();
13916
12846
  this.readline = null;
13917
12847
  try {
@@ -13952,7 +12882,7 @@ class CodexAppServerClient {
13952
12882
  this.sandboxCleanup = null;
13953
12883
  }
13954
12884
  this.sandboxEnabled = false;
13955
- logger.debug("[CodexAppServer] Disconnected");
12885
+ persistence.logger.debug("[CodexAppServer] Disconnected");
13956
12886
  }
13957
12887
  async disconnect() {
13958
12888
  await this.disconnectInternal();
@@ -13990,7 +12920,7 @@ class CodexAppServerClient {
13990
12920
  this._threadId = result.thread.id;
13991
12921
  this._turnId = null;
13992
12922
  this.rememberThreadDefaults(opts);
13993
- logger.debug("[CodexAppServer] Thread started:", this._threadId);
12923
+ persistence.logger.debug("[CodexAppServer] Thread started:", this._threadId);
13994
12924
  return { threadId: result.thread.id, model: result.model };
13995
12925
  }
13996
12926
  async resumeThread(opts) {
@@ -14021,7 +12951,7 @@ class CodexAppServerClient {
14021
12951
  sandbox: opts?.sandbox ?? defaults.sandbox,
14022
12952
  mcpServers: opts?.mcpServers ?? defaults.mcpServers
14023
12953
  });
14024
- logger.debug("[CodexAppServer] Thread resumed:", this._threadId);
12954
+ persistence.logger.debug("[CodexAppServer] Thread resumed:", this._threadId);
14025
12955
  return { threadId: result.thread.id, model: result.model };
14026
12956
  }
14027
12957
  async forkThread(opts) {
@@ -14049,7 +12979,7 @@ class CodexAppServerClient {
14049
12979
  sandbox: opts.sandbox ?? defaults.sandbox,
14050
12980
  mcpServers: opts.mcpServers ?? defaults.mcpServers
14051
12981
  });
14052
- logger.debug("[CodexAppServer] Thread forked:", opts.threadId, "->", this._threadId);
12982
+ persistence.logger.debug("[CodexAppServer] Thread forked:", opts.threadId, "->", this._threadId);
14053
12983
  return { threadId: result.thread.id, model: result.model, thread: result.thread };
14054
12984
  }
14055
12985
  async readThread(opts) {
@@ -14084,7 +13014,7 @@ class CodexAppServerClient {
14084
13014
  await this.resumeThread({ threadId });
14085
13015
  return true;
14086
13016
  } catch (error) {
14087
- logger.warn("[CodexAppServer] Failed to resume thread after reconnect", error);
13017
+ persistence.logger.warn("[CodexAppServer] Failed to resume thread after reconnect", error);
14088
13018
  this._threadId = null;
14089
13019
  this.threadDefaults = null;
14090
13020
  return false;
@@ -14111,7 +13041,7 @@ class CodexAppServerClient {
14111
13041
  const pending = this.pendingTurnCompletion;
14112
13042
  if (!pending) return;
14113
13043
  if (pending.turnId && turnId && pending.turnId !== turnId) {
14114
- logger.debug(
13044
+ persistence.logger.debug(
14115
13045
  `[CodexAppServer] Ignoring ${source} for turn ${turnId}; awaiting ${pending.turnId}`
14116
13046
  );
14117
13047
  return;
@@ -14150,7 +13080,7 @@ class CodexAppServerClient {
14150
13080
  if (!shouldForceRestart) {
14151
13081
  return { hadActiveTurn: true, aborted: false, forcedRestart: false, resumedThread: false };
14152
13082
  }
14153
- logger.warn(`[CodexAppServer] interrupt did not settle turn in ${gracePeriodMs}ms; force-restarting app-server`);
13083
+ persistence.logger.warn(`[CodexAppServer] interrupt did not settle turn in ${gracePeriodMs}ms; force-restarting app-server`);
14154
13084
  const pendingTurnId = this.pendingTurnCompletion?.turnId ?? this._turnId;
14155
13085
  if (this.pendingTurnCompletion) {
14156
13086
  this.eventHandler?.({
@@ -14224,7 +13154,7 @@ class CodexAppServerClient {
14224
13154
  };
14225
13155
  timer = setTimeout(() => {
14226
13156
  if (this.pendingTurnCompletion) {
14227
- logger.warn(`[CodexAppServer] Turn timed out after ${timeoutMs}ms \u2014 treating as abort`);
13157
+ persistence.logger.warn(`[CodexAppServer] Turn timed out after ${timeoutMs}ms \u2014 treating as abort`);
14228
13158
  this.resolvePendingTurn(true);
14229
13159
  }
14230
13160
  }, timeoutMs);
@@ -14243,7 +13173,7 @@ class CodexAppServerClient {
14243
13173
  async interruptTurn() {
14244
13174
  if (!this._threadId) return;
14245
13175
  if (!this._turnId) {
14246
- logger.debug("[CodexAppServer] interruptTurn: no active turnId, skipping");
13176
+ persistence.logger.debug("[CodexAppServer] interruptTurn: no active turnId, skipping");
14247
13177
  return;
14248
13178
  }
14249
13179
  const params = {
@@ -14254,7 +13184,7 @@ class CodexAppServerClient {
14254
13184
  try {
14255
13185
  await this.request("turn/interrupt", params);
14256
13186
  } catch (err) {
14257
- logger.debug("[CodexAppServer] interruptTurn error (may be expected):", err);
13187
+ persistence.logger.debug("[CodexAppServer] interruptTurn error (may be expected):", err);
14258
13188
  } finally {
14259
13189
  this.pendingInterrupt = null;
14260
13190
  }
@@ -14267,7 +13197,7 @@ class CodexAppServerClient {
14267
13197
  return this._threadId !== null;
14268
13198
  }
14269
13199
  clearThreadState() {
14270
- logger.debug(
13200
+ persistence.logger.debug(
14271
13201
  `[CodexAppServer] Clearing thread state: thread=${this._threadId ?? "none"} turn=${this._turnId ?? "none"}`
14272
13202
  );
14273
13203
  this.resolvePendingTurn(true);
@@ -14306,7 +13236,7 @@ class CodexAppServerClient {
14306
13236
  });
14307
13237
  const msg = { jsonrpc: "2.0", id, method, params };
14308
13238
  const line = JSON.stringify(msg) + "\n";
14309
- logger.debug(`[CodexAppServer] \u2192 ${method} (id=${id})`);
13239
+ persistence.logger.debug(`[CodexAppServer] \u2192 ${method} (id=${id})`);
14310
13240
  this.process.stdin.write(line);
14311
13241
  });
14312
13242
  }
@@ -14314,13 +13244,13 @@ class CodexAppServerClient {
14314
13244
  if (!this.process?.stdin?.writable) return;
14315
13245
  const msg = { jsonrpc: "2.0", method, params };
14316
13246
  this.process.stdin.write(JSON.stringify(msg) + "\n");
14317
- logger.debug(`[CodexAppServer] \u2192 ${method} (notification)`);
13247
+ persistence.logger.debug(`[CodexAppServer] \u2192 ${method} (notification)`);
14318
13248
  }
14319
13249
  respond(id, result) {
14320
13250
  if (!this.process?.stdin?.writable) return;
14321
13251
  const msg = { jsonrpc: "2.0", id, result };
14322
13252
  this.process.stdin.write(JSON.stringify(msg) + "\n");
14323
- logger.debug(`[CodexAppServer] \u2192 response (id=${id})`);
13253
+ persistence.logger.debug(`[CodexAppServer] \u2192 response (id=${id})`);
14324
13254
  }
14325
13255
  handleLine(line, sourceEpoch = this.processEpoch) {
14326
13256
  if (sourceEpoch !== this.processEpoch) {
@@ -14331,14 +13261,14 @@ class CodexAppServerClient {
14331
13261
  try {
14332
13262
  msg = JSON.parse(line);
14333
13263
  } catch {
14334
- logger.debug("[CodexAppServer] Non-JSON line received:", contentLogMetadata(line));
13264
+ persistence.logger.debug("[CodexAppServer] Non-JSON line received:", contentLogMetadata(line));
14335
13265
  return;
14336
13266
  }
14337
13267
  if (msg.id != null && (msg.result !== void 0 || msg.error !== void 0)) {
14338
13268
  const pending = this.pending.get(msg.id);
14339
13269
  if (pending) {
14340
13270
  if (pending.epoch !== sourceEpoch) {
14341
- logger.debug(`[CodexAppServer] Ignoring response from stale epoch for id=${msg.id}`);
13271
+ persistence.logger.debug(`[CodexAppServer] Ignoring response from stale epoch for id=${msg.id}`);
14342
13272
  return;
14343
13273
  }
14344
13274
  this.pending.delete(msg.id);
@@ -14352,7 +13282,7 @@ class CodexAppServerClient {
14352
13282
  }
14353
13283
  if (msg.id != null && msg.method) {
14354
13284
  this.handleServerRequest(msg.id, msg.method, msg.params).catch((err) => {
14355
- logger.debug("[CodexAppServer] Error handling server request:", errorLogMetadata(err));
13285
+ persistence.logger.debug("[CodexAppServer] Error handling server request:", errorLogMetadata(err));
14356
13286
  });
14357
13287
  return;
14358
13288
  }
@@ -14360,7 +13290,7 @@ class CodexAppServerClient {
14360
13290
  this.handleNotification(msg.method, msg.params);
14361
13291
  return;
14362
13292
  }
14363
- logger.debug("[CodexAppServer] Unhandled message:", contentLogMetadata(msg));
13293
+ persistence.logger.debug("[CodexAppServer] Unhandled message:", contentLogMetadata(msg));
14364
13294
  }
14365
13295
  /**
14366
13296
  * Map our internal ReviewDecision to the wire format the server expects.
@@ -14472,7 +13402,7 @@ class CodexAppServerClient {
14472
13402
  this.respond(id, { decision: this.mapDecisionToWire(decision, legacy) });
14473
13403
  return;
14474
13404
  }
14475
- logger.debug(`[CodexAppServer] Unknown server request: ${method}`);
13405
+ persistence.logger.debug(`[CodexAppServer] Unknown server request: ${method}`);
14476
13406
  this.respond(id, {});
14477
13407
  }
14478
13408
  async handleApproval(params) {
@@ -14480,7 +13410,7 @@ class CodexAppServerClient {
14480
13410
  try {
14481
13411
  return await this.approvalHandler(params);
14482
13412
  } catch (err) {
14483
- logger.debug("[CodexAppServer] Approval handler error:", err);
13413
+ persistence.logger.debug("[CodexAppServer] Approval handler error:", err);
14484
13414
  return "denied";
14485
13415
  }
14486
13416
  }
@@ -14514,11 +13444,11 @@ class CodexAppServerClient {
14514
13444
  return;
14515
13445
  }
14516
13446
  if (this.handleRawNotification(method, params)) {
14517
- logger.debug(`[CodexAppServer] Raw notification: ${method}`);
13447
+ persistence.logger.debug(`[CodexAppServer] Raw notification: ${method}`);
14518
13448
  return;
14519
13449
  }
14520
13450
  if (method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed") {
14521
- logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
13451
+ persistence.logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
14522
13452
  if (method === "turn/started") {
14523
13453
  const turnId = this.extractTurnId(params);
14524
13454
  if (turnId) {
@@ -14537,10 +13467,10 @@ class CodexAppServerClient {
14537
13467
  return;
14538
13468
  }
14539
13469
  if (method === "mcpServer/startupStatus/updated") {
14540
- logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
13470
+ persistence.logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
14541
13471
  return;
14542
13472
  }
14543
- logger.debug(`[CodexAppServer] Notification: ${method}`);
13473
+ persistence.logger.debug(`[CodexAppServer] Notification: ${method}`);
14544
13474
  }
14545
13475
  }
14546
13476
 
@@ -14819,7 +13749,7 @@ class ApiMachineClient {
14819
13749
  scopePrefix: this.machine.id,
14820
13750
  encryptionKey: this.machine.encryptionKey,
14821
13751
  encryptionVariant: this.machine.encryptionVariant,
14822
- logger: (msg, data) => logger.debug(msg, data)
13752
+ logger: (msg, data) => persistence.logger.debug(msg, data)
14823
13753
  });
14824
13754
  registerCommonHandlers(this.rpcHandlerManager, process.cwd());
14825
13755
  registerFsHandlers(this.rpcHandlerManager);
@@ -14877,7 +13807,7 @@ class ApiMachineClient {
14877
13807
  if (this.relaySocket?.connected) this.relaySocket.emit(event, out);
14878
13808
  }, (n) => {
14879
13809
  sendTerminalNotification({
14880
- baseUrl: configuration.serverUrl,
13810
+ baseUrl: persistence.configuration.serverUrl,
14881
13811
  token: this.token,
14882
13812
  title: n.title,
14883
13813
  message: terminalNotifyMessage(n.event),
@@ -14907,12 +13837,12 @@ class ApiMachineClient {
14907
13837
  * Encrypted with the per-machine key; the server relays without reading.
14908
13838
  */
14909
13839
  pushClipboard(text) {
14910
- const prepared = limits.prepareClipboardText(text);
13840
+ const prepared = persistence.prepareClipboardText(text);
14911
13841
  if (!this.socket?.connected) {
14912
13842
  return { delivered: false, truncated: prepared.truncated, totalBytes: prepared.totalBytes, error: "daemon is not connected to the server" };
14913
13843
  }
14914
13844
  this.socket.emit("clipboard-push", {
14915
- payload: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, prepared.text)),
13845
+ payload: persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, prepared.text)),
14916
13846
  enc: true,
14917
13847
  truncated: prepared.truncated,
14918
13848
  totalBytes: prepared.totalBytes
@@ -14923,7 +13853,7 @@ class ApiMachineClient {
14923
13853
  * as the live output stream) → base64 ciphertext. Used for both live output
14924
13854
  * and the open-terminal snapshot/replay payloads. */
14925
13855
  encTerminalData(dataBase64) {
14926
- return encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, dataBase64));
13856
+ return persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, dataBase64));
14927
13857
  }
14928
13858
  setCliUpdateRetryHandler(handler) {
14929
13859
  this.rpcHandlerManager.registerHandler("cli-update-retry", (params) => handler(params && typeof params === "object" ? params.version : void 0));
@@ -14933,7 +13863,7 @@ class ApiMachineClient {
14933
13863
  setCliUpdateState(state) {
14934
13864
  this.cliUpdateState = state;
14935
13865
  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));
13866
+ 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
13867
  }
14938
13868
  /**
14939
13869
  * B-276: publish the daemon-context Claude auth preflight into daemonState
@@ -14950,7 +13880,7 @@ class ApiMachineClient {
14950
13880
  status: current?.status ?? "running",
14951
13881
  claudeAuth: state
14952
13882
  }))).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);
13883
+ persistence.logger.debug("[API MACHINE] Failed to publish Claude auth state:", error);
14954
13884
  return false;
14955
13885
  });
14956
13886
  this.cliUpdatePushChain = attempt.then(() => void 0);
@@ -14987,17 +13917,17 @@ class ApiMachineClient {
14987
13917
  this.listTrackedSessionIds = listTrackedSessionIds ?? null;
14988
13918
  this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
14989
13919
  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));
13920
+ persistence.logger.debug("[API MACHINE] Spawning session:", summarizeSpawnSessionForLog(params));
14991
13921
  if (!directory && variant !== "assistant") {
14992
13922
  throw new Error("Directory is required");
14993
13923
  }
14994
13924
  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
13925
  switch (result.type) {
14996
13926
  case "success":
14997
- logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
13927
+ persistence.logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
14998
13928
  return { type: "success", sessionId: result.sessionId };
14999
13929
  case "requestToApproveDirectoryCreation":
15000
- logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
13930
+ persistence.logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
15001
13931
  return { type: "requestToApproveDirectoryCreation", directory: result.directory };
15002
13932
  case "error":
15003
13933
  throw new Error(result.errorMessage);
@@ -15045,7 +13975,7 @@ class ApiMachineClient {
15045
13975
  await discardForkedSession(projectDir, newClaudeSessionId);
15046
13976
  return result;
15047
13977
  }
15048
- logger.debug(`[API MACHINE] Imported Claude conversation ${claudeSessionId} as session ${result.sessionId}`);
13978
+ persistence.logger.debug(`[API MACHINE] Imported Claude conversation ${claudeSessionId} as session ${result.sessionId}`);
15049
13979
  return { type: "success", sessionId: result.sessionId, newClaudeSessionId };
15050
13980
  });
15051
13981
  this.syncResumeSessionRpcRegistration();
@@ -15170,7 +14100,7 @@ class ApiMachineClient {
15170
14100
  if (!success) {
15171
14101
  throw new Error("Session not found or failed to stop");
15172
14102
  }
15173
- logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
14103
+ persistence.logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
15174
14104
  return { message: "Session stopped" };
15175
14105
  });
15176
14106
  this.rpcHandlerManager.registerHandler("claude-fork-session", async (params) => {
@@ -15247,7 +14177,7 @@ class ApiMachineClient {
15247
14177
  const projectDirs = typeof directory === "string" ? [getProjectPath(directory)] : await listClaudeProjectDirs(getClaudeProjectsRoot());
15248
14178
  const excludeIds = [
15249
14179
  ...Array.isArray(exclude) ? exclude.filter((id) => typeof id === "string" && UUID_RE.test(id)) : [],
15250
- ...readTrackedClaudeSessionIds()
14180
+ ...persistence.readTrackedClaudeSessionIds()
15251
14181
  ];
15252
14182
  const result = await listClaudeSessionHistory({
15253
14183
  projectDirs,
@@ -15318,9 +14248,9 @@ class ApiMachineClient {
15318
14248
  }));
15319
14249
  });
15320
14250
  this.rpcHandlerManager.registerHandler("stop-daemon", () => {
15321
- logger.debug("[API MACHINE] Received stop-daemon RPC request");
14251
+ persistence.logger.debug("[API MACHINE] Received stop-daemon RPC request");
15322
14252
  setTimeout(() => {
15323
- logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
14253
+ persistence.logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
15324
14254
  requestShutdown();
15325
14255
  }, 100);
15326
14256
  return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
@@ -15403,7 +14333,7 @@ class ApiMachineClient {
15403
14333
  wireAutoRestoreReport() {
15404
14334
  this.webTerminal.setOnAutoRestoreSummary((line) => {
15405
14335
  sendTerminalNotification({
15406
- baseUrl: configuration.serverUrl,
14336
+ baseUrl: persistence.configuration.serverUrl,
15407
14337
  token: this.token,
15408
14338
  title: "Terminals restored",
15409
14339
  message: line,
@@ -15439,7 +14369,7 @@ class ApiMachineClient {
15439
14369
  try {
15440
14370
  this.mirrorListObserver?.(terminals);
15441
14371
  } catch (err) {
15442
- logger.debug("[API MACHINE] mirror list observer failed:", err);
14372
+ persistence.logger.debug("[API MACHINE] mirror list observer failed:", err);
15443
14373
  }
15444
14374
  this.latestTerminalList = terminals;
15445
14375
  if (!this.socket?.connected) return;
@@ -15458,9 +14388,9 @@ class ApiMachineClient {
15458
14388
  // Read at write time — same freshness rule as the list.
15459
14389
  closedTerminals: this.webTerminal.getClosedTerminals()
15460
14390
  }));
15461
- logger.debug(`[API MACHINE] Pushed terminal list (${list.length} terminals)`);
14391
+ persistence.logger.debug(`[API MACHINE] Pushed terminal list (${list.length} terminals)`);
15462
14392
  }).catch((err) => {
15463
- logger.debug("[API MACHINE] Terminal list push failed:", err);
14393
+ persistence.logger.debug("[API MACHINE] Terminal list push failed:", err);
15464
14394
  });
15465
14395
  }
15466
14396
  /**
@@ -15474,18 +14404,18 @@ class ApiMachineClient {
15474
14404
  const updated = handler(this.machine.metadata);
15475
14405
  const answer = await this.socket.emitWithAck("machine-update-metadata", {
15476
14406
  machineId: this.machine.id,
15477
- metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
14407
+ metadata: persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
15478
14408
  expectedVersion: this.machine.metadataVersion
15479
14409
  });
15480
14410
  if (answer.result === "success") {
15481
- this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
14411
+ this.machine.metadata = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.metadata));
15482
14412
  this.machine.metadataVersion = answer.version;
15483
14413
  rateRefusals = 0;
15484
- logger.debug("[API MACHINE] Metadata updated successfully");
14414
+ persistence.logger.debug("[API MACHINE] Metadata updated successfully");
15485
14415
  } else if (answer.result === "version-mismatch") {
15486
14416
  if (answer.version > this.machine.metadataVersion) {
15487
14417
  this.machine.metadataVersion = answer.version;
15488
- this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
14418
+ this.machine.metadata = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.metadata));
15489
14419
  }
15490
14420
  throw new Error("Metadata version mismatch");
15491
14421
  } else if (answer.result === "error") {
@@ -15496,7 +14426,7 @@ class ApiMachineClient {
15496
14426
  code: answer.error,
15497
14427
  attempt: rateRefusals,
15498
14428
  sleep: delay,
15499
- log: (message) => logger.debug(message)
14429
+ log: (message) => persistence.logger.debug(message)
15500
14430
  });
15501
14431
  }
15502
14432
  throw new Error("Machine metadata update failed");
@@ -15513,18 +14443,18 @@ class ApiMachineClient {
15513
14443
  const updated = handler(this.machine.daemonState);
15514
14444
  const answer = await this.socket.emitWithAck("machine-update-state", {
15515
14445
  machineId: this.machine.id,
15516
- daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
14446
+ daemonState: persistence.encodeBase64(persistence.encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
15517
14447
  expectedVersion: this.machine.daemonStateVersion
15518
14448
  });
15519
14449
  if (answer.result === "success") {
15520
- this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
14450
+ this.machine.daemonState = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.daemonState));
15521
14451
  this.machine.daemonStateVersion = answer.version;
15522
14452
  rateRefusals = 0;
15523
- logger.debug("[API MACHINE] Daemon state updated successfully");
14453
+ persistence.logger.debug("[API MACHINE] Daemon state updated successfully");
15524
14454
  } else if (answer.result === "version-mismatch") {
15525
14455
  if (answer.version > this.machine.daemonStateVersion) {
15526
14456
  this.machine.daemonStateVersion = answer.version;
15527
- this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
14457
+ this.machine.daemonState = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(answer.daemonState));
15528
14458
  }
15529
14459
  throw new Error("Daemon state version mismatch");
15530
14460
  } else if (answer.result === "error") {
@@ -15535,7 +14465,7 @@ class ApiMachineClient {
15535
14465
  code: answer.error,
15536
14466
  attempt: rateRefusals,
15537
14467
  sleep: delay,
15538
- log: (message) => logger.debug(message)
14468
+ log: (message) => persistence.logger.debug(message)
15539
14469
  });
15540
14470
  }
15541
14471
  throw new Error("Daemon state update failed");
@@ -15543,8 +14473,8 @@ class ApiMachineClient {
15543
14473
  });
15544
14474
  }
15545
14475
  connect() {
15546
- const serverUrl = configuration.serverUrl.replace(/^http/, "ws");
15547
- logger.debug(`[API MACHINE] Connecting to ${serverUrl}`);
14476
+ const serverUrl = persistence.configuration.serverUrl.replace(/^http/, "ws");
14477
+ persistence.logger.debug(`[API MACHINE] Connecting to ${serverUrl}`);
15548
14478
  this.socket = this.createControlSocket();
15549
14479
  const controlSocket = this.socket;
15550
14480
  this.bindRealtimeHandlers(controlSocket, "control");
@@ -15553,7 +14483,7 @@ class ApiMachineClient {
15553
14483
  this.activateControlSocket(controlSocket);
15554
14484
  });
15555
14485
  controlSocket.on("disconnect", (reason) => {
15556
- logger.debug(`[API MACHINE] Disconnected from server \u2014 reason: ${reason}`);
14486
+ persistence.logger.debug(`[API MACHINE] Disconnected from server \u2014 reason: ${reason}`);
15557
14487
  this.rpcHandlerManager.onSocketDisconnect(controlSocket);
15558
14488
  if (!this.relaySocket?.connected) this.webTerminal.resetSubscribers();
15559
14489
  this.stopKeepAlive();
@@ -15562,14 +14492,14 @@ class ApiMachineClient {
15562
14492
  this.bindControlDataHandlers(controlSocket);
15563
14493
  }
15564
14494
  createControlSocket(handover) {
15565
- const serverUrl = configuration.serverUrl.replace(/^http/, "ws");
14495
+ const serverUrl = persistence.configuration.serverUrl.replace(/^http/, "ws");
15566
14496
  return socket_ioClient.io(serverUrl, {
15567
14497
  transports: ["websocket"],
15568
14498
  auth: {
15569
14499
  token: this.token,
15570
14500
  clientType: "machine-scoped",
15571
14501
  machineId: this.machine.id,
15572
- happyClient: `cli-daemon/${configuration.currentCliVersion}`,
14502
+ happyClient: `cli-daemon/${persistence.configuration.currentCliVersion}`,
15573
14503
  ...handover ? { handoverEpoch: handover.epoch } : {}
15574
14504
  },
15575
14505
  ...handover ? { query: { vh_slot: handover.candidateSlot }, forceNew: true } : {},
@@ -15579,7 +14509,7 @@ class ApiMachineClient {
15579
14509
  });
15580
14510
  }
15581
14511
  activateControlSocket(socket, rpcAlreadyRegistered = false) {
15582
- logger.debug("[API MACHINE] Connected to server");
14512
+ persistence.logger.debug("[API MACHINE] Connected to server");
15583
14513
  if (this.reconnectInterval) {
15584
14514
  clearInterval(this.reconnectInterval);
15585
14515
  this.reconnectInterval = null;
@@ -15625,25 +14555,25 @@ class ApiMachineClient {
15625
14555
  if (data.body.t === "update-machine" && data.body.machineId === this.machine.id) {
15626
14556
  const update = data.body;
15627
14557
  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));
14558
+ persistence.logger.debug("[API MACHINE] Received external metadata update");
14559
+ this.machine.metadata = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(update.metadata.value));
15630
14560
  this.machine.metadataVersion = update.metadata.version;
15631
14561
  }
15632
14562
  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));
14563
+ persistence.logger.debug("[API MACHINE] Received external daemon state update");
14564
+ this.machine.daemonState = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(update.daemonState.value));
15635
14565
  this.machine.daemonStateVersion = update.daemonState.version;
15636
14566
  }
15637
14567
  } else {
15638
- logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
14568
+ persistence.logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
15639
14569
  }
15640
14570
  });
15641
14571
  socket.on("connect_error", (error) => {
15642
- logger.debug(`[API MACHINE] Connection error: ${error.message}`);
14572
+ persistence.logger.debug(`[API MACHINE] Connection error: ${error.message}`);
15643
14573
  if (this.socket === socket) this.startSmartReconnect();
15644
14574
  });
15645
14575
  socket.io.on("error", (error) => {
15646
- logger.debug("[API MACHINE] Socket error:", error);
14576
+ persistence.logger.debug("[API MACHINE] Socket error:", error);
15647
14577
  });
15648
14578
  socket.on("server-draining", (data) => {
15649
14579
  if (this.socket !== socket) return;
@@ -15713,7 +14643,7 @@ class ApiMachineClient {
15713
14643
  this.rpcHandlerManager.onSocketDisconnect(candidate);
15714
14644
  candidate.close();
15715
14645
  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}`);
14646
+ persistence.logger.debug(`[API MACHINE] Release handover failed: ${error instanceof Error ? error.message : error}`);
15717
14647
  }
15718
14648
  }
15719
14649
  startKeepAlive() {
@@ -15724,7 +14654,7 @@ class ApiMachineClient {
15724
14654
  time: Date.now()
15725
14655
  };
15726
14656
  if (process.env.DEBUG) {
15727
- logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
14657
+ persistence.logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
15728
14658
  }
15729
14659
  this.socket.emit("machine-alive", payload);
15730
14660
  void this.refreshRelayConnection();
@@ -15746,15 +14676,15 @@ class ApiMachineClient {
15746
14676
  cliAvailability: newAvailability,
15747
14677
  resumeSupport: { ...newResumeSupport, rpcAvailable: !!this.resumeSessionHandler }
15748
14678
  })).catch((err) => {
15749
- logger.debug("[API MACHINE] Failed to update machine capabilities:", err);
14679
+ persistence.logger.debug("[API MACHINE] Failed to update machine capabilities:", err);
15750
14680
  });
15751
14681
  }
15752
14682
  }, 2e4);
15753
- logger.debug("[API MACHINE] Keep-alive started (20s interval)");
14683
+ persistence.logger.debug("[API MACHINE] Keep-alive started (20s interval)");
15754
14684
  }
15755
14685
  bindRpcRequestHandler(socket, transport) {
15756
14686
  socket.on("rpc-request", async (data, callback) => {
15757
- logger.debugLargeJson(`[API MACHINE] Received RPC request via ${transport}:`, data);
14687
+ persistence.logger.debugLargeJson(`[API MACHINE] Received RPC request via ${transport}:`, data);
15758
14688
  callback(await this.rpcHandlerManager.handleRequest(data));
15759
14689
  });
15760
14690
  }
@@ -15762,7 +14692,7 @@ class ApiMachineClient {
15762
14692
  socket.on("terminal-input", (data) => {
15763
14693
  let payload = data.data;
15764
14694
  if (data.enc) {
15765
- const dec = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(data.data));
14695
+ const dec = persistence.decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, persistence.decodeBase64(data.data));
15766
14696
  if (typeof dec !== "string") return;
15767
14697
  payload = dec;
15768
14698
  }
@@ -15773,7 +14703,7 @@ class ApiMachineClient {
15773
14703
  socket.on("session-archive", (data) => {
15774
14704
  if (typeof data?.sessionId !== "string") return;
15775
14705
  const stopped = this.stopSessionHandler?.(data.sessionId) ?? false;
15776
- logger.debug(`[API MACHINE] Server archived session ${data.sessionId}; local process stopped=${stopped}`);
14706
+ persistence.logger.debug(`[API MACHINE] Server archived session ${data.sessionId}; local process stopped=${stopped}`);
15777
14707
  });
15778
14708
  }
15779
14709
  unbindMachineCommandHandlers(socket) {
@@ -15793,12 +14723,12 @@ class ApiMachineClient {
15793
14723
  if (tracked.length === 0) return;
15794
14724
  try {
15795
14725
  for (let offset = 0; offset < tracked.length; offset += 500) {
15796
- const response = await fetch(`${configuration.serverUrl}/v1/sessions/archive-status`, {
14726
+ const response = await fetch(`${persistence.configuration.serverUrl}/v1/sessions/archive-status`, {
15797
14727
  method: "POST",
15798
14728
  headers: {
15799
14729
  "Authorization": `Bearer ${this.token}`,
15800
14730
  "Content-Type": "application/json",
15801
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
14731
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
15802
14732
  },
15803
14733
  body: JSON.stringify({ sessionIds: tracked.slice(offset, offset + 500) })
15804
14734
  });
@@ -15811,14 +14741,14 @@ class ApiMachineClient {
15811
14741
  }
15812
14742
  }
15813
14743
  } catch (error) {
15814
- logger.debug(`[API MACHINE] Archived-session reconciliation failed: ${error instanceof Error ? error.message : error}`);
14744
+ persistence.logger.debug(`[API MACHINE] Archived-session reconciliation failed: ${error instanceof Error ? error.message : error}`);
15815
14745
  }
15816
14746
  }
15817
14747
  async refreshRelayConnection() {
15818
14748
  if (this.relayRefreshInFlight) return this.relayRefreshInFlight;
15819
14749
  this.relayRefreshInFlight = (async () => {
15820
14750
  const selected = await discoverAndClaimRelay({
15821
- controlUrl: configuration.serverUrl,
14751
+ controlUrl: persistence.configuration.serverUrl,
15822
14752
  token: this.token,
15823
14753
  machineId: this.machine.id,
15824
14754
  connectedRelayId: this.relaySocket?.connected ? this.relayAssignment?.relayId : void 0,
@@ -15832,7 +14762,7 @@ class ApiMachineClient {
15832
14762
  const previousUrl = this.relayAssignment?.url;
15833
14763
  this.relayAssignment = selected.assignment;
15834
14764
  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`);
14765
+ persistence.logger.debug(`[API MACHINE] Regional relay selected: ${selected.assignment.relayId} (${selected.assignment.region}), RTT ${probe ? Math.round(probe.rttMs) : "unknown"}ms`);
15836
14766
  if (this.relaySocket && previousUrl === selected.assignment.url) {
15837
14767
  this.relaySocket.auth = { token: selected.assignment.token };
15838
14768
  if (this.relaySocket.connected) return;
@@ -15856,7 +14786,7 @@ class ApiMachineClient {
15856
14786
  this.rpcHandlerManager.onSocketConnect(relaySocket);
15857
14787
  this.syncResumeSessionRpcRegistration();
15858
14788
  this.syncRestartSessionRpcRegistration();
15859
- logger.debug(`[API MACHINE] Connected to regional relay ${selected.assignment.relayId}`);
14789
+ persistence.logger.debug(`[API MACHINE] Connected to regional relay ${selected.assignment.relayId}`);
15860
14790
  });
15861
14791
  relaySocket.on("disconnect", () => {
15862
14792
  this.relaySwitchTracker = null;
@@ -15877,14 +14807,14 @@ class ApiMachineClient {
15877
14807
  return;
15878
14808
  }
15879
14809
  if (!shouldReconnect()) {
15880
- logger.debug("[API MACHINE] Still not ready to reconnect");
14810
+ persistence.logger.debug("[API MACHINE] Still not ready to reconnect");
15881
14811
  return;
15882
14812
  }
15883
- logger.debug("[API MACHINE] Attempting reconnect");
14813
+ persistence.logger.debug("[API MACHINE] Attempting reconnect");
15884
14814
  this.socket.connect();
15885
14815
  }, 3e3);
15886
14816
  if (shouldReconnect()) {
15887
- logger.debug("[API MACHINE] Network up + lid open \u2014 reconnecting in 1s");
14817
+ persistence.logger.debug("[API MACHINE] Network up + lid open \u2014 reconnecting in 1s");
15888
14818
  setTimeout(() => {
15889
14819
  if (!this.socket.connected) this.socket.connect();
15890
14820
  }, 1e3);
@@ -15894,11 +14824,11 @@ class ApiMachineClient {
15894
14824
  if (this.keepAliveInterval) {
15895
14825
  clearInterval(this.keepAliveInterval);
15896
14826
  this.keepAliveInterval = null;
15897
- logger.debug("[API MACHINE] Keep-alive stopped");
14827
+ persistence.logger.debug("[API MACHINE] Keep-alive stopped");
15898
14828
  }
15899
14829
  }
15900
14830
  shutdown() {
15901
- logger.debug("[API MACHINE] Shutting down");
14831
+ persistence.logger.debug("[API MACHINE] Shutting down");
15902
14832
  this.webTerminal.stopListTracking();
15903
14833
  this.stopKeepAlive();
15904
14834
  if (this.reconnectInterval) {
@@ -15907,13 +14837,13 @@ class ApiMachineClient {
15907
14837
  }
15908
14838
  if (this.socket) {
15909
14839
  this.socket.close();
15910
- logger.debug("[API MACHINE] Socket closed");
14840
+ persistence.logger.debug("[API MACHINE] Socket closed");
15911
14841
  }
15912
14842
  if (this.relaySocket) {
15913
14843
  this.rpcHandlerManager.onSocketDisconnect(this.relaySocket);
15914
14844
  this.relaySocket.close();
15915
14845
  this.relaySocket = null;
15916
- logger.debug("[API MACHINE] Regional relay socket closed");
14846
+ persistence.logger.debug("[API MACHINE] Regional relay socket closed");
15917
14847
  }
15918
14848
  }
15919
14849
  }
@@ -15983,24 +14913,24 @@ class PushNotificationClient {
15983
14913
  headers: {
15984
14914
  "Authorization": `Bearer ${this.token}`,
15985
14915
  "Content-Type": "application/json",
15986
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
14916
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
15987
14917
  }
15988
14918
  }
15989
14919
  );
15990
- logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
14920
+ persistence.logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
15991
14921
  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()}`);
14922
+ persistence.logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
15993
14923
  });
15994
14924
  return response.data.tokens;
15995
14925
  } catch (error) {
15996
- logger.debug(`[PUSH] [ERROR] Failed to fetch push tokens (attempt ${attempt}/${maxAttempts}):`, error);
14926
+ persistence.logger.debug(`[PUSH] [ERROR] Failed to fetch push tokens (attempt ${attempt}/${maxAttempts}):`, error);
15997
14927
  if (attempt < maxAttempts) {
15998
14928
  const delay = 1e3 * Math.pow(2, attempt - 1);
15999
14929
  await new Promise((resolve) => setTimeout(resolve, delay));
16000
14930
  }
16001
14931
  }
16002
14932
  }
16003
- logger.debug("[PUSH] [ERROR] All push token fetch attempts failed");
14933
+ persistence.logger.debug("[PUSH] [ERROR] All push token fetch attempts failed");
16004
14934
  return [];
16005
14935
  }
16006
14936
  /**
@@ -16008,7 +14938,7 @@ class PushNotificationClient {
16008
14938
  * @param messages - Array of push messages to send
16009
14939
  */
16010
14940
  async sendPushNotifications(messages) {
16011
- logger.debug(`Sending ${messages.length} push notifications`);
14941
+ persistence.logger.debug(`Sending ${messages.length} push notifications`);
16012
14942
  const validMessages = messages.filter((message) => {
16013
14943
  if (Array.isArray(message.to)) {
16014
14944
  return message.to.every((token) => expoServerSdk.Expo.isExpoPushToken(token));
@@ -16016,7 +14946,7 @@ class PushNotificationClient {
16016
14946
  return expoServerSdk.Expo.isExpoPushToken(message.to);
16017
14947
  });
16018
14948
  if (validMessages.length === 0) {
16019
- logger.debug("No valid Expo push tokens found");
14949
+ persistence.logger.debug("No valid Expo push tokens found");
16020
14950
  return;
16021
14951
  }
16022
14952
  const chunks = this.expo.chunkPushNotifications(validMessages);
@@ -16030,7 +14960,7 @@ class PushNotificationClient {
16030
14960
  const errors = ticketChunk.filter((ticket) => ticket.status === "error");
16031
14961
  if (errors.length > 0) {
16032
14962
  const errorDetails = errors.map((e) => ({ message: e.message, details: e.details }));
16033
- logger.debug("[PUSH] Some notifications failed:", errorDetails);
14963
+ persistence.logger.debug("[PUSH] Some notifications failed:", errorDetails);
16034
14964
  }
16035
14965
  if (errors.length === ticketChunk.length) {
16036
14966
  throw new Error("All push notifications in chunk failed");
@@ -16039,7 +14969,7 @@ class PushNotificationClient {
16039
14969
  } catch (error) {
16040
14970
  const elapsed = Date.now() - startTime;
16041
14971
  if (elapsed >= timeout) {
16042
- logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
14972
+ persistence.logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
16043
14973
  break;
16044
14974
  }
16045
14975
  attempt++;
@@ -16047,13 +14977,13 @@ class PushNotificationClient {
16047
14977
  const remainingTime = timeout - elapsed;
16048
14978
  const waitTime = Math.min(delay, remainingTime);
16049
14979
  if (waitTime > 0) {
16050
- logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
14980
+ persistence.logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
16051
14981
  await new Promise((resolve) => setTimeout(resolve, waitTime));
16052
14982
  }
16053
14983
  }
16054
14984
  }
16055
14985
  }
16056
- logger.debug(`Push notifications sent successfully`);
14986
+ persistence.logger.debug(`Push notifications sent successfully`);
16057
14987
  }
16058
14988
  /**
16059
14989
  * Send a push notification to all registered devices for the user
@@ -16062,25 +14992,25 @@ class PushNotificationClient {
16062
14992
  * @param data - Additional data to send with the notification
16063
14993
  */
16064
14994
  sendToAllDevices(title, body, data) {
16065
- logger.debug("[PUSH] sendToAllDevices called:", {
14995
+ persistence.logger.debug("[PUSH] sendToAllDevices called:", {
16066
14996
  title: contentLogMetadata(title),
16067
14997
  body: contentLogMetadata(body),
16068
14998
  dataKeyCount: data ? Object.keys(data).length : 0
16069
14999
  });
16070
15000
  (async () => {
16071
15001
  try {
16072
- logger.debug("[PUSH] Fetching push tokens...");
15002
+ persistence.logger.debug("[PUSH] Fetching push tokens...");
16073
15003
  const tokens = await this.fetchPushTokens();
16074
- logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
15004
+ persistence.logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
16075
15005
  tokens.forEach((token, index) => {
16076
- logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
15006
+ persistence.logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
16077
15007
  });
16078
15008
  if (tokens.length === 0) {
16079
- logger.debug("No push tokens found for user");
15009
+ persistence.logger.debug("No push tokens found for user");
16080
15010
  return;
16081
15011
  }
16082
15012
  const messages = tokens.map((token, index) => {
16083
- logger.debug(`[PUSH] Creating message ${index + 1} for token`);
15013
+ persistence.logger.debug(`[PUSH] Creating message ${index + 1} for token`);
16084
15014
  return {
16085
15015
  to: token.token,
16086
15016
  title,
@@ -16093,11 +15023,11 @@ class PushNotificationClient {
16093
15023
  priority: "high"
16094
15024
  };
16095
15025
  });
16096
- logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
15026
+ persistence.logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
16097
15027
  await this.sendPushNotifications(messages);
16098
- logger.debug("[PUSH] Push notifications sent successfully");
15028
+ persistence.logger.debug("[PUSH] Push notifications sent successfully");
16099
15029
  } catch (error) {
16100
- logger.debug("[PUSH] Error sending to all devices:", error);
15030
+ persistence.logger.debug("[PUSH] Error sending to all devices:", error);
16101
15031
  }
16102
15032
  })();
16103
15033
  }
@@ -16119,7 +15049,7 @@ class PushNotificationClient {
16119
15049
  };
16120
15050
  const sessionId = typeof params.data?.sessionId === "string" ? params.data.sessionId : null;
16121
15051
  if (!sessionId) {
16122
- logger.debug("[PUSH] sendSessionNotification: missing sessionId, falling back to direct send");
15052
+ persistence.logger.debug("[PUSH] sendSessionNotification: missing sessionId, falling back to direct send");
16123
15053
  this.sendToAllDevices(title, body, payloadData);
16124
15054
  return;
16125
15055
  }
@@ -16137,14 +15067,14 @@ class PushNotificationClient {
16137
15067
  headers: {
16138
15068
  "Authorization": `Bearer ${this.token}`,
16139
15069
  "Content-Type": "application/json",
16140
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
15070
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
16141
15071
  },
16142
15072
  timeout: 15e3
16143
15073
  }
16144
15074
  );
16145
- logger.debug(`[PUSH] sendSessionNotification dispatched via server (kind=${params.kind})`);
15075
+ persistence.logger.debug(`[PUSH] sendSessionNotification dispatched via server (kind=${params.kind})`);
16146
15076
  } catch (error) {
16147
- logger.debug("[PUSH] sendSessionNotification failed:", error);
15077
+ persistence.logger.debug("[PUSH] sendSessionNotification failed:", error);
16148
15078
  }
16149
15079
  })();
16150
15080
  }
@@ -16162,7 +15092,7 @@ function startOfflineReconnection(config) {
16162
15092
  validateStatus: (status) => status < 500,
16163
15093
  // 4xx = server is up, 5xx = server error
16164
15094
  headers: {
16165
- "X-Happy-Client": `cli-daemon/${configuration.currentCliVersion}`
15095
+ "X-Happy-Client": `cli-daemon/${persistence.configuration.currentCliVersion}`
16166
15096
  }
16167
15097
  });
16168
15098
  };
@@ -16178,16 +15108,16 @@ function startOfflineReconnection(config) {
16178
15108
  if (cancelled) return;
16179
15109
  reconnected = true;
16180
15110
  config.onNotify("\u2705 Reconnected! Session syncing in background.");
16181
- logger.debug("[OfflineReconnection] Successfully reconnected");
15111
+ persistence.logger.debug("[OfflineReconnection] Successfully reconnected");
16182
15112
  } catch (e) {
16183
15113
  if (axios.isAxiosError(e) && e.response?.status === 401) {
16184
- logger.debug("[OfflineReconnection] Authentication error, stopping retries");
15114
+ persistence.logger.debug("[OfflineReconnection] Authentication error, stopping retries");
16185
15115
  config.onNotify("\u274C Authentication failed. Please re-authenticate with `very-happy auth`.");
16186
15116
  return;
16187
15117
  }
16188
15118
  failureCount++;
16189
15119
  const delay = retryDelayMs(failureCount);
16190
- logger.debug(`[OfflineReconnection] Attempt ${failureCount} failed, retrying in ${delay}ms`);
15120
+ persistence.logger.debug(`[OfflineReconnection] Attempt ${failureCount} failed, retrying in ${delay}ms`);
16191
15121
  if (!cancelled) {
16192
15122
  timeoutId = setTimeout(attemptReconnect, delay);
16193
15123
  }
@@ -16281,7 +15211,7 @@ const connectionState = new OfflineState();
16281
15211
 
16282
15212
  function accountBoxPublicKeyFromCredentials(credential) {
16283
15213
  if (credential.encryption.type === "legacy") {
16284
- return libsodiumPublicKeyFromSecretKey(credential.encryption.secret);
15214
+ return persistence.libsodiumPublicKeyFromSecretKey(credential.encryption.secret);
16285
15215
  }
16286
15216
  return credential.encryption.publicKey;
16287
15217
  }
@@ -16348,7 +15278,7 @@ class NotificationProducer {
16348
15278
  const now = Date.now();
16349
15279
  const last = this.lastEmittedAt.get(repeatKey);
16350
15280
  if (last !== void 0 && now - last < DEDUP_WINDOW_MS) {
16351
- logger.debug(`[notif] Suppressing duplicate ${notifType} for ${this.sessionId}`);
15281
+ persistence.logger.debug(`[notif] Suppressing duplicate ${notifType} for ${this.sessionId}`);
16352
15282
  return;
16353
15283
  }
16354
15284
  this.lastEmittedAt.set(repeatKey, now);
@@ -16361,10 +15291,10 @@ class NotificationProducer {
16361
15291
  let enc;
16362
15292
  try {
16363
15293
  const plaintext = new TextEncoder().encode(JSON.stringify(body));
16364
- const bundle = libsodiumEncryptForPublicKey(plaintext, this.accountBoxPubKey);
16365
- enc = encodeBase64(bundle);
15294
+ const bundle = persistence.libsodiumEncryptForPublicKey(plaintext, this.accountBoxPubKey);
15295
+ enc = persistence.encodeBase64(bundle);
16366
15296
  } catch (err) {
16367
- logger.debug("[notif] Failed to encrypt notification body:", err);
15297
+ persistence.logger.debug("[notif] Failed to encrypt notification body:", err);
16368
15298
  return;
16369
15299
  }
16370
15300
  void this.post(notifType, enc, repeatKey);
@@ -16372,7 +15302,7 @@ class NotificationProducer {
16372
15302
  async post(notifType, enc, repeatKey) {
16373
15303
  try {
16374
15304
  await axios.post(
16375
- `${configuration.serverUrl}/v1/feed`,
15305
+ `${persistence.configuration.serverUrl}/v1/feed`,
16376
15306
  {
16377
15307
  notifType,
16378
15308
  sessionId: this.sessionId,
@@ -16383,14 +15313,14 @@ class NotificationProducer {
16383
15313
  headers: {
16384
15314
  "Authorization": `Bearer ${this.token}`,
16385
15315
  "Content-Type": "application/json",
16386
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15316
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16387
15317
  },
16388
15318
  timeout: 5e3
16389
15319
  }
16390
15320
  );
16391
- logger.debug(`[notif] Posted ${notifType} for session ${this.sessionId}`);
15321
+ persistence.logger.debug(`[notif] Posted ${notifType} for session ${this.sessionId}`);
16392
15322
  } catch (err) {
16393
- logger.debug(`[notif] Failed to post ${notifType}:`, err);
15323
+ persistence.logger.debug(`[notif] Failed to post ${notifType}:`, err);
16394
15324
  }
16395
15325
  }
16396
15326
  /** Short, human-friendly label for the project/session for use in titles. */
@@ -16428,7 +15358,7 @@ class ApiClient {
16428
15358
  pushClient;
16429
15359
  constructor(credential) {
16430
15360
  this.credential = credential;
16431
- this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
15361
+ this.pushClient = new PushNotificationClient(credential.token, persistence.configuration.serverUrl);
16432
15362
  }
16433
15363
  /**
16434
15364
  * Create a new session or load existing one with the given tag
@@ -16438,9 +15368,9 @@ class ApiClient {
16438
15368
  let encryptionKey;
16439
15369
  let encryptionVariant;
16440
15370
  if (this.credential.encryption.type === "dataKey") {
16441
- encryptionKey = getRandomBytes(32);
15371
+ encryptionKey = persistence.getRandomBytes(32);
16442
15372
  encryptionVariant = "dataKey";
16443
- let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
15373
+ let encryptedDataKey = persistence.libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
16444
15374
  dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
16445
15375
  dataEncryptionKey.set([0], 0);
16446
15376
  dataEncryptionKey.set(encryptedDataKey, 1);
@@ -16450,38 +15380,38 @@ class ApiClient {
16450
15380
  }
16451
15381
  try {
16452
15382
  const response = await axios.post(
16453
- `${configuration.serverUrl}/v1/sessions`,
15383
+ `${persistence.configuration.serverUrl}/v1/sessions`,
16454
15384
  {
16455
15385
  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
15386
+ metadata: persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.metadata)),
15387
+ agentState: opts.state ? persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
15388
+ dataEncryptionKey: dataEncryptionKey ? persistence.encodeBase64(dataEncryptionKey) : null
16459
15389
  },
16460
15390
  {
16461
15391
  headers: {
16462
15392
  "Authorization": `Bearer ${this.credential.token}`,
16463
15393
  "Content-Type": "application/json",
16464
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15394
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16465
15395
  },
16466
15396
  timeout: 6e4
16467
15397
  // 1 minute timeout for very bad network connections
16468
15398
  }
16469
15399
  );
16470
- logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
15400
+ persistence.logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
16471
15401
  let raw = response.data.session;
16472
15402
  let session = {
16473
15403
  id: raw.id,
16474
15404
  seq: raw.seq,
16475
- metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
15405
+ metadata: persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.metadata)),
16476
15406
  metadataVersion: raw.metadataVersion,
16477
- agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
15407
+ agentState: raw.agentState ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.agentState)) : null,
16478
15408
  agentStateVersion: raw.agentStateVersion,
16479
15409
  encryptionKey,
16480
15410
  encryptionVariant
16481
15411
  };
16482
15412
  return session;
16483
15413
  } catch (error) {
16484
- logger.debug("[API] [ERROR] Failed to get or create session:", error);
15414
+ persistence.logger.debug("[API] [ERROR] Failed to get or create session:", error);
16485
15415
  if (error && typeof error === "object" && "code" in error) {
16486
15416
  const errorCode = error.code;
16487
15417
  if (isNetworkError(errorCode)) {
@@ -16489,7 +15419,7 @@ class ApiClient {
16489
15419
  operation: "Session creation",
16490
15420
  caller: "api.getOrCreateSession",
16491
15421
  errorCode,
16492
- url: `${configuration.serverUrl}/v1/sessions`
15422
+ url: `${persistence.configuration.serverUrl}/v1/sessions`
16493
15423
  });
16494
15424
  return null;
16495
15425
  }
@@ -16499,7 +15429,7 @@ class ApiClient {
16499
15429
  connectionState.fail({
16500
15430
  operation: "Session creation",
16501
15431
  errorCode: "404",
16502
- url: `${configuration.serverUrl}/v1/sessions`
15432
+ url: `${persistence.configuration.serverUrl}/v1/sessions`
16503
15433
  });
16504
15434
  return null;
16505
15435
  }
@@ -16509,7 +15439,7 @@ class ApiClient {
16509
15439
  connectionState.fail({
16510
15440
  operation: "Session creation",
16511
15441
  errorCode: String(status),
16512
- url: `${configuration.serverUrl}/v1/sessions`,
15442
+ url: `${persistence.configuration.serverUrl}/v1/sessions`,
16513
15443
  details: ["Server encountered an error, will retry automatically"]
16514
15444
  });
16515
15445
  return null;
@@ -16529,7 +15459,7 @@ class ApiClient {
16529
15459
  if (this.credential.encryption.type === "dataKey") {
16530
15460
  encryptionVariant = "dataKey";
16531
15461
  encryptionKey = this.credential.encryption.machineKey;
16532
- let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
15462
+ let encryptedDataKey = persistence.libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
16533
15463
  dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
16534
15464
  dataEncryptionKey.set([0], 0);
16535
15465
  dataEncryptionKey.set(encryptedDataKey, 1);
@@ -16548,32 +15478,32 @@ class ApiClient {
16548
15478
  });
16549
15479
  try {
16550
15480
  const response = await axios.post(
16551
- `${configuration.serverUrl}/v1/machines`,
15481
+ `${persistence.configuration.serverUrl}/v1/machines`,
16552
15482
  {
16553
15483
  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
15484
+ metadata: persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.metadata)),
15485
+ daemonState: opts.daemonState ? persistence.encodeBase64(persistence.encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
15486
+ dataEncryptionKey: dataEncryptionKey ? persistence.encodeBase64(dataEncryptionKey) : void 0
16557
15487
  },
16558
15488
  {
16559
15489
  headers: {
16560
15490
  "Authorization": `Bearer ${this.credential.token}`,
16561
15491
  "Content-Type": "application/json",
16562
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15492
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16563
15493
  },
16564
15494
  timeout: 6e4
16565
15495
  // 1 minute timeout for very bad network connections
16566
15496
  }
16567
15497
  );
16568
15498
  const raw = response.data.machine;
16569
- logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
15499
+ persistence.logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
16570
15500
  const machine = {
16571
15501
  id: raw.id,
16572
15502
  encryptionKey,
16573
15503
  encryptionVariant,
16574
- metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
15504
+ metadata: raw.metadata ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.metadata)) : null,
16575
15505
  metadataVersion: raw.metadataVersion || 0,
16576
- daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
15506
+ daemonState: raw.daemonState ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(raw.daemonState)) : null,
16577
15507
  daemonStateVersion: raw.daemonStateVersion || 0
16578
15508
  };
16579
15509
  return machine;
@@ -16583,7 +15513,7 @@ class ApiClient {
16583
15513
  operation: "Machine registration",
16584
15514
  caller: "api.getOrCreateMachine",
16585
15515
  errorCode: error.code,
16586
- url: `${configuration.serverUrl}/v1/machines`
15516
+ url: `${persistence.configuration.serverUrl}/v1/machines`
16587
15517
  });
16588
15518
  return createMinimalMachine();
16589
15519
  }
@@ -16614,7 +15544,7 @@ class ApiClient {
16614
15544
  connectionState.fail({
16615
15545
  operation: "Machine registration",
16616
15546
  errorCode: "429",
16617
- url: `${configuration.serverUrl}/v1/machines`,
15547
+ url: `${persistence.configuration.serverUrl}/v1/machines`,
16618
15548
  details: ["Relay write quota is cooling down; realtime connection will retry automatically"]
16619
15549
  });
16620
15550
  return createMinimalMachine();
@@ -16623,7 +15553,7 @@ class ApiClient {
16623
15553
  connectionState.fail({
16624
15554
  operation: "Machine registration",
16625
15555
  errorCode: String(status),
16626
- url: `${configuration.serverUrl}/v1/machines`,
15556
+ url: `${persistence.configuration.serverUrl}/v1/machines`,
16627
15557
  details: ["Server encountered an error, will retry automatically"]
16628
15558
  });
16629
15559
  return createMinimalMachine();
@@ -16632,7 +15562,7 @@ class ApiClient {
16632
15562
  connectionState.fail({
16633
15563
  operation: "Machine registration",
16634
15564
  errorCode: "404",
16635
- url: `${configuration.serverUrl}/v1/machines`
15565
+ url: `${persistence.configuration.serverUrl}/v1/machines`
16636
15566
  });
16637
15567
  return createMinimalMachine();
16638
15568
  }
@@ -16671,11 +15601,11 @@ class ApiClient {
16671
15601
  async kvGet(key) {
16672
15602
  try {
16673
15603
  const response = await axios.get(
16674
- `${configuration.serverUrl}/v1/kv/${encodeURIComponent(key)}`,
15604
+ `${persistence.configuration.serverUrl}/v1/kv/${encodeURIComponent(key)}`,
16675
15605
  {
16676
15606
  headers: {
16677
15607
  "Authorization": `Bearer ${this.credential.token}`,
16678
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15608
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16679
15609
  },
16680
15610
  timeout: 5e3,
16681
15611
  validateStatus: (s) => s === 200 || s === 404
@@ -16684,7 +15614,7 @@ class ApiClient {
16684
15614
  if (response.status === 404) return null;
16685
15615
  return response.data;
16686
15616
  } catch (error) {
16687
- logger.debug(`[API] kvGet(${key}) failed:`, error);
15617
+ persistence.logger.debug(`[API] kvGet(${key}) failed:`, error);
16688
15618
  return null;
16689
15619
  }
16690
15620
  }
@@ -16695,7 +15625,7 @@ class ApiClient {
16695
15625
  async registerVendorToken(vendor, apiKey) {
16696
15626
  try {
16697
15627
  const response = await axios.post(
16698
- `${configuration.serverUrl}/v1/connect/${vendor}/register`,
15628
+ `${persistence.configuration.serverUrl}/v1/connect/${vendor}/register`,
16699
15629
  {
16700
15630
  token: JSON.stringify(apiKey)
16701
15631
  },
@@ -16703,7 +15633,7 @@ class ApiClient {
16703
15633
  headers: {
16704
15634
  "Authorization": `Bearer ${this.credential.token}`,
16705
15635
  "Content-Type": "application/json",
16706
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15636
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16707
15637
  },
16708
15638
  timeout: 5e3
16709
15639
  }
@@ -16711,9 +15641,9 @@ class ApiClient {
16711
15641
  if (response.status !== 200 && response.status !== 201) {
16712
15642
  throw new Error(`Server returned status ${response.status}`);
16713
15643
  }
16714
- logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
15644
+ persistence.logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
16715
15645
  } catch (error) {
16716
- logger.debug(`[API] [ERROR] Failed to register vendor token:`, redactLogValue(error));
15646
+ persistence.logger.debug(`[API] [ERROR] Failed to register vendor token:`, persistence.redactLogValue(error));
16717
15647
  throw new Error(`Failed to register vendor token: ${error instanceof Error ? error.message : "Unknown error"}`);
16718
15648
  }
16719
15649
  }
@@ -16724,24 +15654,24 @@ class ApiClient {
16724
15654
  async getVendorToken(vendor) {
16725
15655
  try {
16726
15656
  const response = await axios.get(
16727
- `${configuration.serverUrl}/v1/connect/${vendor}/token`,
15657
+ `${persistence.configuration.serverUrl}/v1/connect/${vendor}/token`,
16728
15658
  {
16729
15659
  headers: {
16730
15660
  "Authorization": `Bearer ${this.credential.token}`,
16731
15661
  "Content-Type": "application/json",
16732
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15662
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16733
15663
  },
16734
15664
  timeout: 5e3
16735
15665
  }
16736
15666
  );
16737
15667
  if (response.status === 404) {
16738
- logger.debug(`[API] No vendor token found for ${vendor}`);
15668
+ persistence.logger.debug(`[API] No vendor token found for ${vendor}`);
16739
15669
  return null;
16740
15670
  }
16741
15671
  if (response.status !== 200) {
16742
15672
  throw new Error(`Server returned status ${response.status}`);
16743
15673
  }
16744
- logger.debug(`[API] Raw vendor token response:`, {
15674
+ persistence.logger.debug(`[API] Raw vendor token response:`, {
16745
15675
  status: response.status,
16746
15676
  dataKeys: Object.keys(response.data || {}),
16747
15677
  hasToken: "token" in (response.data || {}),
@@ -16753,37 +15683,37 @@ class ApiClient {
16753
15683
  try {
16754
15684
  tokenData = JSON.parse(response.data.token);
16755
15685
  } catch (parseError) {
16756
- logger.debug(`[API] Failed to parse token as JSON, using as string:`, parseError);
15686
+ persistence.logger.debug(`[API] Failed to parse token as JSON, using as string:`, parseError);
16757
15687
  tokenData = response.data.token;
16758
15688
  }
16759
15689
  } else if (response.data.token !== null) {
16760
15690
  tokenData = response.data.token;
16761
15691
  } else {
16762
- logger.debug(`[API] Token is null for ${vendor}, treating as not found`);
15692
+ persistence.logger.debug(`[API] Token is null for ${vendor}, treating as not found`);
16763
15693
  return null;
16764
15694
  }
16765
15695
  } else if (response.data && typeof response.data === "object") {
16766
15696
  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`);
15697
+ persistence.logger.debug(`[API] Response contains only null token for ${vendor}, treating as not found`);
16768
15698
  return null;
16769
15699
  }
16770
15700
  tokenData = response.data;
16771
15701
  }
16772
15702
  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}`);
15703
+ persistence.logger.debug(`[API] Token data is null for ${vendor}`);
16774
15704
  return null;
16775
15705
  }
16776
- logger.debug(`[API] Vendor token for ${vendor} retrieved successfully`, {
15706
+ persistence.logger.debug(`[API] Vendor token for ${vendor} retrieved successfully`, {
16777
15707
  tokenDataType: typeof tokenData,
16778
15708
  tokenDataKeys: tokenData && typeof tokenData === "object" ? Object.keys(tokenData) : "not an object"
16779
15709
  });
16780
15710
  return tokenData;
16781
15711
  } catch (error) {
16782
15712
  if (error.response?.status === 404) {
16783
- logger.debug(`[API] No vendor token found for ${vendor}`);
15713
+ persistence.logger.debug(`[API] No vendor token found for ${vendor}`);
16784
15714
  return null;
16785
15715
  }
16786
- logger.debug(`[API] [ERROR] Failed to get vendor token:`, redactLogValue(error));
15716
+ persistence.logger.debug(`[API] [ERROR] Failed to get vendor token:`, persistence.redactLogValue(error));
16787
15717
  return null;
16788
15718
  }
16789
15719
  }
@@ -16801,19 +15731,19 @@ class ApiClient {
16801
15731
  async deactivateSession(sessionId) {
16802
15732
  try {
16803
15733
  const response = await axios.post(
16804
- `${configuration.serverUrl}/v1/sessions/${sessionId}/archive`,
15734
+ `${persistence.configuration.serverUrl}/v1/sessions/${sessionId}/archive`,
16805
15735
  {},
16806
15736
  {
16807
15737
  headers: {
16808
15738
  "Authorization": `Bearer ${this.credential.token}`,
16809
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15739
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16810
15740
  },
16811
15741
  timeout: 3e3
16812
15742
  }
16813
15743
  );
16814
15744
  return response.status >= 200 && response.status < 300;
16815
15745
  } catch (error) {
16816
- logger.debug("[API] deactivateSession failed:", error);
15746
+ persistence.logger.debug("[API] deactivateSession failed:", error);
16817
15747
  return false;
16818
15748
  }
16819
15749
  }
@@ -16832,11 +15762,11 @@ class ApiClient {
16832
15762
  for (let attempt = 0; attempt < attempts; attempt += 1) {
16833
15763
  try {
16834
15764
  const response = await axios.get(
16835
- `${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
15765
+ `${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`,
16836
15766
  {
16837
15767
  headers: {
16838
15768
  "Authorization": `Bearer ${this.credential.token}`,
16839
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15769
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16840
15770
  },
16841
15771
  timeout: opts?.timeoutMs ?? 4e3,
16842
15772
  validateStatus: () => true
@@ -16850,17 +15780,17 @@ class ApiClient {
16850
15780
  if (!row || typeof row.seq !== "number" || typeof row.metadata !== "string") {
16851
15781
  return { ok: false, reason: "unsupported" };
16852
15782
  }
16853
- const metadata = decrypt(encryptionKey, encryptionVariant, decodeBase64(row.metadata));
15783
+ const metadata = persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(row.metadata));
16854
15784
  if (!metadata) return { ok: false, reason: "unavailable" };
16855
- const agentState = row.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(row.agentState)) : null;
15785
+ const agentState = row.agentState ? persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(row.agentState)) : null;
16856
15786
  return {
16857
15787
  ok: true,
16858
15788
  session: { seq: row.seq, metadata, metadataVersion: row.metadataVersion, agentState, agentStateVersion: row.agentStateVersion }
16859
15789
  };
16860
15790
  }
16861
- logger.debug(`[API] getSession ${sessionId}: status ${response.status} (attempt ${attempt + 1}/${attempts})`);
15791
+ persistence.logger.debug(`[API] getSession ${sessionId}: status ${response.status} (attempt ${attempt + 1}/${attempts})`);
16862
15792
  } catch (error) {
16863
- logger.debug(`[API] getSession ${sessionId} failed (attempt ${attempt + 1}/${attempts}):`, error);
15793
+ persistence.logger.debug(`[API] getSession ${sessionId} failed (attempt ${attempt + 1}/${attempts}):`, error);
16864
15794
  }
16865
15795
  const delayMs = delays[Math.min(attempt, delays.length - 1)];
16866
15796
  if (attempt < attempts - 1 && delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
@@ -16873,12 +15803,12 @@ class ApiClient {
16873
15803
  async reactivateSession(sessionId) {
16874
15804
  try {
16875
15805
  const response = await axios.post(
16876
- `${configuration.serverUrl}/v1/sessions/${sessionId}/unarchive`,
15806
+ `${persistence.configuration.serverUrl}/v1/sessions/${sessionId}/unarchive`,
16877
15807
  {},
16878
15808
  {
16879
15809
  headers: {
16880
15810
  "Authorization": `Bearer ${this.credential.token}`,
16881
- "X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
15811
+ "X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
16882
15812
  },
16883
15813
  timeout: 3e3,
16884
15814
  validateStatus: (status) => status >= 200 && status < 300 || status === 404
@@ -16886,7 +15816,7 @@ class ApiClient {
16886
15816
  );
16887
15817
  return response.status >= 200 && response.status < 300 || response.status === 404;
16888
15818
  } catch (error) {
16889
- logger.debug("[API] reactivateSession failed:", error);
15819
+ persistence.logger.debug("[API] reactivateSession failed:", error);
16890
15820
  return false;
16891
15821
  }
16892
15822
  }
@@ -16970,67 +15900,36 @@ exports.MAX_CHAT_ATTACHMENT_SOURCE_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES;
16970
15900
  exports.MIRROR_BACKFILL_LINES_DEFAULT = MIRROR_BACKFILL_LINES_DEFAULT;
16971
15901
  exports.RawJSONLinesSchema = RawJSONLinesSchema;
16972
15902
  exports.STREAM_DELTA_MAX_CHARS = STREAM_DELTA_MAX_CHARS;
16973
- exports.SandboxConfigSchema = SandboxConfigSchema;
16974
15903
  exports.TITLE_PROMPT_PREFIX = TITLE_PROMPT_PREFIX;
16975
- exports.acquireDaemonLock = acquireDaemonLock;
16976
15904
  exports.api = api;
16977
- exports.clearCredentials = clearCredentials;
16978
- exports.clearDaemonState = clearDaemonState;
16979
- exports.clearMachineId = clearMachineId;
16980
15905
  exports.commandOnPath = commandOnPath;
16981
15906
  exports.compareExactVersions = compareExactVersions;
16982
- exports.configuration = configuration;
16983
15907
  exports.connectionState = connectionState;
16984
15908
  exports.contentLogMetadata = contentLogMetadata;
16985
15909
  exports.createEnvelope = createEnvelope;
16986
- exports.credentialRelayProblem = credentialRelayProblem;
16987
15910
  exports.decideBackfill = decideBackfill;
16988
15911
  exports.decideMirrorBinding = decideMirrorBinding;
16989
- exports.decodeBase64 = decodeBase64;
16990
- exports.decrypt = decrypt;
16991
- exports.decryptLegacy = decryptLegacy;
16992
- exports.decryptWithDataKey = decryptWithDataKey;
16993
15912
  exports.delay = delay;
16994
- exports.deletePersistedSessions = deletePersistedSessions;
16995
15913
  exports.deriveLocalCliUpdateSummary = deriveLocalCliUpdateSummary;
16996
15914
  exports.detectCLIAvailability = detectCLIAvailability;
16997
15915
  exports.detectResumeSupport = detectResumeSupport;
16998
- exports.encodeBase64 = encodeBase64;
16999
- exports.encodeBase64Url = encodeBase64Url;
17000
- exports.encrypt = encrypt;
17001
- exports.ensurePrivateDirectory = ensurePrivateDirectory;
17002
- exports.ensurePrivateDirectorySync = ensurePrivateDirectorySync;
17003
15916
  exports.errorLogMetadata = errorLogMetadata;
17004
15917
  exports.extractCompleteLines = extractCompleteLines;
17005
15918
  exports.fetchCliUpdateState = fetchCliUpdateState;
17006
- exports.getLatestDaemonLog = getLatestDaemonLog;
17007
15919
  exports.getLocalHappyAgentCredentialPath = getLocalHappyAgentCredentialPath;
17008
15920
  exports.getProjectPath = getProjectPath;
17009
- exports.hardenPrivateFile = hardenPrivateFile;
17010
- exports.hardenPrivateFileSync = hardenPrivateFileSync;
17011
15921
  exports.initializeSandbox = initializeSandbox;
17012
15922
  exports.isClaudeSessionId = isClaudeSessionId;
17013
15923
  exports.isValidSessionId = isValidSessionId;
17014
15924
  exports.isValidTerminalId = isValidTerminalId;
17015
15925
  exports.listVhTerminals = listVhTerminals;
17016
- exports.logger = logger;
17017
15926
  exports.mirrorLineKey = mirrorLineKey;
17018
15927
  exports.mirrorLocalId = mirrorLocalId;
17019
- exports.packageJson = packageJson;
17020
15928
  exports.parseExactVersion = parseExactVersion;
17021
15929
  exports.parseTerminalHookPayload = parseTerminalHookPayload;
17022
- exports.persistSession = persistSession;
17023
- exports.persistence = persistence;
17024
15930
  exports.projectPath = projectPath;
17025
- exports.readCredentials = readCredentials;
17026
- exports.readCredentialsForConfiguredRelay = readCredentialsForConfiguredRelay;
17027
- exports.readDaemonState = readDaemonState;
17028
15931
  exports.readLocalHappyAgentCredentials = readLocalHappyAgentCredentials;
17029
- exports.readPersistedSessions = readPersistedSessions;
17030
- exports.readSettings = readSettings;
17031
- exports.readSettingsOutcome = readSettingsOutcome;
17032
15932
  exports.readVhTerminal = readVhTerminal;
17033
- exports.releaseDaemonLock = releaseDaemonLock;
17034
15933
  exports.resolveCliUpdateCheckInterval = resolveCliUpdateCheckInterval;
17035
15934
  exports.scrubTmuxClientEnv = scrubTmuxClientEnv;
17036
15935
  exports.sendToVhTerminal = sendToVhTerminal;
@@ -17039,10 +15938,4 @@ exports.startOfflineReconnection = startOfflineReconnection;
17039
15938
  exports.streamKeyOf = streamKeyOf;
17040
15939
  exports.summarizeSpawnSessionForLog = summarizeSpawnSessionForLog;
17041
15940
  exports.tmuxArgs = tmuxArgs;
17042
- exports.updateSettings = updateSettings;
17043
15941
  exports.wrapCommand = wrapCommand;
17044
- exports.writeCredentialsDataKey = writeCredentialsDataKey;
17045
- exports.writeCredentialsLegacy = writeCredentialsLegacy;
17046
- exports.writeDaemonState = writeDaemonState;
17047
- exports.writePrivateFileSync = writePrivateFileSync;
17048
- exports.writeSettings = writeSettings;