very-happy-cli 0.2.123 → 0.2.125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{AcpBackend-hM-qHLN3.mjs → AcpBackend-DOQGItcS.mjs} +2 -1
  2. package/dist/{AcpBackend-DzIvpTxZ.cjs → AcpBackend-ssQpTLks.cjs} +30 -29
  3. package/dist/{AcpSessionManager-CRPZPHPa.mjs → AcpSessionManager-D4WkNsRs.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DyU3Ikgj.cjs → AcpSessionManager-DHLc-D50.cjs} +9 -9
  5. package/dist/agentGuidance-Cg9ZG1pP.cjs +236 -0
  6. package/dist/agentGuidance-QpYwykb9.mjs +224 -0
  7. package/dist/codex/happyMcpStdioBridge.cjs +29 -6
  8. package/dist/codex/happyMcpStdioBridge.mjs +25 -2
  9. package/dist/{config-4RaHrVSV.cjs → config-BxHOGtEK.cjs} +11 -11
  10. package/dist/{config-alG86PDN.mjs → config-CZNIz3W0.mjs} +11 -11
  11. package/dist/{index-Beh0UZf8.cjs → index-CBj99SO6.cjs} +53 -19
  12. package/dist/{index-36AkXNF2.mjs → index-CrSifgJM.mjs} +2046 -1719
  13. package/dist/{index-vyYHKKk6.mjs → index-D-BgvQi5.mjs} +8 -8
  14. package/dist/{index-DWBKJ5zy.cjs → index-D_Rm9dw9.cjs} +8 -8
  15. package/dist/{index-5b9YhV-1.cjs → index-Do-kjc30.cjs} +2191 -1864
  16. package/dist/{index-Cg3Vfc00.mjs → index-Jrk6ud3g.mjs} +49 -15
  17. package/dist/index.cjs +5 -5
  18. package/dist/index.mjs +5 -5
  19. package/dist/{installTerminalHooks-DWCa5j7u.cjs → installTerminalHooks-CPsdeOM4.cjs} +9 -9
  20. package/dist/{installTerminalHooks-DqUPkh0l.mjs → installTerminalHooks-Z1Jb6yjb.mjs} +8 -8
  21. package/dist/lib.cjs +12 -12
  22. package/dist/lib.d.cts +3 -0
  23. package/dist/lib.d.mts +3 -0
  24. package/dist/lib.mjs +9 -9
  25. package/dist/limits-DViGHwSj.cjs +1198 -0
  26. package/dist/limits-FjbPJPsF.mjs +1129 -0
  27. package/dist/{mcp-BBya5dsZ.cjs → mcp-BbQ5FWcQ.cjs} +11 -10
  28. package/dist/{mcp-DZIdcsJp.mjs → mcp-zqPJ94Bk.mjs} +7 -6
  29. package/dist/resources-CVl7NdOB.cjs +83 -0
  30. package/dist/resources-D-mn7ptO.mjs +80 -0
  31. package/dist/{runGemini-DmWxHIwS.cjs → runGemini-BnHGYX-H.cjs} +30 -30
  32. package/dist/{runGemini-BQa2eF3i.mjs → runGemini-Bu49s_bN.mjs} +13 -13
  33. package/dist/{runOpenClaw-boyJtPBq.cjs → runOpenClaw-3Ld1eoWV.cjs} +24 -24
  34. package/dist/{runOpenClaw-362eg04u.mjs → runOpenClaw-CznAYPCh.mjs} +9 -9
  35. package/dist/{send-CT22-8Tp.mjs → send-BevgS1mf.mjs} +10 -10
  36. package/dist/{send-BINA__Ke.cjs → send-DCzyBxLl.cjs} +10 -10
  37. package/dist/{sessions-DemgtCkr.mjs → sessions-Ba2oRTmw.mjs} +10 -10
  38. package/dist/{sessions-DXYxV6Np.cjs → sessions-DSoaBHwA.cjs} +11 -11
  39. package/dist/{spawn-BtV9dU1i.cjs → spawn-CqMLY6dE.cjs} +21 -21
  40. package/dist/{spawn-D2egvPgO.mjs → spawn-j9KBfj4e.mjs} +21 -21
  41. package/dist/teams-C0ggJTJ-.mjs +164 -0
  42. package/dist/teams-CmU3K9vB.cjs +166 -0
  43. package/dist/{types-Cd_tUN3U.cjs → types-BGHnQXl5.cjs} +585 -1577
  44. package/dist/{types-BqW7nEN_.mjs → types-DLDkfrTC.mjs} +198 -1153
  45. package/package.json +7 -7
  46. package/dist/agentGuidance-BifkGXdE.cjs +0 -46
  47. package/dist/agentGuidance-C2_pkgRI.mjs +0 -37
  48. package/dist/limits-CwFb5S2b.mjs +0 -22
  49. package/dist/limits-KdWKmwtH.cjs +0 -28
