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.
- package/dist/{AcpBackend-CT9TOBEM.mjs → AcpBackend-BzGpBAZ0.mjs} +2 -1
- package/dist/{AcpBackend-au0veuIF.cjs → AcpBackend-C9eu6CDu.cjs} +30 -29
- package/dist/{AcpSessionManager-p1tj7FxF.mjs → AcpSessionManager-B223yT78.mjs} +1 -1
- package/dist/{AcpSessionManager-BCWeBKDd.cjs → AcpSessionManager-DQ1e_0gc.cjs} +9 -9
- package/dist/agentGuidance-BR0OFumQ.mjs +224 -0
- package/dist/agentGuidance-Tm8Z5HYp.cjs +237 -0
- package/dist/codex/happyMcpStdioBridge.cjs +29 -6
- package/dist/codex/happyMcpStdioBridge.mjs +25 -2
- package/dist/{config-DwMic6AC.mjs → config-Bjd1nG0P.mjs} +11 -11
- package/dist/{config-GzzqE5Ev.cjs → config-C0mofvCH.cjs} +11 -11
- package/dist/{index-B3-daYaX.mjs → index-BP56asuS.mjs} +8 -8
- package/dist/{index-DgN9P02i.cjs → index-CMbKpntk.cjs} +53 -19
- package/dist/{index-Ba6Fu6MJ.mjs → index-CV6sAa4a.mjs} +49 -15
- package/dist/{index-DI_VC3vJ.cjs → index-Cw9Zj1Zr.cjs} +2191 -1864
- package/dist/{index-SQKnWyz-.mjs → index-D4eCRu9x.mjs} +2046 -1719
- package/dist/{index-C69uF6Up.cjs → index-HW-KdG6W.cjs} +8 -8
- package/dist/index.cjs +5 -5
- package/dist/index.mjs +5 -5
- package/dist/{installTerminalHooks-CvMt8cBI.cjs → installTerminalHooks-CNp1LExn.cjs} +9 -9
- package/dist/{installTerminalHooks-Df4hzzdM.mjs → installTerminalHooks-D-CNSalI.mjs} +8 -8
- package/dist/lib.cjs +12 -12
- package/dist/lib.d.cts +3 -0
- package/dist/lib.d.mts +3 -0
- package/dist/lib.mjs +9 -9
- package/dist/limits-DG1OQnCb.cjs +1198 -0
- package/dist/limits-DlbYYtOw.mjs +1129 -0
- package/dist/{mcp-DnyLwSPq.cjs → mcp-BtZoIIIM.cjs} +11 -10
- package/dist/{mcp-C0oW5u-b.mjs → mcp-D3oV_dH-.mjs} +7 -6
- package/dist/resources-CVl7NdOB.cjs +83 -0
- package/dist/resources-D-mn7ptO.mjs +80 -0
- package/dist/{runGemini-mqLjz-M7.mjs → runGemini-BJ8bU1pP.mjs} +13 -13
- package/dist/{runGemini-_FHrxuHN.cjs → runGemini-BfCSOPL1.cjs} +30 -30
- package/dist/{runOpenClaw-C5VZQ6Na.cjs → runOpenClaw-CBlHXEAT.cjs} +24 -24
- package/dist/{runOpenClaw-Bs03yQHe.mjs → runOpenClaw-DTgOpJnO.mjs} +9 -9
- package/dist/{send-BaLjQPZQ.mjs → send-9duZlpGX.mjs} +10 -10
- package/dist/{send-BD2gTVoP.cjs → send-CDK1bZFj.cjs} +10 -10
- package/dist/{sessions-B108j_Xj.cjs → sessions-CNkLCorF.cjs} +11 -11
- package/dist/{sessions-ggBePfSY.mjs → sessions-jAFOkKvW.mjs} +10 -10
- package/dist/{spawn-CORFRb9D.mjs → spawn-BG5oHi_f.mjs} +21 -21
- package/dist/{spawn-iI8w8sD7.cjs → spawn-J-A5WGZ7.cjs} +21 -21
- package/dist/teams-Cuqsk7Ty.mjs +189 -0
- package/dist/teams-MgWeyutG.cjs +191 -0
- package/dist/{types-CZIwVSN2.cjs → types-P5deH8Ls.cjs} +464 -1571
- package/dist/{types-gDOmwOKw.mjs → types-n_VWu9qe.mjs} +78 -1148
- package/package.json +7 -7
- package/dist/agentGuidance-BifkGXdE.cjs +0 -46
- package/dist/agentGuidance-C2_pkgRI.mjs +0 -37
- package/dist/limits-CwFb5S2b.mjs +0 -22
- 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
|
|
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
|
|
14
|
+
var path = require('path');
|
|
22
15
|
var spawn = require('cross-spawn');
|
|
23
16
|
var url = require('url');
|
|
24
|
-
var 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
|
|
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
|
|
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
|
|
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
|
|
1062
|
+
const platformName = os.platform();
|
|
1748
1063
|
const binaryName = platformName === "win32" ? "difft.exe" : "difft";
|
|
1749
|
-
return path
|
|
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
|
|
1787
|
-
const resolvedWorkingDir = path
|
|
1788
|
-
if (!resolvedTarget.startsWith(resolvedWorkingDir + path
|
|
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
|
|
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
|
|
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
|
|
1876
|
-
tempPath = path
|
|
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
|
|
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
|
|
1902
|
-
const tempPath = path
|
|
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
|
|
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
|
|
1465
|
+
persistence.logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
|
|
2151
1466
|
return;
|
|
2152
1467
|
}
|
|
2153
|
-
const childPath = path
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
3812
|
+
const st = await promises.stat(path);
|
|
4498
3813
|
if (!st.isDirectory()) throw new Error("not-a-directory");
|
|
4499
|
-
dirents = await promises.readdir(path
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11997
|
+
const path = path$1.join(dir, name);
|
|
13068
11998
|
try {
|
|
13069
|
-
const s = await promises.stat(path
|
|
11999
|
+
const s = await promises.stat(path);
|
|
13070
12000
|
if (!s.isFile() || s.size === 0) continue;
|
|
13071
|
-
out.push({ path
|
|
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 =
|
|
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;
|