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