@@ -1,625 +1,31 @@
1
1
  import axios from 'axios';
2
- import chalk from 'chalk';
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-FjbPJPsF.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 { randomBytes, createCipheriv, createDecipheriv, createHash, createHmac, randomUUID as randomUUID$1 } from 'node:crypto';
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 as mkdir$1, writeFile as writeFile$1, appendFile, stat, rename, unlink, readdir, readFile } from 'fs/promises';
19
- import { randomUUID, createHash as createHash$1 } from 'crypto';
20
- import { dirname, resolve, join as join$1, sep } from 'path';
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.123";
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.123",
146
- "very-happy-tools-x64-darwin": "0.2.123",
147
- "very-happy-tools-arm64-linux": "0.2.123",
148
- "very-happy-tools-x64-linux": "0.2.123",
149
- "very-happy-tools-arm64-win32": "0.2.123",
150
- "very-happy-tools-x64-win32": "0.2.123"
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,34 @@ 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 id = z$1.string().min(1).max(128);
436
+ const text = z$1.string().min(1).max(32e3);
437
+ const TeamActionSchema = z$1.discriminatedUnion("type", [
438
+ z$1.object({ type: z$1.literal("archive") }),
439
+ 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() }),
440
+ z$1.object({ type: z$1.literal("schedule-pause"), scheduleId: id, version: z$1.number().int().positive() }),
441
+ z$1.object({ type: z$1.literal("schedule-resume"), scheduleId: id, version: z$1.number().int().positive() }),
442
+ z$1.object({ type: z$1.literal("schedule-cancel"), scheduleId: id, version: z$1.number().int().positive() }),
443
+ z$1.object({ type: z$1.literal("reconcile-operation"), operationId: id, claimId: id, note: z$1.string().trim().min(1).max(4e3) }),
444
+ z$1.object({ type: z$1.literal("join"), name: id, sessionId: id, botId: id.optional() }),
445
+ 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() }),
446
+ z$1.object({ type: z$1.literal("message"), taskId: id, body: text, recipientBotId: id.optional() }),
447
+ z$1.object({ type: z$1.literal("submit"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), result: text }),
448
+ z$1.object({ type: z$1.literal("accept"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive() }),
449
+ z$1.object({ type: z$1.literal("return"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), reason: text }),
450
+ z$1.object({ type: z$1.literal("cancel"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), reason: text }),
451
+ z$1.object({ type: z$1.literal("handoff"), taskId: id, attemptId: id, goalVersion: z$1.number().int().positive(), assigneeBotId: id }),
452
+ z$1.object({ type: z$1.literal("claim-operation"), operationId: id, machineId: id }),
453
+ z$1.object({ type: z$1.literal("complete-operation"), operationId: id, machineId: id, claimId: id, sessionId: id.optional() }),
454
+ z$1.object({ type: z$1.literal("fail-operation"), operationId: id, machineId: id, claimId: id, error: text, unknown: z$1.boolean().optional() }),
455
+ z$1.object({ type: z$1.literal("session-event"), sessionId: id, event: z$1.enum(["idle", "blocked", "exited"]) }),
456
+ z$1.object({ type: z$1.literal("message-delivered"), messageId: id, recipientBotId: id, generation: z$1.number().int().positive(), sessionId: id })
457
+ ]);
458
+ z$1.object({ requestId: id, action: TeamActionSchema });
459
+ z$1.object({ name: id, machineId: id, requestId: id.optional() });
1029
460
 
