very-happy-cli 0.2.124 → 0.2.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AcpBackend-CT9TOBEM.mjs → AcpBackend-BzGpBAZ0.mjs} +2 -1
- package/dist/{AcpBackend-au0veuIF.cjs → AcpBackend-C9eu6CDu.cjs} +30 -29
- package/dist/{AcpSessionManager-p1tj7FxF.mjs → AcpSessionManager-B223yT78.mjs} +1 -1
- package/dist/{AcpSessionManager-BCWeBKDd.cjs → AcpSessionManager-DQ1e_0gc.cjs} +9 -9
- package/dist/agentGuidance-BR0OFumQ.mjs +224 -0
- package/dist/agentGuidance-Tm8Z5HYp.cjs +237 -0
- package/dist/codex/happyMcpStdioBridge.cjs +29 -6
- package/dist/codex/happyMcpStdioBridge.mjs +25 -2
- package/dist/{config-DwMic6AC.mjs → config-Bjd1nG0P.mjs} +11 -11
- package/dist/{config-GzzqE5Ev.cjs → config-C0mofvCH.cjs} +11 -11
- package/dist/{index-B3-daYaX.mjs → index-BP56asuS.mjs} +8 -8
- package/dist/{index-DgN9P02i.cjs → index-CMbKpntk.cjs} +53 -19
- package/dist/{index-Ba6Fu6MJ.mjs → index-CV6sAa4a.mjs} +49 -15
- package/dist/{index-DI_VC3vJ.cjs → index-Cw9Zj1Zr.cjs} +2191 -1864
- package/dist/{index-SQKnWyz-.mjs → index-D4eCRu9x.mjs} +2046 -1719
- package/dist/{index-C69uF6Up.cjs → index-HW-KdG6W.cjs} +8 -8
- package/dist/index.cjs +5 -5
- package/dist/index.mjs +5 -5
- package/dist/{installTerminalHooks-CvMt8cBI.cjs → installTerminalHooks-CNp1LExn.cjs} +9 -9
- package/dist/{installTerminalHooks-Df4hzzdM.mjs → installTerminalHooks-D-CNSalI.mjs} +8 -8
- package/dist/lib.cjs +12 -12
- package/dist/lib.d.cts +3 -0
- package/dist/lib.d.mts +3 -0
- package/dist/lib.mjs +9 -9
- package/dist/limits-DG1OQnCb.cjs +1198 -0
- package/dist/limits-DlbYYtOw.mjs +1129 -0
- package/dist/{mcp-DnyLwSPq.cjs → mcp-BtZoIIIM.cjs} +11 -10
- package/dist/{mcp-C0oW5u-b.mjs → mcp-D3oV_dH-.mjs} +7 -6
- package/dist/resources-CVl7NdOB.cjs +83 -0
- package/dist/resources-D-mn7ptO.mjs +80 -0
- package/dist/{runGemini-mqLjz-M7.mjs → runGemini-BJ8bU1pP.mjs} +13 -13
- package/dist/{runGemini-_FHrxuHN.cjs → runGemini-BfCSOPL1.cjs} +30 -30
- package/dist/{runOpenClaw-C5VZQ6Na.cjs → runOpenClaw-CBlHXEAT.cjs} +24 -24
- package/dist/{runOpenClaw-Bs03yQHe.mjs → runOpenClaw-DTgOpJnO.mjs} +9 -9
- package/dist/{send-BaLjQPZQ.mjs → send-9duZlpGX.mjs} +10 -10
- package/dist/{send-BD2gTVoP.cjs → send-CDK1bZFj.cjs} +10 -10
- package/dist/{sessions-B108j_Xj.cjs → sessions-CNkLCorF.cjs} +11 -11
- package/dist/{sessions-ggBePfSY.mjs → sessions-jAFOkKvW.mjs} +10 -10
- package/dist/{spawn-CORFRb9D.mjs → spawn-BG5oHi_f.mjs} +21 -21
- package/dist/{spawn-iI8w8sD7.cjs → spawn-J-A5WGZ7.cjs} +21 -21
- package/dist/teams-Cuqsk7Ty.mjs +189 -0
- package/dist/teams-MgWeyutG.cjs +191 -0
- package/dist/{types-CZIwVSN2.cjs → types-P5deH8Ls.cjs} +464 -1571
- package/dist/{types-gDOmwOKw.mjs → types-n_VWu9qe.mjs} +78 -1148
- package/package.json +7 -7
- package/dist/agentGuidance-BifkGXdE.cjs +0 -46
- package/dist/agentGuidance-C2_pkgRI.mjs +0 -37
- package/dist/limits-CwFb5S2b.mjs +0 -22
- package/dist/limits-KdWKmwtH.cjs +0 -28
|
@@ -1,625 +1,31 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import
|
|
3
|
-
import { inspect } from 'node:util';
|
|
4
|
-
import { mkdirSync, chmodSync, readdirSync, statSync, appendFileSync, writeFileSync, existsSync, readFileSync, constants, unlinkSync, renameSync, accessSync, createReadStream, createWriteStream } from 'node:fs';
|
|
5
|
-
import os$1, { homedir } from 'node:os';
|
|
6
|
-
import { join, basename, isAbsolute, resolve as resolve$1 } from 'node:path';
|
|
7
|
-
import { mkdir, chmod, writeFile, stat as stat$1, readdir as readdir$1, lstat, open, unlink as unlink$1, readFile as readFile$1, rename as rename$1, copyFile } from 'node:fs/promises';
|
|
2
|
+
import { l as logger, g as encodeBase64, j as encrypt, k as decrypt, m as decodeBase64, c as configuration, o as decryptBlob, q as prepareClipboardText, f as readSettings, r as readPersistedSessions, s as credentialRelayProblem, p as packageJson, t as readTrackedClaudeSessionIds, u as libsodiumEncryptForPublicKey, v as libsodiumPublicKeyFromSecretKey, x as getRandomBytes, y as redactLogValue } from './limits-DlbYYtOw.mjs';
|
|
8
3
|
import { EventEmitter } from 'node:events';
|
|
9
4
|
import { io } from 'socket.io-client';
|
|
10
5
|
import * as z from 'zod';
|
|
11
6
|
import { z as z$1 } from 'zod';
|
|
12
7
|
import { isCuid, createId } from '@paralleldrive/cuid2';
|
|
13
|
-
import {
|
|
14
|
-
import tweetnacl from 'tweetnacl';
|
|
15
|
-
import { p as prepareClipboardText } from './limits-CwFb5S2b.mjs';
|
|
8
|
+
import { createHmac, randomUUID as randomUUID$1, randomBytes, createHash as createHash$1 } from 'node:crypto';
|
|
16
9
|
import { spawn as spawn$1, exec, execSync } from 'child_process';
|
|
17
10
|
import { promisify } from 'util';
|
|
18
|
-
import { mkdir
|
|
19
|
-
import { randomUUID, createHash
|
|
20
|
-
import { dirname, resolve, join
|
|
11
|
+
import { mkdir, writeFile, appendFile, stat, rename, unlink, readdir, readFile } from 'fs/promises';
|
|
12
|
+
import { randomUUID, createHash } from 'crypto';
|
|
13
|
+
import { dirname, resolve, join, sep } from 'path';
|
|
21
14
|
import { spawn } from 'cross-spawn';
|
|
22
15
|
import { fileURLToPath } from 'url';
|
|
23
16
|
import os, { platform } from 'os';
|
|
17
|
+
import { stat as stat$1, readdir as readdir$1, lstat, open, copyFile, unlink as unlink$1, readFile as readFile$1, rename as rename$1, writeFile as writeFile$1 } from 'node:fs/promises';
|
|
18
|
+
import { join as join$1, isAbsolute, resolve as resolve$1 } from 'node:path';
|
|
19
|
+
import os$1, { homedir } from 'node:os';
|
|
24
20
|
import { spawn as spawn$2, spawnSync, execSync as execSync$1 } from 'node:child_process';
|
|
25
21
|
import * as pty from 'node-pty';
|
|
22
|
+
import { existsSync, statSync, writeFileSync, unlinkSync, readFileSync, mkdirSync, accessSync, constants, createReadStream, createWriteStream } from 'node:fs';
|
|
26
23
|
import { existsSync as existsSync$1 } from 'fs';
|
|
24
|
+
import tweetnacl from 'tweetnacl';
|
|
27
25
|
import { createInterface } from 'node:readline';
|
|
28
26
|
import { SandboxManager } from '@anthropic-ai/sandbox-runtime';
|
|
29
27
|
import { Expo } from 'expo-server-sdk';
|
|
30
|
-
|
|
31
|
-
var name = "very-happy-cli";
|
|
32
|
-
var version = "0.2.124";
|
|
33
|
-
var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
|
|
34
|
-
var author = "Kirill Dubovitskiy";
|
|
35
|
-
var contributors = [
|
|
36
|
-
"Mereith (very-happy-cli fork)"
|
|
37
|
-
];
|
|
38
|
-
var license = "MIT";
|
|
39
|
-
var engines = {
|
|
40
|
-
node: "^20.19.0 || ^22.13.0 || >=24.0.0"
|
|
41
|
-
};
|
|
42
|
-
var type = "module";
|
|
43
|
-
var homepage = "https://github.com/Mereithhh/very-happy";
|
|
44
|
-
var bugs = "https://github.com/Mereithhh/very-happy/issues";
|
|
45
|
-
var repository = {
|
|
46
|
-
type: "git",
|
|
47
|
-
url: "git+https://github.com/Mereithhh/very-happy.git"
|
|
48
|
-
};
|
|
49
|
-
var bin = {
|
|
50
|
-
"very-happy": "./bin/very-happy.mjs",
|
|
51
|
-
"very-happy-mcp": "./bin/very-happy-mcp.mjs"
|
|
52
|
-
};
|
|
53
|
-
var main = "./dist/index.cjs";
|
|
54
|
-
var module$1 = "./dist/index.mjs";
|
|
55
|
-
var types = "./dist/index.d.cts";
|
|
56
|
-
var exports$1 = {
|
|
57
|
-
".": {
|
|
58
|
-
require: {
|
|
59
|
-
types: "./dist/index.d.cts",
|
|
60
|
-
"default": "./dist/index.cjs"
|
|
61
|
-
},
|
|
62
|
-
"import": {
|
|
63
|
-
types: "./dist/index.d.mts",
|
|
64
|
-
"default": "./dist/index.mjs"
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"./lib": {
|
|
68
|
-
require: {
|
|
69
|
-
types: "./dist/lib.d.cts",
|
|
70
|
-
"default": "./dist/lib.cjs"
|
|
71
|
-
},
|
|
72
|
-
"import": {
|
|
73
|
-
types: "./dist/lib.d.mts",
|
|
74
|
-
"default": "./dist/lib.mjs"
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
"./codex/happyMcpStdioBridge": {
|
|
78
|
-
require: {
|
|
79
|
-
types: "./dist/codex/happyMcpStdioBridge.d.cts",
|
|
80
|
-
"default": "./dist/codex/happyMcpStdioBridge.cjs"
|
|
81
|
-
},
|
|
82
|
-
"import": {
|
|
83
|
-
types: "./dist/codex/happyMcpStdioBridge.d.mts",
|
|
84
|
-
"default": "./dist/codex/happyMcpStdioBridge.mjs"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
var files = [
|
|
89
|
-
"dist",
|
|
90
|
-
"bin",
|
|
91
|
-
"scripts",
|
|
92
|
-
"tools/licenses",
|
|
93
|
-
"package.json",
|
|
94
|
-
"README.md",
|
|
95
|
-
"LICENSE"
|
|
96
|
-
];
|
|
97
|
-
var scripts = {
|
|
98
|
-
typecheck: "tsc --noEmit",
|
|
99
|
-
"bundle:server": "node scripts/bundle-server.cjs",
|
|
100
|
-
"bundle:server:all": "node scripts/bundle-server.cjs --all-platforms",
|
|
101
|
-
"bundle:webapp": "node scripts/bundle-webapp.cjs",
|
|
102
|
-
build: "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc --noEmit && pkgroll",
|
|
103
|
-
test: "pnpm run build && vitest run --project unit",
|
|
104
|
-
"test:integration": "pnpm run build && vitest run --project integration-empty --project integration-plan-mode --project integration-authenticated",
|
|
105
|
-
"cli:install": "node scripts/install-local.cjs",
|
|
106
|
-
prepublishOnly: "pnpm test",
|
|
107
|
-
postinstall: "node scripts/unpack-tools.cjs && node scripts/fix-pty-perms.cjs"
|
|
108
|
-
};
|
|
109
|
-
var dependencies = {
|
|
110
|
-
"@agentclientprotocol/sdk": "^0.14.1",
|
|
111
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.232",
|
|
112
|
-
"@anthropic-ai/sandbox-runtime": "^0.0.37",
|
|
113
|
-
"@modelcontextprotocol/sdk": "1.30.0",
|
|
114
|
-
"@noble/ed25519": "^3.0.0",
|
|
115
|
-
"@noble/hashes": "^2.0.1",
|
|
116
|
-
"@paralleldrive/cuid2": "^2.2.2",
|
|
117
|
-
"@stablelib/base64": "^2.0.1",
|
|
118
|
-
"@stablelib/hex": "^2.0.1",
|
|
119
|
-
"@types/cross-spawn": "^6.0.6",
|
|
120
|
-
"@types/http-proxy": "^1.17.17",
|
|
121
|
-
"@types/react": "^19.2.7",
|
|
122
|
-
"@types/tmp": "^0.2.6",
|
|
123
|
-
axios: "^1.13.2",
|
|
124
|
-
chalk: "^5.6.2",
|
|
125
|
-
"cross-spawn": "^7.0.6",
|
|
126
|
-
"expo-server-sdk": "^3.15.0",
|
|
127
|
-
fastify: "5.8.5",
|
|
128
|
-
"fastify-type-provider-zod": "^6.1.0",
|
|
129
|
-
"http-proxy": "^1.18.1",
|
|
130
|
-
"http-proxy-middleware": "^3.0.7",
|
|
131
|
-
ink: "^6.5.1",
|
|
132
|
-
inquirer: "^13.2.2",
|
|
133
|
-
"node-pty": "^1.0.0",
|
|
134
|
-
open: "^10.2.0",
|
|
135
|
-
"ps-list": "^8.1.1",
|
|
136
|
-
react: "^19.2.0",
|
|
137
|
-
"socket.io-client": "^4.8.1",
|
|
138
|
-
tar: "^7.5.22",
|
|
139
|
-
tmp: "^0.2.7",
|
|
140
|
-
tweetnacl: "^1.0.3",
|
|
141
|
-
ws: "^8.21.3",
|
|
142
|
-
zod: "^4.0.0"
|
|
143
|
-
};
|
|
144
|
-
var optionalDependencies = {
|
|
145
|
-
"very-happy-tools-arm64-darwin": "0.2.124",
|
|
146
|
-
"very-happy-tools-x64-darwin": "0.2.124",
|
|
147
|
-
"very-happy-tools-arm64-linux": "0.2.124",
|
|
148
|
-
"very-happy-tools-x64-linux": "0.2.124",
|
|
149
|
-
"very-happy-tools-arm64-win32": "0.2.124",
|
|
150
|
-
"very-happy-tools-x64-win32": "0.2.124"
|
|
151
|
-
};
|
|
152
|
-
var devDependencies = {
|
|
153
|
-
"@slopus/happy-wire": "workspace:*",
|
|
154
|
-
"@xterm/addon-serialize": "0.13.0",
|
|
155
|
-
"@xterm/headless": "5.5.0",
|
|
156
|
-
"@types/inquirer": "^9.0.9",
|
|
157
|
-
"@types/node": ">=20",
|
|
158
|
-
"@types/ws": "^8.18.1",
|
|
159
|
-
"cross-env": "^10.1.0",
|
|
160
|
-
dotenv: "^16.6.1",
|
|
161
|
-
pkgroll: "^2.14.2",
|
|
162
|
-
"ts-node": "^10",
|
|
163
|
-
tsx: "^4.20.6",
|
|
164
|
-
typescript: "5.9.3",
|
|
165
|
-
vitest: "3.2.6"
|
|
166
|
-
};
|
|
167
|
-
var publishConfig = {
|
|
168
|
-
registry: "https://registry.npmjs.org"
|
|
169
|
-
};
|
|
170
|
-
var packageManager = "pnpm@10.11.0";
|
|
171
|
-
var packageJson = {
|
|
172
|
-
name: name,
|
|
173
|
-
version: version,
|
|
174
|
-
description: description,
|
|
175
|
-
author: author,
|
|
176
|
-
contributors: contributors,
|
|
177
|
-
license: license,
|
|
178
|
-
engines: engines,
|
|
179
|
-
type: type,
|
|
180
|
-
homepage: homepage,
|
|
181
|
-
bugs: bugs,
|
|
182
|
-
repository: repository,
|
|
183
|
-
bin: bin,
|
|
184
|
-
main: main,
|
|
185
|
-
module: module$1,
|
|
186
|
-
types: types,
|
|
187
|
-
exports: exports$1,
|
|
188
|
-
files: files,
|
|
189
|
-
scripts: scripts,
|
|
190
|
-
dependencies: dependencies,
|
|
191
|
-
optionalDependencies: optionalDependencies,
|
|
192
|
-
devDependencies: devDependencies,
|
|
193
|
-
publishConfig: publishConfig,
|
|
194
|
-
packageManager: packageManager
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const PRIVATE_DIRECTORY_MODE = 448;
|
|
198
|
-
const PRIVATE_FILE_MODE = 384;
|
|
199
|
-
function ignoreUnsupportedPermissions(error) {
|
|
200
|
-
if (process.platform !== "win32") throw error;
|
|
201
|
-
}
|
|
202
|
-
function ensurePrivateDirectorySync(path) {
|
|
203
|
-
mkdirSync(path, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
|
|
204
|
-
try {
|
|
205
|
-
chmodSync(path, PRIVATE_DIRECTORY_MODE);
|
|
206
|
-
} catch (error) {
|
|
207
|
-
ignoreUnsupportedPermissions(error);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
async function ensurePrivateDirectory(path) {
|
|
211
|
-
await mkdir(path, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
|
|
212
|
-
try {
|
|
213
|
-
await chmod(path, PRIVATE_DIRECTORY_MODE);
|
|
214
|
-
} catch (error) {
|
|
215
|
-
ignoreUnsupportedPermissions(error);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
function hardenPrivateFileSync(path) {
|
|
219
|
-
try {
|
|
220
|
-
chmodSync(path, PRIVATE_FILE_MODE);
|
|
221
|
-
} catch (error) {
|
|
222
|
-
ignoreUnsupportedPermissions(error);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
function hardenPrivateDirectoryFilesSync(path) {
|
|
226
|
-
for (const name of readdirSync(path)) {
|
|
227
|
-
const candidate = join(path, name);
|
|
228
|
-
try {
|
|
229
|
-
if (statSync(candidate).isFile()) hardenPrivateFileSync(candidate);
|
|
230
|
-
} catch {
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
async function hardenPrivateFile(path) {
|
|
235
|
-
try {
|
|
236
|
-
await chmod(path, PRIVATE_FILE_MODE);
|
|
237
|
-
} catch (error) {
|
|
238
|
-
ignoreUnsupportedPermissions(error);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function writePrivateFileSync(path, data) {
|
|
242
|
-
writeFileSync(path, data, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
243
|
-
hardenPrivateFileSync(path);
|
|
244
|
-
}
|
|
245
|
-
async function writePrivateFile(path, data) {
|
|
246
|
-
await writeFile(path, data, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
247
|
-
await hardenPrivateFile(path);
|
|
248
|
-
}
|
|
249
|
-
function appendPrivateFileSync(path, data) {
|
|
250
|
-
appendFileSync(path, data, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
|
|
251
|
-
hardenPrivateFileSync(path);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const DEFAULT_RELAY_URL = "https://veryhappy.dev";
|
|
255
|
-
function normalizeHttpEndpoint(value, source) {
|
|
256
|
-
const trimmed = value.trim();
|
|
257
|
-
let parsed;
|
|
258
|
-
try {
|
|
259
|
-
parsed = new URL(trimmed);
|
|
260
|
-
} catch {
|
|
261
|
-
throw new Error(`${source} must be an absolute http(s) origin`);
|
|
262
|
-
}
|
|
263
|
-
const onlyOriginPath = /^\/+$/u.test(parsed.pathname);
|
|
264
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username.length > 0 || parsed.password.length > 0 || !onlyOriginPath || parsed.search.length > 0 || parsed.hash.length > 0) {
|
|
265
|
-
throw new Error(`${source} must be an http(s) origin without credentials, path, query, or fragment`);
|
|
266
|
-
}
|
|
267
|
-
return parsed.origin;
|
|
268
|
-
}
|
|
269
|
-
function resolveHttpEndpoint(environmentValue, settingsValue, environmentName, settingsName) {
|
|
270
|
-
if (environmentValue !== void 0) {
|
|
271
|
-
return normalizeHttpEndpoint(environmentValue, environmentName);
|
|
272
|
-
}
|
|
273
|
-
if (settingsValue !== void 0) {
|
|
274
|
-
return normalizeHttpEndpoint(settingsValue, `settings.${settingsName}`);
|
|
275
|
-
}
|
|
276
|
-
return DEFAULT_RELAY_URL;
|
|
277
|
-
}
|
|
278
|
-
class Configuration {
|
|
279
|
-
serverUrl;
|
|
280
|
-
webappUrl;
|
|
281
|
-
isDaemonProcess;
|
|
282
|
-
// Directories and paths (from persistence)
|
|
283
|
-
happyHomeDir;
|
|
284
|
-
logsDir;
|
|
285
|
-
settingsFile;
|
|
286
|
-
privateKeyFile;
|
|
287
|
-
daemonStateFile;
|
|
288
|
-
daemonLockFile;
|
|
289
|
-
sessionsFile;
|
|
290
|
-
currentCliVersion;
|
|
291
|
-
isExperimentalEnabled;
|
|
292
|
-
disableCaffeinate;
|
|
293
|
-
constructor() {
|
|
294
|
-
const args = process.argv.slice(2);
|
|
295
|
-
this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
|
|
296
|
-
if (process.env.HAPPY_HOME_DIR) {
|
|
297
|
-
const expandedPath = process.env.HAPPY_HOME_DIR.replace(/^~/, homedir());
|
|
298
|
-
this.happyHomeDir = expandedPath;
|
|
299
|
-
} else {
|
|
300
|
-
this.happyHomeDir = join(homedir(), ".happy");
|
|
301
|
-
}
|
|
302
|
-
this.logsDir = join(this.happyHomeDir, "logs");
|
|
303
|
-
this.settingsFile = join(this.happyHomeDir, "settings.json");
|
|
304
|
-
this.privateKeyFile = join(this.happyHomeDir, "access.key");
|
|
305
|
-
this.daemonStateFile = join(this.happyHomeDir, "daemon.state.json");
|
|
306
|
-
this.daemonLockFile = join(this.happyHomeDir, "daemon.state.json.lock");
|
|
307
|
-
this.sessionsFile = join(this.happyHomeDir, "sessions.json");
|
|
308
|
-
try {
|
|
309
|
-
this.serverUrl = resolveHttpEndpoint(
|
|
310
|
-
process.env.HAPPY_SERVER_URL,
|
|
311
|
-
readSettingsStringSync(this.settingsFile, "serverUrl"),
|
|
312
|
-
"HAPPY_SERVER_URL",
|
|
313
|
-
"serverUrl"
|
|
314
|
-
);
|
|
315
|
-
this.webappUrl = resolveHttpEndpoint(
|
|
316
|
-
process.env.HAPPY_WEBAPP_URL,
|
|
317
|
-
readSettingsStringSync(this.settingsFile, "webappUrl"),
|
|
318
|
-
"HAPPY_WEBAPP_URL",
|
|
319
|
-
"webappUrl"
|
|
320
|
-
);
|
|
321
|
-
} catch (error) {
|
|
322
|
-
const message = error instanceof Error ? error.message : "invalid client endpoint";
|
|
323
|
-
process.stderr.write(`[very-happy] Configuration error: ${message}
|
|
324
|
-
`);
|
|
325
|
-
process.exit(1);
|
|
326
|
-
}
|
|
327
|
-
this.isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || "");
|
|
328
|
-
this.disableCaffeinate = ["true", "1", "yes"].includes(process.env.HAPPY_DISABLE_CAFFEINATE?.toLowerCase() || "");
|
|
329
|
-
this.currentCliVersion = packageJson.version;
|
|
330
|
-
const variant = process.env.HAPPY_VARIANT || "stable";
|
|
331
|
-
if (!this.isDaemonProcess && variant === "dev") {
|
|
332
|
-
console.log("\x1B[33m\u{1F527} DEV MODE\x1B[0m - Data: " + this.happyHomeDir);
|
|
333
|
-
}
|
|
334
|
-
ensurePrivateDirectorySync(this.happyHomeDir);
|
|
335
|
-
ensurePrivateDirectorySync(this.logsDir);
|
|
336
|
-
for (const privateFile of [
|
|
337
|
-
this.settingsFile,
|
|
338
|
-
this.privateKeyFile,
|
|
339
|
-
this.daemonStateFile,
|
|
340
|
-
this.daemonLockFile,
|
|
341
|
-
this.sessionsFile
|
|
342
|
-
]) {
|
|
343
|
-
if (existsSync(privateFile)) hardenPrivateFileSync(privateFile);
|
|
344
|
-
}
|
|
345
|
-
hardenPrivateDirectoryFilesSync(this.logsDir);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
function readSettingsStringSync(settingsFile, key) {
|
|
349
|
-
try {
|
|
350
|
-
if (!existsSync(settingsFile)) return void 0;
|
|
351
|
-
const raw = JSON.parse(readFileSync(settingsFile, "utf8"));
|
|
352
|
-
const value = raw?.[key];
|
|
353
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
354
|
-
} catch {
|
|
355
|
-
return void 0;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
const configuration = new Configuration();
|
|
359
|
-
|
|
360
|
-
function sensitiveKey(key) {
|
|
361
|
-
const normalized = key.toLowerCase().replace(/[_\-\s]/g, "");
|
|
362
|
-
return /(token|secret|password|authorization|cookie|credential|apikey|privatekey|encryptionkey|accesskey|claimsecret)/.test(normalized);
|
|
363
|
-
}
|
|
364
|
-
function redactLogString(value) {
|
|
365
|
-
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]");
|
|
366
|
-
}
|
|
367
|
-
function redactLogValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
|
|
368
|
-
if (typeof value === "string") return redactLogString(value);
|
|
369
|
-
if (value === null || typeof value !== "object") return value;
|
|
370
|
-
if (depth >= 6) return "[TRUNCATED]";
|
|
371
|
-
if (seen.has(value)) return "[CIRCULAR]";
|
|
372
|
-
seen.add(value);
|
|
373
|
-
if (value instanceof Error) {
|
|
374
|
-
const error = value;
|
|
375
|
-
return {
|
|
376
|
-
name: error.name,
|
|
377
|
-
message: redactLogString(error.message),
|
|
378
|
-
...typeof error.code === "string" || typeof error.code === "number" ? { code: error.code } : {},
|
|
379
|
-
...typeof (error.status ?? error.response?.status) === "number" ? { status: error.status ?? error.response?.status } : {}
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
if (Array.isArray(value)) return value.map((item) => redactLogValue(item, depth + 1, seen));
|
|
383
|
-
const result = {};
|
|
384
|
-
for (const [key, child] of Object.entries(value)) {
|
|
385
|
-
if (sensitiveKey(key)) {
|
|
386
|
-
result[key] = "[REDACTED]";
|
|
387
|
-
} else if (key === "environmentVariables" && child && typeof child === "object") {
|
|
388
|
-
result[key] = { names: Object.keys(child).sort() };
|
|
389
|
-
} else {
|
|
390
|
-
result[key] = redactLogValue(child, depth + 1, seen);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return result;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
|
|
397
|
-
return date.toLocaleString("sv-SE", {
|
|
398
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
399
|
-
year: "numeric",
|
|
400
|
-
month: "2-digit",
|
|
401
|
-
day: "2-digit",
|
|
402
|
-
hour: "2-digit",
|
|
403
|
-
minute: "2-digit",
|
|
404
|
-
second: "2-digit"
|
|
405
|
-
}).replace(/[: ]/g, "-").replace(/,/g, "") + "-pid-" + process.pid;
|
|
406
|
-
}
|
|
407
|
-
function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
|
|
408
|
-
return date.toLocaleTimeString("en-US", {
|
|
409
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
410
|
-
hour12: false,
|
|
411
|
-
hour: "2-digit",
|
|
412
|
-
minute: "2-digit",
|
|
413
|
-
second: "2-digit",
|
|
414
|
-
fractionalSecondDigits: 3
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
function getSessionLogPath() {
|
|
418
|
-
const timestamp = createTimestampForFilename();
|
|
419
|
-
const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
|
|
420
|
-
return join(configuration.logsDir, filename);
|
|
421
|
-
}
|
|
422
|
-
function safeRemoteLogUrl(raw) {
|
|
423
|
-
if (!raw) return void 0;
|
|
424
|
-
try {
|
|
425
|
-
const url = new URL(raw);
|
|
426
|
-
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
427
|
-
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) return void 0;
|
|
428
|
-
if (url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "") return void 0;
|
|
429
|
-
return url.origin;
|
|
430
|
-
} catch {
|
|
431
|
-
return void 0;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
class Logger {
|
|
435
|
-
constructor(logFilePath = getSessionLogPath()) {
|
|
436
|
-
this.logFilePath = logFilePath;
|
|
437
|
-
const remoteUrl = safeRemoteLogUrl(process.env.HAPPY_SERVER_URL);
|
|
438
|
-
const remoteToken = process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING_TOKEN;
|
|
439
|
-
if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING && remoteUrl && remoteToken && Buffer.byteLength(remoteToken) >= 32) {
|
|
440
|
-
this.dangerouslyUnencryptedServerLoggingUrl = remoteUrl;
|
|
441
|
-
this.dangerouslyUnencryptedServerLoggingToken = remoteToken;
|
|
442
|
-
console.log(chalk.yellow("[REMOTE LOGGING] Sending logs to server for AI debugging"));
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
dangerouslyUnencryptedServerLoggingUrl;
|
|
446
|
-
dangerouslyUnencryptedServerLoggingToken;
|
|
447
|
-
// Use local timezone for simplicity of locating the logs,
|
|
448
|
-
// in practice you will not need absolute timestamps
|
|
449
|
-
localTimezoneTimestamp() {
|
|
450
|
-
return createTimestampForLogEntry();
|
|
451
|
-
}
|
|
452
|
-
debug(message, ...args) {
|
|
453
|
-
this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, ...args);
|
|
454
|
-
}
|
|
455
|
-
debugLargeJson(message, object, maxStringLength = 100, maxArrayLength = 10) {
|
|
456
|
-
if (!process.env.DEBUG) {
|
|
457
|
-
this.debug(`In production, skipping message inspection`);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
const truncateStrings = (obj) => {
|
|
461
|
-
if (typeof obj === "string") {
|
|
462
|
-
return obj.length > maxStringLength ? obj.substring(0, maxStringLength) + "... [truncated for logs]" : obj;
|
|
463
|
-
}
|
|
464
|
-
if (Array.isArray(obj)) {
|
|
465
|
-
const truncatedArray = obj.map((item) => truncateStrings(item)).slice(0, maxArrayLength);
|
|
466
|
-
if (obj.length > maxArrayLength) {
|
|
467
|
-
truncatedArray.push(`... [truncated array for logs up to ${maxArrayLength} items]`);
|
|
468
|
-
}
|
|
469
|
-
return truncatedArray;
|
|
470
|
-
}
|
|
471
|
-
if (obj && typeof obj === "object") {
|
|
472
|
-
const result = {};
|
|
473
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
474
|
-
if (key === "usage") {
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
result[key] = truncateStrings(value);
|
|
478
|
-
}
|
|
479
|
-
return result;
|
|
480
|
-
}
|
|
481
|
-
return obj;
|
|
482
|
-
};
|
|
483
|
-
const truncatedObject = truncateStrings(redactLogValue(object));
|
|
484
|
-
const json = JSON.stringify(truncatedObject, null, 2);
|
|
485
|
-
this.logToFile(`[${this.localTimezoneTimestamp()}]`, redactLogString(message), "\n", json);
|
|
486
|
-
}
|
|
487
|
-
info(message, ...args) {
|
|
488
|
-
this.logToConsole("info", "", message, ...args);
|
|
489
|
-
this.debug(message, args);
|
|
490
|
-
}
|
|
491
|
-
infoDeveloper(message, ...args) {
|
|
492
|
-
this.debug(message, ...args);
|
|
493
|
-
if (process.env.DEBUG) {
|
|
494
|
-
this.logToConsole("info", "[DEV]", message, ...args);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
warn(message, ...args) {
|
|
498
|
-
this.logToConsole("warn", "", message, ...args);
|
|
499
|
-
this.debug(`[WARN] ${message}`, ...args);
|
|
500
|
-
}
|
|
501
|
-
getLogPath() {
|
|
502
|
-
return this.logFilePath;
|
|
503
|
-
}
|
|
504
|
-
logToConsole(level, prefix, message, ...args) {
|
|
505
|
-
message = redactLogString(message);
|
|
506
|
-
args = args.map((arg) => redactLogValue(arg));
|
|
507
|
-
switch (level) {
|
|
508
|
-
case "debug": {
|
|
509
|
-
console.log(chalk.gray(prefix), message, ...args);
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
case "error": {
|
|
513
|
-
console.error(chalk.red(prefix), message, ...args);
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
case "info": {
|
|
517
|
-
console.log(chalk.blue(prefix), message, ...args);
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
case "warn": {
|
|
521
|
-
console.log(chalk.yellow(prefix), message, ...args);
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
default: {
|
|
525
|
-
this.debug("Unknown log level:", level);
|
|
526
|
-
console.log(chalk.blue(prefix), message, ...args);
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
async sendToRemoteServer(level, message, ...args) {
|
|
532
|
-
if (!this.dangerouslyUnencryptedServerLoggingUrl) return;
|
|
533
|
-
try {
|
|
534
|
-
await fetch(this.dangerouslyUnencryptedServerLoggingUrl + "/logs-combined-from-cli-and-mobile-for-simple-ai-debugging", {
|
|
535
|
-
method: "POST",
|
|
536
|
-
headers: {
|
|
537
|
-
"Content-Type": "application/json",
|
|
538
|
-
"Authorization": `Bearer ${this.dangerouslyUnencryptedServerLoggingToken}`
|
|
539
|
-
},
|
|
540
|
-
body: JSON.stringify({
|
|
541
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
542
|
-
level,
|
|
543
|
-
message: `${redactLogString(message)} ${args.map((raw) => {
|
|
544
|
-
const safe = redactLogValue(raw);
|
|
545
|
-
return typeof safe === "object" ? JSON.stringify(safe, null, 2) : String(safe);
|
|
546
|
-
}).join(" ")}`,
|
|
547
|
-
source: "cli",
|
|
548
|
-
platform: process.platform
|
|
549
|
-
})
|
|
550
|
-
});
|
|
551
|
-
} catch (error) {
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
logToFile(prefix, message, ...args) {
|
|
555
|
-
const safeMessage = redactLogString(message);
|
|
556
|
-
const safeArgs = args.map((arg) => redactLogValue(arg));
|
|
557
|
-
const logLine = `${prefix} ${safeMessage} ${safeArgs.map(
|
|
558
|
-
(arg) => typeof arg === "string" ? arg : inspect(arg, { depth: 5, breakLength: 120 })
|
|
559
|
-
).join(" ")}
|
|
560
|
-
`;
|
|
561
|
-
if (this.dangerouslyUnencryptedServerLoggingUrl) {
|
|
562
|
-
let level = "info";
|
|
563
|
-
if (prefix.includes(this.localTimezoneTimestamp())) {
|
|
564
|
-
level = "debug";
|
|
565
|
-
}
|
|
566
|
-
this.sendToRemoteServer(level, safeMessage, ...safeArgs).catch(() => {
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
try {
|
|
570
|
-
appendPrivateFileSync(this.logFilePath, logLine);
|
|
571
|
-
} catch (appendError) {
|
|
572
|
-
if (process.env.DEBUG) {
|
|
573
|
-
console.error("[DEV MODE ONLY THROWING] Failed to append to log file:", appendError);
|
|
574
|
-
throw appendError;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
let logger = new Logger();
|
|
580
|
-
async function listDaemonLogFiles(limit = 50) {
|
|
581
|
-
try {
|
|
582
|
-
const logsDir = configuration.logsDir;
|
|
583
|
-
if (!existsSync(logsDir)) {
|
|
584
|
-
return [];
|
|
585
|
-
}
|
|
586
|
-
const logs = readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
|
|
587
|
-
const fullPath = join(logsDir, file);
|
|
588
|
-
const stats = statSync(fullPath);
|
|
589
|
-
return { file, path: fullPath, modified: stats.mtime };
|
|
590
|
-
}).sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
591
|
-
try {
|
|
592
|
-
const { readDaemonState } = await Promise.resolve().then(function () { return persistence; });
|
|
593
|
-
const state = await readDaemonState();
|
|
594
|
-
if (!state) {
|
|
595
|
-
return logs;
|
|
596
|
-
}
|
|
597
|
-
if (state.daemonLogPath && existsSync(state.daemonLogPath)) {
|
|
598
|
-
const stats = statSync(state.daemonLogPath);
|
|
599
|
-
const persisted = {
|
|
600
|
-
file: basename(state.daemonLogPath),
|
|
601
|
-
path: state.daemonLogPath,
|
|
602
|
-
modified: stats.mtime
|
|
603
|
-
};
|
|
604
|
-
const idx = logs.findIndex((l) => l.path === persisted.path);
|
|
605
|
-
if (idx >= 0) {
|
|
606
|
-
const [found] = logs.splice(idx, 1);
|
|
607
|
-
logs.unshift(found);
|
|
608
|
-
} else {
|
|
609
|
-
logs.unshift(persisted);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
} catch {
|
|
613
|
-
}
|
|
614
|
-
return logs.slice(0, Math.max(0, limit));
|
|
615
|
-
} catch {
|
|
616
|
-
return [];
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
async function getLatestDaemonLog() {
|
|
620
|
-
const [latest] = await listDaemonLogFiles(1);
|
|
621
|
-
return latest || null;
|
|
622
|
-
}
|
|
28
|
+
import chalk from 'chalk';
|
|
623
29
|
|
|
624
30
|
const sessionRoleSchema = z.enum(["user", "agent"]);
|
|
625
31
|
const sessionTextEventSchema = z.object({
|
|
@@ -1026,8 +432,36 @@ const ConnectionDiagnosticEventSchema = z$1.object({
|
|
|
1026
432
|
z$1.object({
|
|
1027
433
|
events: z$1.array(ConnectionDiagnosticEventSchema).min(1).max(32)
|
|
1028
434
|
}).strict();
|
|
435
|
+
const TeamPermissionModeSchema = z$1.enum(["default", "bypassPermissions"]);
|
|
436
|
+
const id = z$1.string().min(1).max(128);
|
|
437
|
+
const text = z$1.string().min(1).max(32e3);
|
|
438
|
+
const TeamActionSchema = z$1.discriminatedUnion("type", [
|
|
439
|
+
z$1.object({ type: z$1.literal("archive") }),
|
|
440
|
+
z$1.object({ type: z$1.literal("set-permission-mode"), permissionMode: TeamPermissionModeSchema }),
|
|
441
|
+
z$1.object({ type: z$1.literal("schedule-create"), name: id, botId: id, body: text, runAt: z$1.number().int().positive().max(864e13), intervalMs: z$1.number().int().min(6e4).max(366 * 864e5).optional() }),
|
|
442
|
+
z$1.object({ type: z$1.literal("schedule-pause"), scheduleId: id, version: z$1.number().int().positive() }),
|
|
443
|
+
z$1.object({ type: z$1.literal("schedule-resume"), scheduleId: id, version: z$1.number().int().positive() }),
|
|
444
|
+
z$1.object({ type: z$1.literal("schedule-cancel"), scheduleId: id, version: z$1.number().int().positive() }),
|
|
445
|
+
z$1.object({ type: z$1.literal("reconcile-operation"), operationId: id, claimId: id, note: z$1.string().trim().min(1).max(4e3) }),
|
|
446
|
+
z$1.object({ type: z$1.literal("join"), name: id, sessionId: id, botId: id.optional() }),
|
|
447
|
+
z$1.object({ type: z$1.literal("delegate"), goal: text, acceptance: z$1.array(text).min(1).max(32), parentTaskId: id.optional(), assigneeBotId: id.optional(), botName: id.optional(), directory: z$1.string().min(1).max(4096).optional(), assistant: z$1.enum(["claude", "codex", "pi-acp"]).optional() }),
|
|
448
|
+
z$1.object({ type: z$1.literal("message"), taskId: id, body: text, recipientBotId: id.optional() }),
|
|
449
|
+
z$1.object({ type: z$1.literal("submit"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), result: text }),
|
|
450
|
+
z$1.object({ type: z$1.literal("accept"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive() }),
|
|
451
|
+
z$1.object({ type: z$1.literal("return"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), reason: text }),
|
|
452
|
+
z$1.object({ type: z$1.literal("cancel"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), reason: text }),
|
|
453
|
+
z$1.object({ type: z$1.literal("handoff"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), assigneeBotId: id }),
|
|
454
|
+
z$1.object({ type: z$1.literal("claim-operation"), operationId: id, machineId: id }),
|
|
455
|
+
z$1.object({ type: z$1.literal("complete-operation"), operationId: id, machineId: id, claimId: id, sessionId: id.optional() }),
|
|
456
|
+
z$1.object({ type: z$1.literal("fail-operation"), operationId: id, machineId: id, claimId: id, error: text, unknown: z$1.boolean().optional() }),
|
|
457
|
+
z$1.object({ type: z$1.literal("session-event"), sessionId: id, event: z$1.enum(["idle", "blocked", "exited"]) }),
|
|
458
|
+
z$1.object({ type: z$1.literal("message-delivered"), messageId: id, recipientBotId: id, generation: z$1.number().int().positive(), sessionId: id })
|
|
459
|
+
]);
|
|
460
|
+
z$1.object({ requestId: id, action: TeamActionSchema });
|
|
461
|
+
z$1.object({ name: id, machineId: id, requestId: id.optional() });
|
|
1029
462
|
|
|
1030
463
|
z$1.object({
|
|
464
|
+
teamsVersion: z$1.number().int().positive().optional(),
|
|
1031
465
|
host: z$1.string(),
|
|
1032
466
|
platform: z$1.string(),
|
|
1033
467
|
happyCliVersion: z$1.string(),
|
|
@@ -1302,125 +736,6 @@ const AgentMessageSchema = z$1.object({
|
|
|
1302
736
|
});
|
|
1303
737
|
z$1.union([UserMessageSchema, AgentMessageSchema]);
|
|
1304
738
|
|
|
1305
|
-
function encodeBase64(buffer, variant = "base64") {
|
|
1306
|
-
if (variant === "base64url") {
|
|
1307
|
-
return encodeBase64Url(buffer);
|
|
1308
|
-
}
|
|
1309
|
-
return Buffer.from(buffer).toString("base64");
|
|
1310
|
-
}
|
|
1311
|
-
function encodeBase64Url(buffer) {
|
|
1312
|
-
return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
1313
|
-
}
|
|
1314
|
-
function decodeBase64(base64, variant = "base64") {
|
|
1315
|
-
if (variant === "base64url") {
|
|
1316
|
-
const base64Standard = base64.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - base64.length % 4) % 4);
|
|
1317
|
-
return new Uint8Array(Buffer.from(base64Standard, "base64"));
|
|
1318
|
-
}
|
|
1319
|
-
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
1320
|
-
}
|
|
1321
|
-
function getRandomBytes(size) {
|
|
1322
|
-
return new Uint8Array(randomBytes(size));
|
|
1323
|
-
}
|
|
1324
|
-
function libsodiumPublicKeyFromSecretKey(seed) {
|
|
1325
|
-
const hashedSeed = new Uint8Array(createHash("sha512").update(seed).digest());
|
|
1326
|
-
const secretKey = hashedSeed.slice(0, 32);
|
|
1327
|
-
return new Uint8Array(tweetnacl.box.keyPair.fromSecretKey(secretKey).publicKey);
|
|
1328
|
-
}
|
|
1329
|
-
function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
|
|
1330
|
-
const ephemeralKeyPair = tweetnacl.box.keyPair();
|
|
1331
|
-
const nonce = getRandomBytes(tweetnacl.box.nonceLength);
|
|
1332
|
-
const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
|
|
1333
|
-
const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
|
|
1334
|
-
result.set(ephemeralKeyPair.publicKey, 0);
|
|
1335
|
-
result.set(nonce, ephemeralKeyPair.publicKey.length);
|
|
1336
|
-
result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
|
|
1337
|
-
return result;
|
|
1338
|
-
}
|
|
1339
|
-
function encryptLegacy(data, secret) {
|
|
1340
|
-
const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
|
|
1341
|
-
const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
|
|
1342
|
-
const result = new Uint8Array(nonce.length + encrypted.length);
|
|
1343
|
-
result.set(nonce);
|
|
1344
|
-
result.set(encrypted, nonce.length);
|
|
1345
|
-
return result;
|
|
1346
|
-
}
|
|
1347
|
-
function decryptLegacy(data, secret) {
|
|
1348
|
-
const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
|
|
1349
|
-
const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
|
|
1350
|
-
const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
|
|
1351
|
-
if (!decrypted) {
|
|
1352
|
-
return null;
|
|
1353
|
-
}
|
|
1354
|
-
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
1355
|
-
}
|
|
1356
|
-
function decryptBlob(bundle, key) {
|
|
1357
|
-
if (bundle.length < tweetnacl.secretbox.nonceLength + 16) {
|
|
1358
|
-
return null;
|
|
1359
|
-
}
|
|
1360
|
-
const nonce = bundle.slice(0, tweetnacl.secretbox.nonceLength);
|
|
1361
|
-
const ciphertext = bundle.slice(tweetnacl.secretbox.nonceLength);
|
|
1362
|
-
const decrypted = tweetnacl.secretbox.open(ciphertext, nonce, key);
|
|
1363
|
-
if (!decrypted) {
|
|
1364
|
-
return null;
|
|
1365
|
-
}
|
|
1366
|
-
return new Uint8Array(decrypted);
|
|
1367
|
-
}
|
|
1368
|
-
function encryptWithDataKey(data, dataKey) {
|
|
1369
|
-
const nonce = getRandomBytes(12);
|
|
1370
|
-
const cipher = createCipheriv("aes-256-gcm", dataKey, nonce);
|
|
1371
|
-
const plaintext = new TextEncoder().encode(JSON.stringify(data));
|
|
1372
|
-
const encrypted = Buffer.concat([
|
|
1373
|
-
cipher.update(plaintext),
|
|
1374
|
-
cipher.final()
|
|
1375
|
-
]);
|
|
1376
|
-
const authTag = cipher.getAuthTag();
|
|
1377
|
-
const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
|
|
1378
|
-
bundle.set([0], 0);
|
|
1379
|
-
bundle.set(nonce, 1);
|
|
1380
|
-
bundle.set(new Uint8Array(encrypted), 13);
|
|
1381
|
-
bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
|
|
1382
|
-
return bundle;
|
|
1383
|
-
}
|
|
1384
|
-
function decryptWithDataKey(bundle, dataKey) {
|
|
1385
|
-
if (bundle.length < 1) {
|
|
1386
|
-
return null;
|
|
1387
|
-
}
|
|
1388
|
-
if (bundle[0] !== 0) {
|
|
1389
|
-
return null;
|
|
1390
|
-
}
|
|
1391
|
-
if (bundle.length < 12 + 16 + 1) {
|
|
1392
|
-
return null;
|
|
1393
|
-
}
|
|
1394
|
-
const nonce = bundle.slice(1, 13);
|
|
1395
|
-
const authTag = bundle.slice(bundle.length - 16);
|
|
1396
|
-
const ciphertext = bundle.slice(13, bundle.length - 16);
|
|
1397
|
-
try {
|
|
1398
|
-
const decipher = createDecipheriv("aes-256-gcm", dataKey, nonce);
|
|
1399
|
-
decipher.setAuthTag(authTag);
|
|
1400
|
-
const decrypted = Buffer.concat([
|
|
1401
|
-
decipher.update(ciphertext),
|
|
1402
|
-
decipher.final()
|
|
1403
|
-
]);
|
|
1404
|
-
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
1405
|
-
} catch (error) {
|
|
1406
|
-
return null;
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
function encrypt(key, variant, data) {
|
|
1410
|
-
if (variant === "legacy") {
|
|
1411
|
-
return encryptLegacy(data, key);
|
|
1412
|
-
} else {
|
|
1413
|
-
return encryptWithDataKey(data, key);
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
function decrypt(key, variant, data) {
|
|
1417
|
-
if (variant === "legacy") {
|
|
1418
|
-
return decryptLegacy(data, key);
|
|
1419
|
-
} else {
|
|
1420
|
-
return decryptWithDataKey(data, key);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
739
|
async function delay(ms) {
|
|
1425
740
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1426
741
|
}
|
|
@@ -1693,7 +1008,7 @@ function projectPath() {
|
|
|
1693
1008
|
}
|
|
1694
1009
|
|
|
1695
1010
|
function run$1(args, options) {
|
|
1696
|
-
const RUNNER_PATH = resolve(join
|
|
1011
|
+
const RUNNER_PATH = resolve(join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
|
|
1697
1012
|
return new Promise((resolve2, reject) => {
|
|
1698
1013
|
const child = spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
|
|
1699
1014
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -1724,7 +1039,7 @@ function run$1(args, options) {
|
|
|
1724
1039
|
function getBinaryPath() {
|
|
1725
1040
|
const platformName = platform();
|
|
1726
1041
|
const binaryName = platformName === "win32" ? "difft.exe" : "difft";
|
|
1727
|
-
return resolve(join
|
|
1042
|
+
return resolve(join(projectPath(), "tools", "unpacked", binaryName));
|
|
1728
1043
|
}
|
|
1729
1044
|
function run(args, options) {
|
|
1730
1045
|
const binaryPath = getBinaryPath();
|
|
@@ -1836,7 +1151,7 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
|
|
|
1836
1151
|
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1837
1152
|
await Promise.all(entries.map(async (entry) => {
|
|
1838
1153
|
if (!entry.isFile() || !/^\..+\.[A-Za-z0-9_-]{8,80}\.part$/.test(entry.name)) return;
|
|
1839
|
-
const path = join
|
|
1154
|
+
const path = join(dir, entry.name);
|
|
1840
1155
|
const fileStat = await stat(path).catch(() => null);
|
|
1841
1156
|
if (fileStat && fileStat.mtimeMs < cutoff) await unlink(path).catch(() => {
|
|
1842
1157
|
});
|
|
@@ -1846,13 +1161,13 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
|
|
|
1846
1161
|
let tempPath = null;
|
|
1847
1162
|
try {
|
|
1848
1163
|
const buffer = decodeUploadBase64(data.content, MAX_USER_UPLOAD_BYTES);
|
|
1849
|
-
const dir = join
|
|
1850
|
-
await mkdir
|
|
1164
|
+
const dir = join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
|
|
1165
|
+
await mkdir(dir, { recursive: true });
|
|
1851
1166
|
const name = safeFileName(data.name);
|
|
1852
1167
|
const token = randomUUID().replace(/-/g, "").slice(0, 12);
|
|
1853
|
-
const target = join
|
|
1854
|
-
tempPath = join
|
|
1855
|
-
await writeFile
|
|
1168
|
+
const target = join(dir, uniqueFileName(name, token));
|
|
1169
|
+
tempPath = join(dir, `.${name}.legacy_${randomUUID().replace(/-/g, "")}.part`);
|
|
1170
|
+
await writeFile(tempPath, buffer, { flag: "wx" });
|
|
1856
1171
|
await rename(tempPath, target);
|
|
1857
1172
|
tempPath = null;
|
|
1858
1173
|
return { success: true, path: target, pathQuoteStyle, size: buffer.length };
|
|
@@ -1872,13 +1187,13 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
|
|
|
1872
1187
|
}
|
|
1873
1188
|
if (active.has(uploadId)) throw new Error("Upload id is already active");
|
|
1874
1189
|
if (active.size >= MAX_ACTIVE_UPLOADS) throw new Error("Too many active uploads");
|
|
1875
|
-
const dir = join
|
|
1876
|
-
await mkdir
|
|
1190
|
+
const dir = join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
|
|
1191
|
+
await mkdir(dir, { recursive: true });
|
|
1877
1192
|
await sweepExpiredFiles(dir);
|
|
1878
1193
|
const name = safeFileName(data.name);
|
|
1879
|
-
const targetPath = join
|
|
1880
|
-
const tempPath = join
|
|
1881
|
-
await writeFile
|
|
1194
|
+
const targetPath = join(dir, uniqueFileName(name, randomUUID().replace(/-/g, "").slice(0, 12)));
|
|
1195
|
+
const tempPath = join(dir, `.${name}.${uploadId}.part`);
|
|
1196
|
+
await writeFile(tempPath, Buffer.alloc(0), { flag: "wx" });
|
|
1882
1197
|
active.set(uploadId, { tempPath, targetPath, totalSize: data.totalSize, received: 0, touchedAt: now() });
|
|
1883
1198
|
return { success: true, size: 0 };
|
|
1884
1199
|
}
|
|
@@ -2014,7 +1329,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
|
|
|
2014
1329
|
if (data.expectedHash !== null && data.expectedHash !== void 0) {
|
|
2015
1330
|
try {
|
|
2016
1331
|
const existingBuffer = await readFile(validation.resolvedPath);
|
|
2017
|
-
const existingHash = createHash
|
|
1332
|
+
const existingHash = createHash("sha256").update(existingBuffer).digest("hex");
|
|
2018
1333
|
if (existingHash !== data.expectedHash) {
|
|
2019
1334
|
return {
|
|
2020
1335
|
success: false,
|
|
@@ -2046,8 +1361,8 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
|
|
|
2046
1361
|
}
|
|
2047
1362
|
}
|
|
2048
1363
|
const buffer = Buffer.from(data.content, "base64");
|
|
2049
|
-
await writeFile
|
|
2050
|
-
const hash = createHash
|
|
1364
|
+
await writeFile(validation.resolvedPath, buffer);
|
|
1365
|
+
const hash = createHash("sha256").update(buffer).digest("hex");
|
|
2051
1366
|
return { success: true, hash };
|
|
2052
1367
|
} catch (error) {
|
|
2053
1368
|
logger.debug("Failed to write file:", error);
|
|
@@ -2068,7 +1383,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
|
|
|
2068
1383
|
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
2069
1384
|
const directoryEntries = await Promise.all(
|
|
2070
1385
|
entries.map(async (entry) => {
|
|
2071
|
-
const fullPath = join
|
|
1386
|
+
const fullPath = join(directoryPath, entry.name);
|
|
2072
1387
|
let type = "other";
|
|
2073
1388
|
let size;
|
|
2074
1389
|
let modified;
|
|
@@ -2125,10 +1440,10 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
|
|
|
2125
1440
|
await Promise.all(
|
|
2126
1441
|
entries.map(async (entry) => {
|
|
2127
1442
|
if (entry.isSymbolicLink()) {
|
|
2128
|
-
logger.debug(`Skipping symlink: ${join
|
|
1443
|
+
logger.debug(`Skipping symlink: ${join(path, entry.name)}`);
|
|
2129
1444
|
return;
|
|
2130
1445
|
}
|
|
2131
|
-
const childPath = join
|
|
1446
|
+
const childPath = join(path, entry.name);
|
|
2132
1447
|
const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
|
|
2133
1448
|
if (childNode) {
|
|
2134
1449
|
children.push(childNode);
|
|
@@ -4423,10 +3738,10 @@ function normalizeFsPath(input, homeDir) {
|
|
|
4423
3738
|
if (p === "~") {
|
|
4424
3739
|
p = homeDir;
|
|
4425
3740
|
} else if (p.startsWith("~/")) {
|
|
4426
|
-
p = join(homeDir, p.slice(2));
|
|
3741
|
+
p = join$1(homeDir, p.slice(2));
|
|
4427
3742
|
}
|
|
4428
3743
|
if (!isAbsolute(p)) {
|
|
4429
|
-
p = join(homeDir, p);
|
|
3744
|
+
p = join$1(homeDir, p);
|
|
4430
3745
|
}
|
|
4431
3746
|
return resolve$1(p);
|
|
4432
3747
|
}
|
|
@@ -4482,7 +3797,7 @@ async function fsList(inputPath, homeDir = homedir()) {
|
|
|
4482
3797
|
dirents.map(async (d) => {
|
|
4483
3798
|
const entry = { name: d.name, type: entryTypeOf(d) };
|
|
4484
3799
|
try {
|
|
4485
|
-
const s = await lstat(join(path, d.name));
|
|
3800
|
+
const s = await lstat(join$1(path, d.name));
|
|
4486
3801
|
entry.size = s.size;
|
|
4487
3802
|
entry.mtimeMs = Math.round(s.mtimeMs);
|
|
4488
3803
|
} catch {
|
|
@@ -4539,391 +3854,6 @@ function registerFsHandlers(rpcHandlerManager) {
|
|
|
4539
3854
|
});
|
|
4540
3855
|
}
|
|
4541
3856
|
|
|
4542
|
-
const DEFAULT_SERVER_URL = "https://veryhappy.dev";
|
|
4543
|
-
const LEGACY_CLOUD_SERVER_URL = "https://happy.mereith.com";
|
|
4544
|
-
const CLOUD_SERVER_URLS = [DEFAULT_SERVER_URL, LEGACY_CLOUD_SERVER_URL];
|
|
4545
|
-
function sameRelay(left, right) {
|
|
4546
|
-
try {
|
|
4547
|
-
return new URL(left).origin === new URL(right).origin;
|
|
4548
|
-
} catch {
|
|
4549
|
-
return left.replace(/\/+$/, "") === right.replace(/\/+$/, "");
|
|
4550
|
-
}
|
|
4551
|
-
}
|
|
4552
|
-
function credentialRelayProblem(credentialServerUrl, configuredServerUrl) {
|
|
4553
|
-
if (credentialServerUrl) {
|
|
4554
|
-
return sameRelay(credentialServerUrl, configuredServerUrl) ? void 0 : `Credentials belong to ${credentialServerUrl}, but the active relay is ${configuredServerUrl}.`;
|
|
4555
|
-
}
|
|
4556
|
-
if (CLOUD_SERVER_URLS.some((cloudUrl) => sameRelay(configuredServerUrl, cloudUrl))) {
|
|
4557
|
-
return void 0;
|
|
4558
|
-
}
|
|
4559
|
-
return `These credentials predate relay tracking, so they cannot be safely reused with ${configuredServerUrl}.`;
|
|
4560
|
-
}
|
|
4561
|
-
|
|
4562
|
-
const SandboxConfigSchema = z.object({
|
|
4563
|
-
enabled: z.boolean().default(false),
|
|
4564
|
-
workspaceRoot: z.string().optional(),
|
|
4565
|
-
sessionIsolation: z.enum(["strict", "workspace", "custom"]).default("workspace"),
|
|
4566
|
-
customWritePaths: z.array(z.string()).default([]),
|
|
4567
|
-
denyReadPaths: z.array(z.string()).default(["~/.ssh", "~/.aws", "~/.gnupg"]),
|
|
4568
|
-
extraWritePaths: z.array(z.string()).default(["/tmp"]),
|
|
4569
|
-
denyWritePaths: z.array(z.string()).default([".env"]),
|
|
4570
|
-
networkMode: z.enum(["blocked", "allowed", "custom"]).default("allowed"),
|
|
4571
|
-
allowedDomains: z.array(z.string()).default([]),
|
|
4572
|
-
deniedDomains: z.array(z.string()).default([]),
|
|
4573
|
-
allowLocalBinding: z.boolean().default(true)
|
|
4574
|
-
});
|
|
4575
|
-
const SUPPORTED_SCHEMA_VERSION = 2;
|
|
4576
|
-
const defaultSettings = {
|
|
4577
|
-
schemaVersion: SUPPORTED_SCHEMA_VERSION,
|
|
4578
|
-
onboardingCompleted: false,
|
|
4579
|
-
sandboxConfig: void 0
|
|
4580
|
-
};
|
|
4581
|
-
function migrateSettings(raw, fromVersion) {
|
|
4582
|
-
let migrated = { ...raw };
|
|
4583
|
-
return migrated;
|
|
4584
|
-
}
|
|
4585
|
-
async function readSettingsOutcome() {
|
|
4586
|
-
if (!existsSync(configuration.settingsFile)) {
|
|
4587
|
-
return { kind: "absent" };
|
|
4588
|
-
}
|
|
4589
|
-
try {
|
|
4590
|
-
return { kind: "ok", settings: await parseSettingsFile() };
|
|
4591
|
-
} catch (error) {
|
|
4592
|
-
return { kind: "unreadable", error: error?.message ?? String(error) };
|
|
4593
|
-
}
|
|
4594
|
-
}
|
|
4595
|
-
async function readSettings() {
|
|
4596
|
-
const outcome = await readSettingsOutcome();
|
|
4597
|
-
if (outcome.kind === "ok") return outcome.settings;
|
|
4598
|
-
if (outcome.kind === "unreadable") {
|
|
4599
|
-
logger.warn(`Failed to read settings: ${outcome.error}`);
|
|
4600
|
-
}
|
|
4601
|
-
return { ...defaultSettings };
|
|
4602
|
-
}
|
|
4603
|
-
async function parseSettingsFile() {
|
|
4604
|
-
const content = await readFile$1(configuration.settingsFile, "utf8");
|
|
4605
|
-
const raw = JSON.parse(content);
|
|
4606
|
-
const schemaVersion = raw.schemaVersion ?? 1;
|
|
4607
|
-
if (schemaVersion > SUPPORTED_SCHEMA_VERSION) {
|
|
4608
|
-
logger.warn(
|
|
4609
|
-
`\u26A0\uFE0F Settings schema v${schemaVersion} > supported v${SUPPORTED_SCHEMA_VERSION}. Update happy-cli for full functionality.`
|
|
4610
|
-
);
|
|
4611
|
-
}
|
|
4612
|
-
const migrated = migrateSettings(raw);
|
|
4613
|
-
if (migrated.sandboxConfig !== void 0) {
|
|
4614
|
-
try {
|
|
4615
|
-
migrated.sandboxConfig = SandboxConfigSchema.parse(migrated.sandboxConfig);
|
|
4616
|
-
} catch (error) {
|
|
4617
|
-
logger.warn(`\u26A0\uFE0F Invalid sandbox config - skipping. Error: ${error.message}`);
|
|
4618
|
-
migrated.sandboxConfig = void 0;
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
return { ...defaultSettings, ...migrated };
|
|
4622
|
-
}
|
|
4623
|
-
async function writeSettings(settings) {
|
|
4624
|
-
await ensurePrivateDirectory(configuration.happyHomeDir);
|
|
4625
|
-
const settingsWithVersion = {
|
|
4626
|
-
...settings,
|
|
4627
|
-
schemaVersion: settings.schemaVersion ?? SUPPORTED_SCHEMA_VERSION
|
|
4628
|
-
};
|
|
4629
|
-
await writePrivateFile(configuration.settingsFile, JSON.stringify(settingsWithVersion, null, 2));
|
|
4630
|
-
}
|
|
4631
|
-
async function updateSettings(updater) {
|
|
4632
|
-
const LOCK_RETRY_INTERVAL_MS = 100;
|
|
4633
|
-
const MAX_LOCK_ATTEMPTS = 50;
|
|
4634
|
-
const STALE_LOCK_TIMEOUT_MS = 1e4;
|
|
4635
|
-
const lockFile = configuration.settingsFile + ".lock";
|
|
4636
|
-
const tmpFile = configuration.settingsFile + ".tmp";
|
|
4637
|
-
let fileHandle;
|
|
4638
|
-
let attempts = 0;
|
|
4639
|
-
while (attempts < MAX_LOCK_ATTEMPTS) {
|
|
4640
|
-
try {
|
|
4641
|
-
fileHandle = await open(lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, PRIVATE_FILE_MODE);
|
|
4642
|
-
break;
|
|
4643
|
-
} catch (err) {
|
|
4644
|
-
if (err.code === "EEXIST") {
|
|
4645
|
-
attempts++;
|
|
4646
|
-
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
|
|
4647
|
-
try {
|
|
4648
|
-
const stats = await stat$1(lockFile);
|
|
4649
|
-
if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
|
|
4650
|
-
await unlink$1(lockFile).catch(() => {
|
|
4651
|
-
});
|
|
4652
|
-
}
|
|
4653
|
-
} catch {
|
|
4654
|
-
}
|
|
4655
|
-
} else {
|
|
4656
|
-
throw err;
|
|
4657
|
-
}
|
|
4658
|
-
}
|
|
4659
|
-
}
|
|
4660
|
-
if (!fileHandle) {
|
|
4661
|
-
throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
|
|
4662
|
-
}
|
|
4663
|
-
try {
|
|
4664
|
-
const outcome = await readSettingsOutcome();
|
|
4665
|
-
if (outcome.kind === "unreadable") {
|
|
4666
|
-
throw new Error(
|
|
4667
|
-
`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).`
|
|
4668
|
-
);
|
|
4669
|
-
}
|
|
4670
|
-
const current = outcome.kind === "ok" ? outcome.settings : { ...defaultSettings };
|
|
4671
|
-
const updated = await updater(current);
|
|
4672
|
-
await ensurePrivateDirectory(configuration.happyHomeDir);
|
|
4673
|
-
await writePrivateFile(tmpFile, JSON.stringify(updated, null, 2));
|
|
4674
|
-
await rename$1(tmpFile, configuration.settingsFile);
|
|
4675
|
-
await hardenPrivateFile(configuration.settingsFile);
|
|
4676
|
-
return updated;
|
|
4677
|
-
} finally {
|
|
4678
|
-
await fileHandle.close();
|
|
4679
|
-
await unlink$1(lockFile).catch(() => {
|
|
4680
|
-
});
|
|
4681
|
-
}
|
|
4682
|
-
}
|
|
4683
|
-
const credentialsSchema = z.object({
|
|
4684
|
-
token: z.string(),
|
|
4685
|
-
authServerUrl: z.string().optional(),
|
|
4686
|
-
secret: z.string().base64().nullish(),
|
|
4687
|
-
// Legacy
|
|
4688
|
-
encryption: z.object({
|
|
4689
|
-
publicKey: z.string().base64(),
|
|
4690
|
-
machineKey: z.string().base64()
|
|
4691
|
-
}).nullish()
|
|
4692
|
-
});
|
|
4693
|
-
async function readCredentials() {
|
|
4694
|
-
if (!existsSync(configuration.privateKeyFile)) {
|
|
4695
|
-
return null;
|
|
4696
|
-
}
|
|
4697
|
-
try {
|
|
4698
|
-
const keyBase64 = await readFile$1(configuration.privateKeyFile, "utf8");
|
|
4699
|
-
const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
|
|
4700
|
-
if (credentials.secret) {
|
|
4701
|
-
return {
|
|
4702
|
-
token: credentials.token,
|
|
4703
|
-
authServerUrl: credentials.authServerUrl,
|
|
4704
|
-
encryption: {
|
|
4705
|
-
type: "legacy",
|
|
4706
|
-
secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
|
|
4707
|
-
}
|
|
4708
|
-
};
|
|
4709
|
-
} else if (credentials.encryption) {
|
|
4710
|
-
return {
|
|
4711
|
-
token: credentials.token,
|
|
4712
|
-
authServerUrl: credentials.authServerUrl,
|
|
4713
|
-
encryption: {
|
|
4714
|
-
type: "dataKey",
|
|
4715
|
-
publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
|
|
4716
|
-
machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
|
|
4717
|
-
}
|
|
4718
|
-
};
|
|
4719
|
-
}
|
|
4720
|
-
} catch {
|
|
4721
|
-
return null;
|
|
4722
|
-
}
|
|
4723
|
-
return null;
|
|
4724
|
-
}
|
|
4725
|
-
async function readCredentialsForConfiguredRelay() {
|
|
4726
|
-
const credentials = await readCredentials();
|
|
4727
|
-
if (!credentials) return null;
|
|
4728
|
-
const relayProblem = credentialRelayProblem(credentials.authServerUrl, configuration.serverUrl);
|
|
4729
|
-
if (relayProblem) {
|
|
4730
|
-
throw new Error(
|
|
4731
|
-
`${relayProblem} Use a separate HAPPY_HOME_DIR for this relay, or run \`very-happy auth login --force\` before making relay requests.`
|
|
4732
|
-
);
|
|
4733
|
-
}
|
|
4734
|
-
return credentials;
|
|
4735
|
-
}
|
|
4736
|
-
async function writeCredentialsLegacy(credentials) {
|
|
4737
|
-
await ensurePrivateDirectory(configuration.happyHomeDir);
|
|
4738
|
-
await writePrivateFile(configuration.privateKeyFile, JSON.stringify({
|
|
4739
|
-
secret: encodeBase64(credentials.secret),
|
|
4740
|
-
token: credentials.token,
|
|
4741
|
-
authServerUrl: configuration.serverUrl
|
|
4742
|
-
}, null, 2));
|
|
4743
|
-
}
|
|
4744
|
-
async function writeCredentialsDataKey(credentials) {
|
|
4745
|
-
await ensurePrivateDirectory(configuration.happyHomeDir);
|
|
4746
|
-
await writePrivateFile(configuration.privateKeyFile, JSON.stringify({
|
|
4747
|
-
encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
|
|
4748
|
-
token: credentials.token,
|
|
4749
|
-
authServerUrl: configuration.serverUrl
|
|
4750
|
-
}, null, 2));
|
|
4751
|
-
}
|
|
4752
|
-
async function clearCredentials() {
|
|
4753
|
-
if (existsSync(configuration.privateKeyFile)) {
|
|
4754
|
-
await unlink$1(configuration.privateKeyFile);
|
|
4755
|
-
}
|
|
4756
|
-
}
|
|
4757
|
-
async function clearMachineId() {
|
|
4758
|
-
await updateSettings((settings) => ({
|
|
4759
|
-
...settings,
|
|
4760
|
-
machineId: void 0
|
|
4761
|
-
}));
|
|
4762
|
-
}
|
|
4763
|
-
async function readDaemonState() {
|
|
4764
|
-
try {
|
|
4765
|
-
if (!existsSync(configuration.daemonStateFile)) {
|
|
4766
|
-
return null;
|
|
4767
|
-
}
|
|
4768
|
-
const content = await readFile$1(configuration.daemonStateFile, "utf-8");
|
|
4769
|
-
return JSON.parse(content);
|
|
4770
|
-
} catch (error) {
|
|
4771
|
-
console.error(`[PERSISTENCE] Daemon state file corrupted: ${configuration.daemonStateFile}`, error);
|
|
4772
|
-
return null;
|
|
4773
|
-
}
|
|
4774
|
-
}
|
|
4775
|
-
function writeDaemonState(state) {
|
|
4776
|
-
writePrivateFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2));
|
|
4777
|
-
}
|
|
4778
|
-
async function clearDaemonState() {
|
|
4779
|
-
if (existsSync(configuration.daemonStateFile)) {
|
|
4780
|
-
await unlink$1(configuration.daemonStateFile);
|
|
4781
|
-
}
|
|
4782
|
-
if (existsSync(configuration.daemonLockFile)) {
|
|
4783
|
-
try {
|
|
4784
|
-
await unlink$1(configuration.daemonLockFile);
|
|
4785
|
-
} catch {
|
|
4786
|
-
}
|
|
4787
|
-
}
|
|
4788
|
-
}
|
|
4789
|
-
async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
|
|
4790
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
4791
|
-
try {
|
|
4792
|
-
const fileHandle = await open(
|
|
4793
|
-
configuration.daemonLockFile,
|
|
4794
|
-
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
|
|
4795
|
-
PRIVATE_FILE_MODE
|
|
4796
|
-
);
|
|
4797
|
-
await fileHandle.writeFile(String(process.pid));
|
|
4798
|
-
return fileHandle;
|
|
4799
|
-
} catch (error) {
|
|
4800
|
-
if (error.code === "EEXIST") {
|
|
4801
|
-
try {
|
|
4802
|
-
const lockPid = readFileSync(configuration.daemonLockFile, "utf-8").trim();
|
|
4803
|
-
if (lockPid && !isNaN(Number(lockPid))) {
|
|
4804
|
-
try {
|
|
4805
|
-
process.kill(Number(lockPid), 0);
|
|
4806
|
-
} catch {
|
|
4807
|
-
unlinkSync(configuration.daemonLockFile);
|
|
4808
|
-
continue;
|
|
4809
|
-
}
|
|
4810
|
-
}
|
|
4811
|
-
} catch {
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
if (attempt === maxAttempts) {
|
|
4815
|
-
return null;
|
|
4816
|
-
}
|
|
4817
|
-
const delayMs = attempt * delayIncrementMs;
|
|
4818
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
4819
|
-
}
|
|
4820
|
-
}
|
|
4821
|
-
return null;
|
|
4822
|
-
}
|
|
4823
|
-
async function releaseDaemonLock(lockHandle) {
|
|
4824
|
-
try {
|
|
4825
|
-
await lockHandle.close();
|
|
4826
|
-
} catch {
|
|
4827
|
-
}
|
|
4828
|
-
try {
|
|
4829
|
-
if (existsSync(configuration.daemonLockFile)) {
|
|
4830
|
-
unlinkSync(configuration.daemonLockFile);
|
|
4831
|
-
}
|
|
4832
|
-
} catch {
|
|
4833
|
-
}
|
|
4834
|
-
}
|
|
4835
|
-
const SESSION_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
4836
|
-
function readPersistedSessions() {
|
|
4837
|
-
try {
|
|
4838
|
-
if (!existsSync(configuration.sessionsFile)) return {};
|
|
4839
|
-
const data = JSON.parse(readFileSync(configuration.sessionsFile, "utf-8"));
|
|
4840
|
-
if (!data?.sessions || typeof data.sessions !== "object") return {};
|
|
4841
|
-
const now = Date.now();
|
|
4842
|
-
const sessions = {};
|
|
4843
|
-
for (const [id, session] of Object.entries(data.sessions)) {
|
|
4844
|
-
if (now - session.savedAt < SESSION_MAX_AGE_MS) {
|
|
4845
|
-
sessions[id] = session;
|
|
4846
|
-
}
|
|
4847
|
-
}
|
|
4848
|
-
return sessions;
|
|
4849
|
-
} catch {
|
|
4850
|
-
return {};
|
|
4851
|
-
}
|
|
4852
|
-
}
|
|
4853
|
-
function readTrackedClaudeSessionIds() {
|
|
4854
|
-
try {
|
|
4855
|
-
if (!existsSync(configuration.sessionsFile)) return [];
|
|
4856
|
-
const data = JSON.parse(readFileSync(configuration.sessionsFile, "utf-8"));
|
|
4857
|
-
if (!data?.sessions || typeof data.sessions !== "object") return [];
|
|
4858
|
-
const ids = /* @__PURE__ */ new Set();
|
|
4859
|
-
for (const session of Object.values(data.sessions)) {
|
|
4860
|
-
const metadata = (session ?? {}).metadata;
|
|
4861
|
-
for (const id of [metadata?.claudeSessionId, metadata?.importedFromClaudeSessionId]) {
|
|
4862
|
-
if (typeof id === "string" && id.length > 0) ids.add(id.toLowerCase());
|
|
4863
|
-
}
|
|
4864
|
-
}
|
|
4865
|
-
return [...ids];
|
|
4866
|
-
} catch {
|
|
4867
|
-
return [];
|
|
4868
|
-
}
|
|
4869
|
-
}
|
|
4870
|
-
function persistSession(sessionId, session) {
|
|
4871
|
-
try {
|
|
4872
|
-
const existing = readPersistedSessions();
|
|
4873
|
-
existing[sessionId] = session;
|
|
4874
|
-
const tmpFile = configuration.sessionsFile + ".tmp";
|
|
4875
|
-
writePrivateFileSync(tmpFile, JSON.stringify({ sessions: existing }, null, 2));
|
|
4876
|
-
renameSync(tmpFile, configuration.sessionsFile);
|
|
4877
|
-
hardenPrivateFileSync(configuration.sessionsFile);
|
|
4878
|
-
} catch (error) {
|
|
4879
|
-
logger.debug(`[PERSISTENCE] Failed to persist session ${sessionId}:`, error);
|
|
4880
|
-
}
|
|
4881
|
-
}
|
|
4882
|
-
function deletePersistedSessions(sessionIds) {
|
|
4883
|
-
try {
|
|
4884
|
-
const existing = readPersistedSessions();
|
|
4885
|
-
let changed = false;
|
|
4886
|
-
for (const id of sessionIds) {
|
|
4887
|
-
if (id in existing) {
|
|
4888
|
-
delete existing[id];
|
|
4889
|
-
changed = true;
|
|
4890
|
-
}
|
|
4891
|
-
}
|
|
4892
|
-
if (!changed) return;
|
|
4893
|
-
const tmpFile = configuration.sessionsFile + ".tmp";
|
|
4894
|
-
writePrivateFileSync(tmpFile, JSON.stringify({ sessions: existing }, null, 2));
|
|
4895
|
-
renameSync(tmpFile, configuration.sessionsFile);
|
|
4896
|
-
hardenPrivateFileSync(configuration.sessionsFile);
|
|
4897
|
-
} catch (error) {
|
|
4898
|
-
logger.debug(`[PERSISTENCE] Failed to delete persisted sessions:`, error);
|
|
4899
|
-
}
|
|
4900
|
-
}
|
|
4901
|
-
|
|
4902
|
-
var persistence = /*#__PURE__*/Object.freeze({
|
|
4903
|
-
__proto__: null,
|
|
4904
|
-
SUPPORTED_SCHEMA_VERSION: SUPPORTED_SCHEMA_VERSION,
|
|
4905
|
-
SandboxConfigSchema: SandboxConfigSchema,
|
|
4906
|
-
acquireDaemonLock: acquireDaemonLock,
|
|
4907
|
-
clearCredentials: clearCredentials,
|
|
4908
|
-
clearDaemonState: clearDaemonState,
|
|
4909
|
-
clearMachineId: clearMachineId,
|
|
4910
|
-
deletePersistedSessions: deletePersistedSessions,
|
|
4911
|
-
persistSession: persistSession,
|
|
4912
|
-
readCredentials: readCredentials,
|
|
4913
|
-
readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay,
|
|
4914
|
-
readDaemonState: readDaemonState,
|
|
4915
|
-
readPersistedSessions: readPersistedSessions,
|
|
4916
|
-
readSettings: readSettings,
|
|
4917
|
-
readSettingsOutcome: readSettingsOutcome,
|
|
4918
|
-
readTrackedClaudeSessionIds: readTrackedClaudeSessionIds,
|
|
4919
|
-
releaseDaemonLock: releaseDaemonLock,
|
|
4920
|
-
updateSettings: updateSettings,
|
|
4921
|
-
writeCredentialsDataKey: writeCredentialsDataKey,
|
|
4922
|
-
writeCredentialsLegacy: writeCredentialsLegacy,
|
|
4923
|
-
writeDaemonState: writeDaemonState,
|
|
4924
|
-
writeSettings: writeSettings
|
|
4925
|
-
});
|
|
4926
|
-
|
|
4927
3857
|
const TODO_LIST_MAX_ITEMS = 500;
|
|
4928
3858
|
const TODO_TITLE_MAX_CHARS = 500;
|
|
4929
3859
|
const FIELD_MAX_CHARS = 200;
|
|
@@ -5282,14 +4212,14 @@ function scrubTmuxClientEnv(env) {
|
|
|
5282
4212
|
}
|
|
5283
4213
|
|
|
5284
4214
|
function getClaudeConfigDir() {
|
|
5285
|
-
return process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
4215
|
+
return process.env.CLAUDE_CONFIG_DIR || join$1(homedir(), ".claude");
|
|
5286
4216
|
}
|
|
5287
4217
|
function getClaudeProjectsRoot() {
|
|
5288
|
-
return join(getClaudeConfigDir(), "projects");
|
|
4218
|
+
return join$1(getClaudeConfigDir(), "projects");
|
|
5289
4219
|
}
|
|
5290
4220
|
function getProjectPath(workingDirectory) {
|
|
5291
4221
|
const projectId = resolve$1(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
5292
|
-
return join(getClaudeProjectsRoot(), projectId);
|
|
4222
|
+
return join$1(getClaudeProjectsRoot(), projectId);
|
|
5293
4223
|
}
|
|
5294
4224
|
|
|
5295
4225
|
var xtermHeadless = {};
|
|
@@ -10521,7 +9451,7 @@ function pruneTombstones(map, now, ttlMs = TOMBSTONE_TTL_MS) {
|
|
|
10521
9451
|
return out;
|
|
10522
9452
|
}
|
|
10523
9453
|
function tombstoneFile() {
|
|
10524
|
-
return join(configuration.happyHomeDir, "terminal-tombstones.json");
|
|
9454
|
+
return join$1(configuration.happyHomeDir, "terminal-tombstones.json");
|
|
10525
9455
|
}
|
|
10526
9456
|
function loadTombstones() {
|
|
10527
9457
|
try {
|
|
@@ -10540,7 +9470,7 @@ function saveTombstones(map) {
|
|
|
10540
9470
|
}
|
|
10541
9471
|
}
|
|
10542
9472
|
function closedTerminalsFile() {
|
|
10543
|
-
return join(configuration.happyHomeDir, "closed-terminals.json");
|
|
9473
|
+
return join$1(configuration.happyHomeDir, "closed-terminals.json");
|
|
10544
9474
|
}
|
|
10545
9475
|
function loadClosedTerminals() {
|
|
10546
9476
|
try {
|
|
@@ -10558,7 +9488,7 @@ function saveClosedTerminals(list) {
|
|
|
10558
9488
|
}
|
|
10559
9489
|
}
|
|
10560
9490
|
function liveTerminalsFile() {
|
|
10561
|
-
return join(configuration.happyHomeDir, "live-terminals.json");
|
|
9491
|
+
return join$1(configuration.happyHomeDir, "live-terminals.json");
|
|
10562
9492
|
}
|
|
10563
9493
|
function loadLiveSnapshot() {
|
|
10564
9494
|
try {
|
|
@@ -10833,7 +9763,7 @@ function tmuxSupportsEnvFlag() {
|
|
|
10833
9763
|
}
|
|
10834
9764
|
const PASTE_FILE_TTL_MS = 5e3;
|
|
10835
9765
|
function pasteSpoolDir() {
|
|
10836
|
-
const dir = join(configuration.happyHomeDir, "paste-spool");
|
|
9766
|
+
const dir = join$1(configuration.happyHomeDir, "paste-spool");
|
|
10837
9767
|
try {
|
|
10838
9768
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
10839
9769
|
} catch {
|
|
@@ -11662,7 +10592,7 @@ class WebTerminalManager {
|
|
|
11662
10592
|
return false;
|
|
11663
10593
|
}
|
|
11664
10594
|
},
|
|
11665
|
-
conversationExists: (cwd, claudeSessionId) => existsSync(join(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
|
|
10595
|
+
conversationExists: (cwd, claudeSessionId) => existsSync(join$1(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
|
|
11666
10596
|
// B-273: only consulted for records that were attach terminals.
|
|
11667
10597
|
// Unbounded: the panel's 50-row cap must not hide a restore target.
|
|
11668
10598
|
userSessions: record.attachTmux ? this.listUserTmuxSessions(Infinity) : [],
|
|
@@ -12907,7 +11837,7 @@ function detectPosix() {
|
|
|
12907
11837
|
const codex = commandExists("codex");
|
|
12908
11838
|
const gemini = commandExists("gemini");
|
|
12909
11839
|
const openclawCommand = commandExists("openclaw");
|
|
12910
|
-
const openclawConfig = existsSync$1(join
|
|
11840
|
+
const openclawConfig = existsSync$1(join(os.homedir(), ".openclaw", "openclaw.json"));
|
|
12911
11841
|
const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
|
|
12912
11842
|
const openclaw = openclawCommand || openclawConfig || openclawEnv;
|
|
12913
11843
|
const pi = commandExists("pi") && commandExists("pi-acp");
|
|
@@ -12919,7 +11849,7 @@ function detectWindows() {
|
|
|
12919
11849
|
const codex = checkCommand("codex");
|
|
12920
11850
|
const gemini = checkCommand("gemini");
|
|
12921
11851
|
const openclawCommand = checkCommand("openclaw");
|
|
12922
|
-
const openclawConfig = existsSync$1(join
|
|
11852
|
+
const openclawConfig = existsSync$1(join(process.env.USERPROFILE || os.homedir(), ".openclaw", "openclaw.json"));
|
|
12923
11853
|
const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
|
|
12924
11854
|
const openclaw = openclawCommand || openclawConfig || openclawEnv;
|
|
12925
11855
|
const pi = checkCommand("pi") && checkCommand("pi-acp");
|
|
@@ -12954,7 +11884,7 @@ function deriveKey(master, usage, path) {
|
|
|
12954
11884
|
}
|
|
12955
11885
|
function deriveContentKeyPair(secret) {
|
|
12956
11886
|
const seed = deriveKey(secret, "Happy EnCoder", ["content"]);
|
|
12957
|
-
const hashedSeed = new Uint8Array(createHash("sha512").update(seed).digest());
|
|
11887
|
+
const hashedSeed = new Uint8Array(createHash$1("sha512").update(seed).digest());
|
|
12958
11888
|
const secretKey = hashedSeed.slice(0, 32);
|
|
12959
11889
|
const keyPair = tweetnacl.box.keyPair.fromSecretKey(secretKey);
|
|
12960
11890
|
return {
|
|
@@ -12963,7 +11893,7 @@ function deriveContentKeyPair(secret) {
|
|
|
12963
11893
|
};
|
|
12964
11894
|
}
|
|
12965
11895
|
function getLocalHappyAgentCredentialPath(happyHomeDir = configuration.happyHomeDir) {
|
|
12966
|
-
return join(happyHomeDir, "agent.key");
|
|
11896
|
+
return join$1(happyHomeDir, "agent.key");
|
|
12967
11897
|
}
|
|
12968
11898
|
function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDir, serverUrl = configuration.serverUrl) {
|
|
12969
11899
|
const credentialPath = getLocalHappyAgentCredentialPath(happyHomeDir);
|
|
@@ -13021,7 +11951,7 @@ async function listClaudeProjectDirs(projectsRoot) {
|
|
|
13021
11951
|
}
|
|
13022
11952
|
const dirs = [];
|
|
13023
11953
|
for (const name of names) {
|
|
13024
|
-
const full = join(projectsRoot, name);
|
|
11954
|
+
const full = join$1(projectsRoot, name);
|
|
13025
11955
|
try {
|
|
13026
11956
|
if ((await stat$1(full)).isDirectory()) dirs.push(full);
|
|
13027
11957
|
} catch {
|
|
@@ -13042,7 +11972,7 @@ async function collectCandidates(projectDirs, exclude) {
|
|
|
13042
11972
|
if (!name.endsWith(".jsonl")) continue;
|
|
13043
11973
|
const id = name.slice(0, -".jsonl".length);
|
|
13044
11974
|
if (!UUID_RE$1.test(id) || exclude.has(id.toLowerCase())) continue;
|
|
13045
|
-
const path = join(dir, name);
|
|
11975
|
+
const path = join$1(dir, name);
|
|
13046
11976
|
try {
|
|
13047
11977
|
const s = await stat$1(path);
|
|
13048
11978
|
if (!s.isFile() || s.size === 0) continue;
|
|
@@ -13191,7 +12121,7 @@ class ForkSourceMissingError extends Error {
|
|
|
13191
12121
|
}
|
|
13192
12122
|
}
|
|
13193
12123
|
function jsonlPath(projectDir, sessionId) {
|
|
13194
|
-
return join(projectDir, `${sessionId}.jsonl`);
|
|
12124
|
+
return join$1(projectDir, `${sessionId}.jsonl`);
|
|
13195
12125
|
}
|
|
13196
12126
|
function isUserPrompt(parsed) {
|
|
13197
12127
|
if (!parsed || parsed.type !== "user") return false;
|
|
@@ -13364,7 +12294,7 @@ async function forkBeforeUserMessage(projectDir, sourceClaudeSessionId, cutBefor
|
|
|
13364
12294
|
const destination = jsonlPath(projectDir, newId);
|
|
13365
12295
|
const temporary = `${destination}.tmp-${process.pid}`;
|
|
13366
12296
|
try {
|
|
13367
|
-
await writeFile(temporary, lines.slice(0, cut).join("\n") + "\n", { encoding: "utf-8", flag: "wx" });
|
|
12297
|
+
await writeFile$1(temporary, lines.slice(0, cut).join("\n") + "\n", { encoding: "utf-8", flag: "wx" });
|
|
13368
12298
|
await rename$1(temporary, destination);
|
|
13369
12299
|
} catch (error) {
|
|
13370
12300
|
await unlink$1(temporary).catch(() => void 0);
|
|
@@ -16937,4 +15867,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
|
|
|
16937
15867
|
}).passthrough()
|
|
16938
15868
|
]);
|
|
16939
15869
|
|
|
16940
|
-
export {
|
|
15870
|
+
export { ApiClient as A, MIRROR_BACKFILL_LINES_DEFAULT as B, mirrorLineKey as C, mirrorLocalId as D, detectCLIAvailability as E, detectResumeSupport as F, summarizeSpawnSessionForLog as G, shellescape as H, InvalidateSync as I, fetchCliUpdateState as J, resolveCliUpdateCheckInterval as K, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, listVhTerminals as N, isValidTerminalId as O, readVhTerminal as P, sendToVhTerminal as Q, RawJSONLinesSchema as R, STREAM_DELTA_MAX_CHARS as S, TITLE_PROMPT_PREFIX as T, BOARD_ANALYZER_PROMPT_PREFIX as U, startOfflineReconnection as V, CodexAppServerClient as W, api as X, ApiSessionClient as a, contentLogMetadata as b, createEnvelope as c, delay as d, errorLogMetadata as e, connectionState as f, getProjectPath as g, initializeSandbox as h, isValidSessionId as i, AsyncLock as j, commandOnPath as k, deriveLocalCliUpdateSummary as l, isClaudeSessionId as m, getLocalHappyAgentCredentialPath as n, parseExactVersion as o, projectPath as p, compareExactVersions as q, readLocalHappyAgentCredentials as r, streamKeyOf as s, extractCompleteLines as t, decideBackfill as u, parseTerminalHookPayload as v, wrapCommand as w, tmuxArgs as x, scrubTmuxClientEnv as y, decideMirrorBinding as z };
|