1030
461
  z$1.object({
462
+ teamsVersion: z$1.number().int().positive().optional(),
1031
463
  host: z$1.string(),
1032
464
  platform: z$1.string(),
1033
465
  happyCliVersion: z$1.string(),
@@ -1302,125 +734,6 @@ const AgentMessageSchema = z$1.object({
1302
734
  });
1303
735
  z$1.union([UserMessageSchema, AgentMessageSchema]);
1304
736
 
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
737
  async function delay(ms) {
1425
738
  return new Promise((resolve) => setTimeout(resolve, ms));
1426
739
  }
@@ -1693,7 +1006,7 @@ function projectPath() {
1693
1006
  }
1694
1007
 
1695
1008
  function run$1(args, options) {
1696
- const RUNNER_PATH = resolve(join$1(projectPath(), "scripts", "ripgrep_launcher.cjs"));
1009
+ const RUNNER_PATH = resolve(join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
1697
1010
  return new Promise((resolve2, reject) => {
1698
1011
  const child = spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
1699
1012
  stdio: ["pipe", "pipe", "pipe"],
@@ -1724,7 +1037,7 @@ function run$1(args, options) {
1724
1037
  function getBinaryPath() {
1725
1038
  const platformName = platform();
1726
1039
  const binaryName = platformName === "win32" ? "difft.exe" : "difft";
1727
- return resolve(join$1(projectPath(), "tools", "unpacked", binaryName));
1040
+ return resolve(join(projectPath(), "tools", "unpacked", binaryName));
1728
1041
  }
1729
1042
  function run(args, options) {
1730
1043
  const binaryPath = getBinaryPath();
@@ -1836,7 +1149,7 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1836
1149
  const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
1837
1150
  await Promise.all(entries.map(async (entry) => {
1838
1151
  if (!entry.isFile() || !/^\..+\.[A-Za-z0-9_-]{8,80}\.part$/.test(entry.name)) return;
1839
- const path = join$1(dir, entry.name);
1152
+ const path = join(dir, entry.name);
1840
1153
  const fileStat = await stat(path).catch(() => null);
1841
1154
  if (fileStat && fileStat.mtimeMs < cutoff) await unlink(path).catch(() => {
1842
1155
  });
@@ -1846,13 +1159,13 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1846
1159
  let tempPath = null;
1847
1160
  try {
1848
1161
  const buffer = decodeUploadBase64(data.content, MAX_USER_UPLOAD_BYTES);
1849
- const dir = join$1(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1850
- await mkdir$1(dir, { recursive: true });
1162
+ const dir = join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1163
+ await mkdir(dir, { recursive: true });
1851
1164
  const name = safeFileName(data.name);
1852
1165
  const token = randomUUID().replace(/-/g, "").slice(0, 12);
1853
- const target = join$1(dir, uniqueFileName(name, token));
1854
- tempPath = join$1(dir, `.${name}.legacy_${randomUUID().replace(/-/g, "")}.part`);
1855
- await writeFile$1(tempPath, buffer, { flag: "wx" });
1166
+ const target = join(dir, uniqueFileName(name, token));
1167
+ tempPath = join(dir, `.${name}.legacy_${randomUUID().replace(/-/g, "")}.part`);
1168
+ await writeFile(tempPath, buffer, { flag: "wx" });
1856
1169
  await rename(tempPath, target);
1857
1170
  tempPath = null;
1858
1171
  return { success: true, path: target, pathQuoteStyle, size: buffer.length };
@@ -1872,13 +1185,13 @@ function createUploadFileHandlers(happyHomeDir, now = Date.now, pathQuoteStyle =
1872
1185
  }
1873
1186
  if (active.has(uploadId)) throw new Error("Upload id is already active");
1874
1187
  if (active.size >= MAX_ACTIVE_UPLOADS) throw new Error("Too many active uploads");
1875
- const dir = join$1(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1876
- await mkdir$1(dir, { recursive: true });
1188
+ const dir = join(happyHomeDir, "uploads", safeSegment(data.subdir, "misc", 80));
1189
+ await mkdir(dir, { recursive: true });
1877
1190
  await sweepExpiredFiles(dir);
1878
1191
  const name = safeFileName(data.name);
1879
- const targetPath = join$1(dir, uniqueFileName(name, randomUUID().replace(/-/g, "").slice(0, 12)));
1880
- const tempPath = join$1(dir, `.${name}.${uploadId}.part`);
1881
- await writeFile$1(tempPath, Buffer.alloc(0), { flag: "wx" });
1192
+ const targetPath = join(dir, uniqueFileName(name, randomUUID().replace(/-/g, "").slice(0, 12)));
1193
+ const tempPath = join(dir, `.${name}.${uploadId}.part`);
1194
+ await writeFile(tempPath, Buffer.alloc(0), { flag: "wx" });
1882
1195
  active.set(uploadId, { tempPath, targetPath, totalSize: data.totalSize, received: 0, touchedAt: now() });
1883
1196
  return { success: true, size: 0 };
1884
1197
  }
@@ -2014,7 +1327,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2014
1327
  if (data.expectedHash !== null && data.expectedHash !== void 0) {
2015
1328
  try {
2016
1329
  const existingBuffer = await readFile(validation.resolvedPath);
2017
- const existingHash = createHash$1("sha256").update(existingBuffer).digest("hex");
1330
+ const existingHash = createHash("sha256").update(existingBuffer).digest("hex");
2018
1331
  if (existingHash !== data.expectedHash) {
2019
1332
  return {
2020
1333
  success: false,
@@ -2046,8 +1359,8 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2046
1359
  }
2047
1360
  }
2048
1361
  const buffer = Buffer.from(data.content, "base64");
2049
- await writeFile$1(validation.resolvedPath, buffer);
2050
- const hash = createHash$1("sha256").update(buffer).digest("hex");
1362
+ await writeFile(validation.resolvedPath, buffer);
1363
+ const hash = createHash("sha256").update(buffer).digest("hex");
2051
1364
  return { success: true, hash };
2052
1365
  } catch (error) {
2053
1366
  logger.debug("Failed to write file:", error);
@@ -2068,7 +1381,7 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2068
1381
  const entries = await readdir(directoryPath, { withFileTypes: true });
2069
1382
  const directoryEntries = await Promise.all(
2070
1383
  entries.map(async (entry) => {
2071
- const fullPath = join$1(directoryPath, entry.name);
1384
+ const fullPath = join(directoryPath, entry.name);
2072
1385
  let type = "other";
2073
1386
  let size;
2074
1387
  let modified;
@@ -2125,10 +1438,10 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
2125
1438
  await Promise.all(
2126
1439
  entries.map(async (entry) => {
2127
1440
  if (entry.isSymbolicLink()) {
2128
- logger.debug(`Skipping symlink: ${join$1(path, entry.name)}`);
1441
+ logger.debug(`Skipping symlink: ${join(path, entry.name)}`);
2129
1442
  return;
2130
1443
  }
2131
- const childPath = join$1(path, entry.name);
1444
+ const childPath = join(path, entry.name);
2132
1445
  const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
2133
1446
  if (childNode) {
2134
1447
  children.push(childNode);
@@ -4423,10 +3736,10 @@ function normalizeFsPath(input, homeDir) {
4423
3736
  if (p === "~") {
4424
3737
  p = homeDir;
4425
3738
  } else if (p.startsWith("~/")) {
4426
- p = join(homeDir, p.slice(2));
3739
+ p = join$1(homeDir, p.slice(2));
4427
3740
  }
4428
3741
  if (!isAbsolute(p)) {
4429
- p = join(homeDir, p);
3742
+ p = join$1(homeDir, p);
4430
3743
  }
4431
3744
  return resolve$1(p);
4432
3745
  }
@@ -4482,7 +3795,7 @@ async function fsList(inputPath, homeDir = homedir()) {
4482
3795
  dirents.map(async (d) => {
4483
3796
  const entry = { name: d.name, type: entryTypeOf(d) };
4484
3797
  try {
4485
- const s = await lstat(join(path, d.name));
3798
+ const s = await lstat(join$1(path, d.name));
4486
3799
  entry.size = s.size;
4487
3800
  entry.mtimeMs = Math.round(s.mtimeMs);
4488
3801
  } catch {
@@ -4539,391 +3852,6 @@ function registerFsHandlers(rpcHandlerManager) {
4539
3852
  });
4540
3853
  }
4541
3854
 
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
3855
  const TODO_LIST_MAX_ITEMS = 500;
4928
3856
  const TODO_TITLE_MAX_CHARS = 500;
4929
3857
  const FIELD_MAX_CHARS = 200;
@@ -5282,14 +4210,14 @@ function scrubTmuxClientEnv(env) {
5282
4210
  }
5283
4211
 
5284
4212
  function getClaudeConfigDir() {
5285
- return process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
4213
+ return process.env.CLAUDE_CONFIG_DIR || join$1(homedir(), ".claude");
5286
4214
  }
5287
4215
  function getClaudeProjectsRoot() {
5288
- return join(getClaudeConfigDir(), "projects");
4216
+ return join$1(getClaudeConfigDir(), "projects");
5289
4217
  }
5290
4218
  function getProjectPath(workingDirectory) {
5291
4219
  const projectId = resolve$1(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
5292
- return join(getClaudeProjectsRoot(), projectId);
4220
+ return join$1(getClaudeProjectsRoot(), projectId);
5293
4221
  }
5294
4222
 
5295
4223
  var xtermHeadless = {};
@@ -10521,7 +9449,7 @@ function pruneTombstones(map, now, ttlMs = TOMBSTONE_TTL_MS) {
10521
9449
  return out;
10522
9450
  }
10523
9451
  function tombstoneFile() {
10524
- return join(configuration.happyHomeDir, "terminal-tombstones.json");
9452
+ return join$1(configuration.happyHomeDir, "terminal-tombstones.json");
10525
9453
  }
10526
9454
  function loadTombstones() {
10527
9455
  try {
@@ -10540,7 +9468,7 @@ function saveTombstones(map) {
10540
9468
  }
10541
9469
  }
10542
9470
  function closedTerminalsFile() {
10543
- return join(configuration.happyHomeDir, "closed-terminals.json");
9471
+ return join$1(configuration.happyHomeDir, "closed-terminals.json");
10544
9472
  }
10545
9473
  function loadClosedTerminals() {
10546
9474
  try {
@@ -10558,7 +9486,7 @@ function saveClosedTerminals(list) {
10558
9486
  }
10559
9487
  }
10560
9488
  function liveTerminalsFile() {
10561
- return join(configuration.happyHomeDir, "live-terminals.json");
9489
+ return join$1(configuration.happyHomeDir, "live-terminals.json");
10562
9490
  }
10563
9491
  function loadLiveSnapshot() {
10564
9492
  try {
@@ -10833,7 +9761,7 @@ function tmuxSupportsEnvFlag() {
10833
9761
  }
10834
9762
  const PASTE_FILE_TTL_MS = 5e3;
10835
9763
  function pasteSpoolDir() {
10836
- const dir = join(configuration.happyHomeDir, "paste-spool");
9764
+ const dir = join$1(configuration.happyHomeDir, "paste-spool");
10837
9765
  try {
10838
9766
  mkdirSync(dir, { recursive: true, mode: 448 });
10839
9767
  } catch {
@@ -11662,7 +10590,7 @@ class WebTerminalManager {
11662
10590
  return false;
11663
10591
  }
11664
10592
  },
11665
- conversationExists: (cwd, claudeSessionId) => existsSync(join(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
10593
+ conversationExists: (cwd, claudeSessionId) => existsSync(join$1(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
11666
10594
  // B-273: only consulted for records that were attach terminals.
11667
10595
  // Unbounded: the panel's 50-row cap must not hide a restore target.
11668
10596
  userSessions: record.attachTmux ? this.listUserTmuxSessions(Infinity) : [],
@@ -12907,7 +11835,7 @@ function detectPosix() {
12907
11835
  const codex = commandExists("codex");
12908
11836
  const gemini = commandExists("gemini");
12909
11837
  const openclawCommand = commandExists("openclaw");
12910
- const openclawConfig = existsSync$1(join$1(os.homedir(), ".openclaw", "openclaw.json"));
11838
+ const openclawConfig = existsSync$1(join(os.homedir(), ".openclaw", "openclaw.json"));
12911
11839
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12912
11840
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12913
11841
  const pi = commandExists("pi") && commandExists("pi-acp");
@@ -12919,7 +11847,7 @@ function detectWindows() {
12919
11847
  const codex = checkCommand("codex");
12920
11848
  const gemini = checkCommand("gemini");
12921
11849
  const openclawCommand = checkCommand("openclaw");
12922
- const openclawConfig = existsSync$1(join$1(process.env.USERPROFILE || os.homedir(), ".openclaw", "openclaw.json"));
11850
+ const openclawConfig = existsSync$1(join(process.env.USERPROFILE || os.homedir(), ".openclaw", "openclaw.json"));
12923
11851
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12924
11852
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12925
11853
  const pi = checkCommand("pi") && checkCommand("pi-acp");
@@ -12954,7 +11882,7 @@ function deriveKey(master, usage, path) {
12954
11882
  }
12955
11883
  function deriveContentKeyPair(secret) {
12956
11884
  const seed = deriveKey(secret, "Happy EnCoder", ["content"]);
12957
- const hashedSeed = new Uint8Array(createHash("sha512").update(seed).digest());
11885
+ const hashedSeed = new Uint8Array(createHash$1("sha512").update(seed).digest());
12958
11886
  const secretKey = hashedSeed.slice(0, 32);
12959
11887
  const keyPair = tweetnacl.box.keyPair.fromSecretKey(secretKey);
12960
11888
  return {
@@ -12963,7 +11891,7 @@ function deriveContentKeyPair(secret) {
12963
11891
  };
12964
11892
  }
12965
11893
  function getLocalHappyAgentCredentialPath(happyHomeDir = configuration.happyHomeDir) {
12966
- return join(happyHomeDir, "agent.key");
11894
+ return join$1(happyHomeDir, "agent.key");
12967
11895
  }
12968
11896
  function readLocalHappyAgentCredentials(happyHomeDir = configuration.happyHomeDir, serverUrl = configuration.serverUrl) {
12969
11897
  const credentialPath = getLocalHappyAgentCredentialPath(happyHomeDir);
@@ -13021,7 +11949,7 @@ async function listClaudeProjectDirs(projectsRoot) {
13021
11949
  }
13022
11950
  const dirs = [];
13023
11951
  for (const name of names) {
13024
- const full = join(projectsRoot, name);
11952
+ const full = join$1(projectsRoot, name);
13025
11953
  try {
13026
11954
  if ((await stat$1(full)).isDirectory()) dirs.push(full);
13027
11955
  } catch {
@@ -13042,7 +11970,7 @@ async function collectCandidates(projectDirs, exclude) {
13042
11970
  if (!name.endsWith(".jsonl")) continue;
13043
11971
  const id = name.slice(0, -".jsonl".length);
13044
11972
  if (!UUID_RE$1.test(id) || exclude.has(id.toLowerCase())) continue;
13045
- const path = join(dir, name);
11973
+ const path = join$1(dir, name);
13046
11974
  try {
13047
11975
  const s = await stat$1(path);
13048
11976
  if (!s.isFile() || s.size === 0) continue;
@@ -13191,7 +12119,7 @@ class ForkSourceMissingError extends Error {
13191
12119
  }
13192
12120
  }
13193
12121
  function jsonlPath(projectDir, sessionId) {
13194
- return join(projectDir, `${sessionId}.jsonl`);
12122
+ return join$1(projectDir, `${sessionId}.jsonl`);
13195
12123
  }
13196
12124
  function isUserPrompt(parsed) {
13197
12125
  if (!parsed || parsed.type !== "user") return false;
@@ -13199,6 +12127,18 @@ function isUserPrompt(parsed) {
13199
12127
  const content = parsed.message?.content;
13200
12128
  return typeof content === "string";
13201
12129
  }
12130
+ function hasPromptAttachments(parsed) {
12131
+ const content = parsed.message?.content;
12132
+ return typeof content === "string" && content.includes("<attached_files>") || Array.isArray(content) && content.some((block) => block?.type !== "text" || typeof block.text === "string" && block.text.includes("<attached_files>"));
12133
+ }
12134
+ function userPromptText(parsed) {
12135
+ if (parsed?.type !== "user" || parsed.isSidechain || parsed.isMeta || parsed.isSynthetic) return null;
12136
+ const content = parsed.message?.content;
12137
+ if (typeof content === "string") return content.trim() ? content : null;
12138
+ if (!Array.isArray(content) || content.some((block) => block?.type === "tool_result")) return null;
12139
+ const text = content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
12140
+ return text.trim() ? text : null;
12141
+ }
13202
12142
  async function discardForkedSession(projectDir, claudeSessionId) {
13203
12143
  try {
13204
12144
  await unlink$1(jsonlPath(projectDir, claudeSessionId));
@@ -13298,23 +12238,68 @@ async function listClaudeRewindPoints(projectDir, claudeSessionId) {
13298
12238
  } catch {
13299
12239
  continue;
13300
12240
  }
13301
- if (parsed?.type !== "user") continue;
13302
- if (parsed.isSidechain) continue;
12241
+ const content = userPromptText(parsed);
12242
+ if (content === null) continue;
13303
12243
  if (typeof parsed.uuid !== "string" || parsed.uuid.length === 0) continue;
13304
- const content = parsed.message?.content;
13305
- if (typeof content !== "string") continue;
13306
- const trimmed = content.trim();
13307
- if (trimmed.length === 0) continue;
13308
12244
  const timestampRaw = parsed.timestamp;
13309
12245
  const timestamp = typeof timestampRaw === "string" ? Date.parse(timestampRaw) : typeof timestampRaw === "number" ? timestampRaw : Date.now();
13310
12246
  points.push({
13311
12247
  uuid: parsed.uuid,
13312
12248
  text: content,
12249
+ ...hasPromptAttachments(parsed) ? { hasAttachments: true } : {},
13313
12250
  timestamp: Number.isFinite(timestamp) ? timestamp : Date.now()
13314
12251
  });
13315
12252
  }
13316
12253
  return points;
13317
12254
  }
12255
+ async function forkBeforeUserMessage(projectDir, sourceClaudeSessionId, cutBeforeUuid) {
12256
+ const src = jsonlPath(projectDir, sourceClaudeSessionId);
12257
+ let raw;
12258
+ try {
12259
+ raw = await readFile$1(src, "utf-8");
12260
+ } catch (error) {
12261
+ if (error.code === "ENOENT") throw new ForkSourceMissingError(src);
12262
+ throw error;
12263
+ }
12264
+ const lines = raw.split("\n");
12265
+ let hasPreviousConversation = false;
12266
+ let hasPreviousPrompt = false;
12267
+ let cut = -1;
12268
+ for (let i = 0; i < lines.length; i++) {
12269
+ let entry;
12270
+ try {
12271
+ entry = JSON.parse(lines[i]);
12272
+ } catch {
12273
+ continue;
12274
+ }
12275
+ if (userPromptText(entry) !== null && entry.uuid === cutBeforeUuid) {
12276
+ if (hasPromptAttachments(entry)) throw new Error("Cannot edit and rerun a message with attachments");
12277
+ cut = i;
12278
+ break;
12279
+ }
12280
+ if (!entry?.isSidechain && !entry?.isMeta && !entry?.isSynthetic && (entry?.type === "user" || entry?.type === "assistant")) {
12281
+ hasPreviousConversation = true;
12282
+ const content = entry.message?.content;
12283
+ if (entry.type === "user" && (typeof content === "string" || Array.isArray(content) && content.length > 0 && !content.some((block) => block?.type === "tool_result"))) {
12284
+ hasPreviousPrompt = true;
12285
+ }
12286
+ }
12287
+ }
12288
+ if (cut < 0) throw new ForkTruncateUuidNotFoundError(cutBeforeUuid, src);
12289
+ if (!hasPreviousConversation) return null;
12290
+ if (!hasPreviousPrompt) throw new Error("Cannot safely rewind: earlier history has no user prompt");
12291
+ const newId = randomUUID$1();
12292
+ const destination = jsonlPath(projectDir, newId);
12293
+ const temporary = `${destination}.tmp-${process.pid}`;
12294
+ try {
12295
+ await writeFile$1(temporary, lines.slice(0, cut).join("\n") + "\n", { encoding: "utf-8", flag: "wx" });
12296
+ await rename$1(temporary, destination);
12297
+ } catch (error) {
12298
+ await unlink$1(temporary).catch(() => void 0);
12299
+ throw error;
12300
+ }
12301
+ return newId;
12302
+ }
13318
12303
 
13319
12304
  function serializedByteLength(value) {
13320
12305
  try {
@@ -14591,6 +13576,14 @@ class CodexForkRewindPointNotFoundError extends Error {
14591
13576
  this.name = "CodexForkRewindPointNotFoundError";
14592
13577
  }
14593
13578
  }
13579
+ function hasUserAttachments(item) {
13580
+ const content = item.content;
13581
+ return Array.isArray(content) && content.some((part) => !part || typeof part !== "object" || part.type !== "text" || typeof part.text === "string" && part.text.includes("<attached_files>"));
13582
+ }
13583
+ function rejectAttachedTarget(thread, itemId) {
13584
+ const item = thread.turns?.flatMap((turn) => turn.items ?? []).find((item2) => item2.id === itemId);
13585
+ if (item && hasUserAttachments(item)) throw new Error("Cannot edit and rerun a message with attachments");
13586
+ }
14594
13587
  function textFromUserItem(item) {
14595
13588
  if (item.type !== "userMessage") {
14596
13589
  return null;
@@ -14617,6 +13610,7 @@ function listCodexRewindPoints(thread) {
14617
13610
  points.push({
14618
13611
  itemId: item.id,
14619
13612
  text,
13613
+ ...hasUserAttachments(item) ? { hasAttachments: true } : {},
14620
13614
  timestamp: timestampFromTurn(turn)
14621
13615
  });
14622
13616
  }
@@ -14674,6 +13668,37 @@ async function forkCodexThread(client, opts) {
14674
13668
  newCodexThreadId: forked.threadId
14675
13669
  };
14676
13670
  }
13671
+ async function forkCodexBeforeUserMessage(client, opts) {
13672
+ const { thread: source } = await client.readThread({ threadId: opts.threadId, includeTurns: true });
13673
+ rejectAttachedTarget(source, opts.cutBeforeItemId);
13674
+ const sourceCut = findCutTurn(source, opts.cutBeforeItemId);
13675
+ if (!sourceCut) throw new CodexForkRewindPointNotFoundError(opts.cutBeforeItemId, opts.threadId);
13676
+ if ((source.turns?.[sourceCut.index].items ?? []).findIndex((item) => item.id === opts.cutBeforeItemId) !== 0) {
13677
+ throw new Error("Cannot safely rewind a user message inside an existing turn");
13678
+ }
13679
+ if (sourceCut.index === 0) return { type: "success", startFresh: true };
13680
+ const forked = await client.forkThread({ threadId: opts.threadId, cwd: opts.cwd });
13681
+ if (forked.threadId === opts.threadId) throw new Error("Codex did not create an independent fork");
13682
+ rejectAttachedTarget(forked.thread, opts.cutBeforeItemId);
13683
+ const cut = findCutTurn(forked.thread, opts.cutBeforeItemId);
13684
+ if (!cut) throw new CodexForkRewindPointNotFoundError(opts.cutBeforeItemId, opts.threadId);
13685
+ const turns = forked.thread.turns ?? [];
13686
+ const items = turns[cut.index].items ?? [];
13687
+ if (items.findIndex((item) => item.id === opts.cutBeforeItemId) !== 0) {
13688
+ throw new Error("Cannot safely rewind a user message inside an existing turn");
13689
+ }
13690
+ if (cut.index === 0) return { type: "success", startFresh: true };
13691
+ const expectedTurnIds = turns.slice(0, cut.index).map((turn) => turn.id);
13692
+ const { thread } = await client.rollbackThread({
13693
+ threadId: forked.threadId,
13694
+ numTurns: turns.length - cut.index
13695
+ });
13696
+ const remaining = thread.turns ?? [];
13697
+ if (remaining.length !== cut.index || remaining.some((turn, index) => turn.id !== expectedTurnIds[index])) {
13698
+ throw new Error("Codex rollback did not preserve the expected history");
13699
+ }
13700
+ return { type: "success", newCodexThreadId: forked.threadId };
13701
+ }
14677
13702
 
14678
13703
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14679
13704
  function requireNonEmptyString(value, name) {
@@ -15178,6 +14203,26 @@ class ApiMachineClient {
15178
14203
  throw error;
15179
14204
  }
15180
14205
  });
14206
+ this.rpcHandlerManager.registerHandler("claude-rewind-before-message", async (params) => {
14207
+ const directory = requireNonEmptyString(params?.directory, "directory");
14208
+ const claudeSessionId = requireNonEmptyString(params?.claudeSessionId, "claudeSessionId");
14209
+ const cutBeforeUuid = requireNonEmptyString(params?.cutBeforeUuid, "cutBeforeUuid");
14210
+ if (!UUID_RE.test(claudeSessionId) || !UUID_RE.test(cutBeforeUuid)) {
14211
+ throw new Error("claudeSessionId and cutBeforeUuid must be valid UUIDs");
14212
+ }
14213
+ const newClaudeSessionId = await forkBeforeUserMessage(getProjectPath(directory), claudeSessionId, cutBeforeUuid);
14214
+ return newClaudeSessionId === null ? { type: "success", startFresh: true } : { type: "success", newClaudeSessionId };
14215
+ });
14216
+ this.rpcHandlerManager.registerHandler("codex-rewind-before-message", async (params) => {
14217
+ const directory = requireNonEmptyString(params?.directory, "directory");
14218
+ const codexThreadId = requireNonEmptyString(params?.codexThreadId, "codexThreadId");
14219
+ const cutBeforeItemId = requireNonEmptyString(params?.cutBeforeItemId, "cutBeforeItemId");
14220
+ return withCodexAppServerClient((client) => forkCodexBeforeUserMessage(client, {
14221
+ threadId: codexThreadId,
14222
+ cwd: directory,
14223
+ cutBeforeItemId
14224
+ }));
14225
+ });
15181
14226
  this.rpcHandlerManager.registerHandler("stop-daemon", () => {
15182
14227
  logger.debug("[API MACHINE] Received stop-daemon RPC request");
15183
14228
  setTimeout(() => {
@@ -16820,4 +15865,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
16820
15865
  }).passthrough()
16821
15866
  ]);
16822
15867
 
16823
- export { tmuxArgs as $, ApiClient as A, commandOnPath as B, readCredentials as C, credentialRelayProblem as D, deriveLocalCliUpdateSummary as E, readSettingsOutcome as F, updateSettings as G, encodeBase64Url as H, InvalidateSync as I, decodeBase64 as J, writeCredentialsLegacy as K, writeCredentialsDataKey as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, readCredentialsForConfiguredRelay as N, encrypt as O, isClaudeSessionId as P, getLocalHappyAgentCredentialPath as Q, RawJSONLinesSchema as R, STREAM_DELTA_MAX_CHARS as S, readLocalHappyAgentCredentials as T, decryptWithDataKey as U, decryptLegacy as V, parseExactVersion as W, compareExactVersions as X, extractCompleteLines as Y, decideBackfill as Z, parseTerminalHookPayload as _, ApiSessionClient as a, scrubTmuxClientEnv as a0, decideMirrorBinding as a1, MIRROR_BACKFILL_LINES_DEFAULT as a2, persistSession as a3, mirrorLineKey as a4, mirrorLocalId as a5, detectCLIAvailability as a6, detectResumeSupport as a7, acquireDaemonLock as a8, summarizeSpawnSessionForLog as a9, deletePersistedSessions as aa, shellescape as ab, decrypt as ac, writeDaemonState as ad, writeSettings as ae, fetchCliUpdateState as af, resolveCliUpdateCheckInterval as ag, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as ah, releaseDaemonLock as ai, listVhTerminals as aj, isValidTerminalId as ak, readVhTerminal as al, sendToVhTerminal as am, TITLE_PROMPT_PREFIX as an, BOARD_ANALYZER_PROMPT_PREFIX as ao, startOfflineReconnection as ap, clearCredentials as aq, clearMachineId as ar, SandboxConfigSchema as as, CodexAppServerClient as at, getLatestDaemonLog as au, persistence as av, api as aw, createEnvelope as b, configuration as c, connectionState as d, readSettings as e, encodeBase64 as f, contentLogMetadata as g, errorLogMetadata as h, isValidSessionId as i, packageJson as j, delay as k, logger as l, ensurePrivateDirectorySync as m, hardenPrivateFileSync as n, readDaemonState as o, projectPath as p, clearDaemonState as q, readPersistedSessions as r, streamKeyOf as s, getProjectPath as t, initializeSandbox as u, wrapCommand as v, writePrivateFileSync as w, AsyncLock as x, ensurePrivateDirectory as y, hardenPrivateFile as z };
15868
+ 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 };