very-happy-cli 0.2.154 → 0.2.156

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 (57) hide show
  1. package/dist/{AcpBackend-B14xNj5L.cjs → AcpBackend-BHe1_F1h.cjs} +3 -3
  2. package/dist/{AcpBackend-Ckyadhf4.mjs → AcpBackend-C3nc1u-s.mjs} +2 -2
  3. package/dist/{AcpSessionManager-CCHmPdrR.cjs → AcpSessionManager-BVxkQnNf.cjs} +1 -1
  4. package/dist/{AcpSessionManager-H8t4P-IW.mjs → AcpSessionManager-dN3k1LbI.mjs} +1 -1
  5. package/dist/agentGuidance-C-CmV03f.cjs +1665 -0
  6. package/dist/agentGuidance-C604n0Zq.mjs +1613 -0
  7. package/dist/{agentHome-BUsLTZnz.cjs → agentHome-CmIJNV7f.cjs} +5 -5
  8. package/dist/{agentHome-B20RWdgE.mjs → agentHome-D2XGsoqs.mjs} +5 -5
  9. package/dist/{auto-BQnncDcs.mjs → auto-B1I6G_Nf.mjs} +21 -21
  10. package/dist/{auto-IeldARBS.cjs → auto-CMDq4oIu.cjs} +24 -24
  11. package/dist/codex/happyMcpStdioBridge.cjs +6 -3
  12. package/dist/codex/happyMcpStdioBridge.mjs +6 -3
  13. package/dist/{config-DM_xI9M6.cjs → config-BYHSRY2E.cjs} +8 -8
  14. package/dist/{config-B-ocQh5V.mjs → config-BkTu25bH.mjs} +8 -8
  15. package/dist/{index-ZKuBjvHi.mjs → index-B5QbzEga.mjs} +21 -13
  16. package/dist/{index-BUxBxSxJ.cjs → index-B_-7NpGZ.cjs} +21 -13
  17. package/dist/{index-eCjkm8Y2.cjs → index-BbtR-Qto.cjs} +606 -982
  18. package/dist/{index-okLyUzeD.cjs → index-CB8QnjvS.cjs} +7 -7
  19. package/dist/{index-BELV05ko.mjs → index-DAAreiRF.mjs} +548 -911
  20. package/dist/{index-CIx3248o.mjs → index-PYO8Mdz4.mjs} +7 -7
  21. package/dist/index.cjs +6 -6
  22. package/dist/index.mjs +6 -6
  23. package/dist/{install-CoLN3whR.mjs → install-CNtXzwT_.mjs} +1 -1
  24. package/dist/{install-BZOdxfPd.cjs → install-CO1ps6Wt.cjs} +1 -1
  25. package/dist/{installTerminalHooks-CA4OcAS7.cjs → installTerminalHooks-BOAnQ1Qe.cjs} +5 -5
  26. package/dist/{installTerminalHooks-Dde_8t36.mjs → installTerminalHooks-CMpOV3RU.mjs} +5 -5
  27. package/dist/lib.cjs +2 -2
  28. package/dist/lib.d.cts +24 -8
  29. package/dist/lib.d.mts +24 -8
  30. package/dist/lib.mjs +2 -2
  31. package/dist/{limits-Dt7_unH4.mjs → limits-DmMdchTz.mjs} +309 -13
  32. package/dist/{limits-CmzISka2.cjs → limits-o29KXBgj.cjs} +329 -12
  33. package/dist/{mcp-9Ui4nQZL.mjs → mcp-C_uejotl.mjs} +17 -17
  34. package/dist/{mcp-7iDHlyCI.cjs → mcp-CwK1nI7Y.cjs} +23 -23
  35. package/dist/{piRuntime-C4E4pgye.mjs → piRuntime-Cu2-8nOT.mjs} +2 -2
  36. package/dist/{piRuntime-BesGGPME.cjs → piRuntime-Dj5WnqzJ.cjs} +2 -2
  37. package/dist/{piTerminal-fUogwxYr.cjs → piTerminal-D-BIbov9.cjs} +6 -6
  38. package/dist/{piTerminal-CCEGCQOY.mjs → piTerminal-DanZeBrO.mjs} +6 -6
  39. package/dist/{runGemini-CNPGSsSK.cjs → runGemini--QqK-Ap5.cjs} +7 -7
  40. package/dist/{runGemini-BW-pEklU.mjs → runGemini-CZ_lhMWY.mjs} +6 -6
  41. package/dist/{runOpenClaw-Bsodkg_s.cjs → runOpenClaw-C0xLhTvk.cjs} +6 -6
  42. package/dist/{runOpenClaw-NRB6-_jU.mjs → runOpenClaw-CxgLpm38.mjs} +5 -5
  43. package/dist/{send-DYeSFg8s.mjs → send-BaOkDgH0.mjs} +5 -39
  44. package/dist/{send-Bd00QcHC.cjs → send-CTcGMVuG.cjs} +8 -42
  45. package/dist/{sessions-DlPOYKLU.mjs → sessions-Bn304xF1.mjs} +88 -45
  46. package/dist/{sessions-RIFUoy_g.cjs → sessions-OmvyJb0n.cjs} +99 -56
  47. package/dist/{spawn-D1bhyHbH.mjs → spawn-BKNabutV.mjs} +22 -22
  48. package/dist/{spawn-DxrfHTF4.cjs → spawn-CC6V2KiA.cjs} +30 -30
  49. package/dist/{teams-pfKHpkgL.mjs → teams-CLbS2_4Q.mjs} +8 -8
  50. package/dist/{teams-Cfy0VMCo.cjs → teams-XtrH3_h3.cjs} +8 -8
  51. package/dist/{todo-B2CjDg_V.mjs → todo-BcPbNKt2.mjs} +8 -6
  52. package/dist/{todo-B4cBFzHB.cjs → todo-CEDvKNXK.cjs} +8 -6
  53. package/dist/{types-D5RWyBE5.mjs → types-BEVzYSoV.mjs} +52 -291
  54. package/dist/{types-Bd_TGr0F.cjs → types-CphWjqHc.cjs} +64 -320
  55. package/package.json +7 -7
  56. package/dist/agentGuidance-DUBXppv-.cjs +0 -554
  57. package/dist/agentGuidance-TLYQxsm2.mjs +0 -530
@@ -1,18 +1,18 @@
1
- import { q as query } from './index-BELV05ko.mjs';
1
+ import { q as query } from './index-DAAreiRF.mjs';
2
2
  export { AbortError } from '@anthropic-ai/claude-agent-sdk';
3
3
  import 'chalk';
4
4
  import 'node:os';
5
5
  import 'node:crypto';
6
- import './types-D5RWyBE5.mjs';
6
+ import './types-BEVzYSoV.mjs';
7
7
  import 'axios';
8
- import './limits-Dt7_unH4.mjs';
8
+ import './limits-DmMdchTz.mjs';
9
+ import 'zod';
10
+ import '@paralleldrive/cuid2';
9
11
  import 'node:fs/promises';
10
12
  import 'node:fs';
11
- import 'node:path';
12
- import 'zod';
13
13
  import 'tweetnacl';
14
14
  import 'node:util';
15
- import '@paralleldrive/cuid2';
15
+ import 'node:path';
16
16
  import 'node:events';
17
17
  import 'socket.io-client';
18
18
  import 'child_process';
@@ -30,7 +30,7 @@ import 'fs';
30
30
  import 'node:readline';
31
31
  import '@anthropic-ai/sandbox-runtime';
32
32
  import 'expo-server-sdk';
33
- import './agentGuidance-TLYQxsm2.mjs';
33
+ import './agentGuidance-C604n0Zq.mjs';
34
34
  import 'ink';
35
35
  import 'react';
36
36
  import 'ps-list';
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-eCjkm8Y2.cjs');
5
- require('./limits-CmzISka2.cjs');
4
+ require('./index-BbtR-Qto.cjs');
5
+ require('./limits-o29KXBgj.cjs');
6
6
  require('zod');
7
- require('./types-Bd_TGr0F.cjs');
7
+ require('./types-CphWjqHc.cjs');
8
8
  require('node:child_process');
9
9
  require('node:os');
10
10
  require('node:crypto');
@@ -15,15 +15,15 @@ require('node:fs');
15
15
  require('./terminalTitleSuggest-Dji8I0Jy.cjs');
16
16
  require('path');
17
17
  require('url');
18
- require('./agentGuidance-DUBXppv-.cjs');
18
+ require('./agentGuidance-C-CmV03f.cjs');
19
19
  require('node:fs/promises');
20
+ require('axios');
21
+ require('socket.io-client');
20
22
  require('fs/promises');
21
23
  require('ink');
22
24
  require('react');
23
25
  require('@anthropic-ai/claude-agent-sdk');
24
- require('axios');
25
26
  require('node:events');
26
- require('socket.io-client');
27
27
  require('tweetnacl');
28
28
  require('os');
29
29
  require('child_process');
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import 'chalk';
2
- import './index-BELV05ko.mjs';
3
- import './limits-Dt7_unH4.mjs';
2
+ import './index-DAAreiRF.mjs';
3
+ import './limits-DmMdchTz.mjs';
4
4
  import 'zod';
5
- import './types-D5RWyBE5.mjs';
5
+ import './types-BEVzYSoV.mjs';
6
6
  import 'node:child_process';
7
7
  import 'node:os';
8
8
  import 'node:crypto';
@@ -13,15 +13,15 @@ import 'node:fs';
13
13
  import './terminalTitleSuggest-DKgX3osW.mjs';
14
14
  import 'path';
15
15
  import 'url';
16
- import './agentGuidance-TLYQxsm2.mjs';
16
+ import './agentGuidance-C604n0Zq.mjs';
17
17
  import 'node:fs/promises';
18
+ import 'axios';
19
+ import 'socket.io-client';
18
20
  import 'fs/promises';
19
21
  import 'ink';
20
22
  import 'react';
21
23
  import '@anthropic-ai/claude-agent-sdk';
22
- import 'axios';
23
24
  import 'node:events';
24
- import 'socket.io-client';
25
25
  import 'tweetnacl';
26
26
  import 'os';
27
27
  import 'child_process';
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { unlink, rmdir, mkdir, lstat, readFile, writeFile, rename } from 'node:fs/promises';
3
3
  import { join, resolve, dirname } from 'node:path';
4
- import { B as TEAM_SKILL } from './index-BELV05ko.mjs';
4
+ import { T as TEAM_SKILL } from './index-DAAreiRF.mjs';
5
5
 
6
6
  async function assertPrivateDestination(directory) {
7
7
  for (let path = directory; ; path = dirname(path)) {
@@ -3,7 +3,7 @@
3
3
  var node_crypto = require('node:crypto');
4
4
  var promises = require('node:fs/promises');
5
5
  var path = require('node:path');
6
- var index = require('./index-eCjkm8Y2.cjs');
6
+ var index = require('./index-BbtR-Qto.cjs');
7
7
 
8
8
  async function assertPrivateDestination(directory) {
9
9
  for (let path$1 = directory; ; path$1 = path.dirname(path$1)) {
@@ -6,18 +6,18 @@ var os = require('node:os');
6
6
  var node_child_process = require('node:child_process');
7
7
  var chalk = require('chalk');
8
8
  var terminalTitleSuggest = require('./terminalTitleSuggest-Dji8I0Jy.cjs');
9
- var index = require('./index-eCjkm8Y2.cjs');
9
+ var index = require('./index-BbtR-Qto.cjs');
10
10
  require('path');
11
11
  require('url');
12
12
  require('zod');
13
13
  require('node:crypto');
14
- require('./types-Bd_TGr0F.cjs');
14
+ require('./types-CphWjqHc.cjs');
15
15
  require('axios');
16
- require('./limits-CmzISka2.cjs');
16
+ require('./limits-o29KXBgj.cjs');
17
+ require('@paralleldrive/cuid2');
17
18
  require('node:fs/promises');
18
19
  require('tweetnacl');
19
20
  require('node:util');
20
- require('@paralleldrive/cuid2');
21
21
  require('node:events');
22
22
  require('socket.io-client');
23
23
  require('child_process');
@@ -31,7 +31,7 @@ require('fs');
31
31
  require('node:readline');
32
32
  require('@anthropic-ai/sandbox-runtime');
33
33
  require('expo-server-sdk');
34
- require('./agentGuidance-DUBXppv-.cjs');
34
+ require('./agentGuidance-C-CmV03f.cjs');
35
35
  require('ink');
36
36
  require('react');
37
37
  require('@anthropic-ai/claude-agent-sdk');
@@ -4,18 +4,18 @@ import { homedir } from 'node:os';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import chalk from 'chalk';
6
6
  import { p as projectPath } from './terminalTitleSuggest-DKgX3osW.mjs';
7
- import { j as tmuxSupportsSessionEnv } from './index-BELV05ko.mjs';
7
+ import { b as tmuxSupportsSessionEnv } from './index-DAAreiRF.mjs';
8
8
  import 'path';
9
9
  import 'url';
10
10
  import 'zod';
11
11
  import 'node:crypto';
12
- import './types-D5RWyBE5.mjs';
12
+ import './types-BEVzYSoV.mjs';
13
13
  import 'axios';
14
- import './limits-Dt7_unH4.mjs';
14
+ import './limits-DmMdchTz.mjs';
15
+ import '@paralleldrive/cuid2';
15
16
  import 'node:fs/promises';
16
17
  import 'tweetnacl';
17
18
  import 'node:util';
18
- import '@paralleldrive/cuid2';
19
19
  import 'node:events';
20
20
  import 'socket.io-client';
21
21
  import 'child_process';
@@ -29,7 +29,7 @@ import 'fs';
29
29
  import 'node:readline';
30
30
  import '@anthropic-ai/sandbox-runtime';
31
31
  import 'expo-server-sdk';
32
- import './agentGuidance-TLYQxsm2.mjs';
32
+ import './agentGuidance-C604n0Zq.mjs';
33
33
  import 'ink';
34
34
  import 'react';
35
35
  import '@anthropic-ai/claude-agent-sdk';
package/dist/lib.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./types-Bd_TGr0F.cjs');
4
- var persistence = require('./limits-CmzISka2.cjs');
3
+ var index = require('./types-CphWjqHc.cjs');
4
+ var persistence = require('./limits-o29KXBgj.cjs');
5
5
  require('axios');
6
6
  require('node:events');
7
7
  require('socket.io-client');
package/dist/lib.d.cts CHANGED
@@ -1648,17 +1648,33 @@ declare class ApiClient {
1648
1648
  */
1649
1649
  getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini'): Promise<any | null>;
1650
1650
  /**
1651
- * Mark a session as inactive on the server (active=false). Does NOT
1652
- * change `lifecycleState`, so the session remains visible in the app
1653
- * and resumable — same effect as the in-app "Archive" button hitting
1654
- * the /archive endpoint, but without the extra metadata.
1651
+ * B-505: mark a session OFFLINE on the server (active=false, `archivedAt`
1652
+ * untouched) — the same row state a missed keepalive produces, so the web
1653
+ * shows it offline and resumable instead of archived.
1655
1654
  *
1656
- * Used during graceful shutdown (Ctrl-C / SIGTERM) as a synchronous
1657
- * fallback for the socket-based session-end signal: even if the
1658
- * socket emit doesn't drain before the process exits, the HTTP
1659
- * response confirms the deactivate landed.
1655
+ * Used when the wrapper exits for an infrastructure reason (SIGTERM /
1656
+ * SIGINT: daemon stop, supervisor restart, machine shutdown) as a
1657
+ * synchronous fallback for the socket `session-end` signal: even if the
1658
+ * socket emit doesn't drain before the process exits, the HTTP response
1659
+ * confirms the row went inactive.
1660
+ *
1661
+ * This MUST NOT hit `/archive`: since B-265 that route writes the
1662
+ * server-owned `archivedAt` tombstone (the only thing that distinguishes
1663
+ * "archived" from "offline"), and the server answers it with a
1664
+ * `session-archive` command that makes the wrapper archive itself. That is
1665
+ * exactly how every dev-sg upgrade archived the sessions mid-turn on
1666
+ * 2026-09-25. A server without `/deactivate` returns 404, which is a
1667
+ * compatible no-op: `session-end` already sets active=false there.
1660
1668
  */
1661
1669
  deactivateSession(sessionId: string): Promise<boolean>;
1670
+ /**
1671
+ * Archive a session on the server: writes the `archivedAt` tombstone and
1672
+ * makes the server tell every wrapper of this session to exit. Only for an
1673
+ * intentional end of life — the user's Archive / kill, a crash the wrapper
1674
+ * cannot recover from, a terminal mirror whose pane went back to a shell.
1675
+ * Never call this from a signal handler; see `deactivateSession`.
1676
+ */
1677
+ archiveSession(sessionId: string): Promise<boolean>;
1662
1678
  /**
1663
1679
  * B-265: one session by id — the message cursor (`seq`), metadata and
1664
1680
  * agent state with their versions, decrypted with the session key the
package/dist/lib.d.mts CHANGED
@@ -1648,17 +1648,33 @@ declare class ApiClient {
1648
1648
  */
1649
1649
  getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini'): Promise<any | null>;
1650
1650
  /**
1651
- * Mark a session as inactive on the server (active=false). Does NOT
1652
- * change `lifecycleState`, so the session remains visible in the app
1653
- * and resumable — same effect as the in-app "Archive" button hitting
1654
- * the /archive endpoint, but without the extra metadata.
1651
+ * B-505: mark a session OFFLINE on the server (active=false, `archivedAt`
1652
+ * untouched) — the same row state a missed keepalive produces, so the web
1653
+ * shows it offline and resumable instead of archived.
1655
1654
  *
1656
- * Used during graceful shutdown (Ctrl-C / SIGTERM) as a synchronous
1657
- * fallback for the socket-based session-end signal: even if the
1658
- * socket emit doesn't drain before the process exits, the HTTP
1659
- * response confirms the deactivate landed.
1655
+ * Used when the wrapper exits for an infrastructure reason (SIGTERM /
1656
+ * SIGINT: daemon stop, supervisor restart, machine shutdown) as a
1657
+ * synchronous fallback for the socket `session-end` signal: even if the
1658
+ * socket emit doesn't drain before the process exits, the HTTP response
1659
+ * confirms the row went inactive.
1660
+ *
1661
+ * This MUST NOT hit `/archive`: since B-265 that route writes the
1662
+ * server-owned `archivedAt` tombstone (the only thing that distinguishes
1663
+ * "archived" from "offline"), and the server answers it with a
1664
+ * `session-archive` command that makes the wrapper archive itself. That is
1665
+ * exactly how every dev-sg upgrade archived the sessions mid-turn on
1666
+ * 2026-09-25. A server without `/deactivate` returns 404, which is a
1667
+ * compatible no-op: `session-end` already sets active=false there.
1660
1668
  */
1661
1669
  deactivateSession(sessionId: string): Promise<boolean>;
1670
+ /**
1671
+ * Archive a session on the server: writes the `archivedAt` tombstone and
1672
+ * makes the server tell every wrapper of this session to exit. Only for an
1673
+ * intentional end of life — the user's Archive / kill, a crash the wrapper
1674
+ * cannot recover from, a terminal mirror whose pane went back to a shell.
1675
+ * Never call this from a signal handler; see `deactivateSession`.
1676
+ */
1677
+ archiveSession(sessionId: string): Promise<boolean>;
1662
1678
  /**
1663
1679
  * B-265: one session by id — the message cursor (`seq`), metadata and
1664
1680
  * agent state with their versions, decrypted with the session key the
package/dist/lib.mjs CHANGED
@@ -1,5 +1,5 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema } from './types-D5RWyBE5.mjs';
2
- export { c as configuration, l as logger } from './limits-Dt7_unH4.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema } from './types-BEVzYSoV.mjs';
2
+ export { c as configuration, l as logger } from './limits-DmMdchTz.mjs';
3
3
  import 'axios';
4
4
  import 'node:events';
5
5
  import 'socket.io-client';
@@ -1,17 +1,17 @@
1
- import { mkdir, chmod, writeFile, readFile, open, unlink, stat, rename } from 'node:fs/promises';
2
- import { mkdirSync, chmodSync, readdirSync, statSync, appendFileSync, writeFileSync, existsSync, readFileSync, constants, unlinkSync, renameSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
- import { join, basename } from 'node:path';
5
1
  import * as z from 'zod';
6
2
  import { z as z$1 } from 'zod';
3
+ import { isCuid, createId } from '@paralleldrive/cuid2';
4
+ import { mkdir, chmod, writeFile, readFile, open, unlink, stat, rename } from 'node:fs/promises';
5
+ import { mkdirSync, chmodSync, readdirSync, statSync, appendFileSync, writeFileSync, existsSync, readFileSync, constants, unlinkSync, renameSync } from 'node:fs';
7
6
  import { randomBytes, createCipheriv, createDecipheriv, createHash } from 'node:crypto';
8
7
  import tweetnacl from 'tweetnacl';
9
8
  import chalk from 'chalk';
10
9
  import { inspect } from 'node:util';
11
- import { isCuid, createId } from '@paralleldrive/cuid2';
10
+ import { homedir } from 'node:os';
11
+ import { join, basename } from 'node:path';
12
12
 
13
13
  var name = "very-happy-cli";
14
- var version = "0.2.154";
14
+ var version = "0.2.156";
15
15
  var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
16
16
  var author = "Kirill Dubovitskiy";
17
17
  var contributors = [
@@ -128,12 +128,12 @@ var dependencies = {
128
128
  zod: "^4.0.0"
129
129
  };
130
130
  var optionalDependencies = {
131
- "very-happy-tools-arm64-darwin": "0.2.154",
132
- "very-happy-tools-x64-darwin": "0.2.154",
133
- "very-happy-tools-arm64-linux": "0.2.154",
134
- "very-happy-tools-x64-linux": "0.2.154",
135
- "very-happy-tools-arm64-win32": "0.2.154",
136
- "very-happy-tools-x64-win32": "0.2.154"
131
+ "very-happy-tools-arm64-darwin": "0.2.156",
132
+ "very-happy-tools-x64-darwin": "0.2.156",
133
+ "very-happy-tools-arm64-linux": "0.2.156",
134
+ "very-happy-tools-x64-linux": "0.2.156",
135
+ "very-happy-tools-arm64-win32": "0.2.156",
136
+ "very-happy-tools-x64-win32": "0.2.156"
137
137
  };
138
138
  var devDependencies = {
139
139
  "@slopus/happy-wire": "workspace:*",
@@ -2042,6 +2042,302 @@ function getAgentCodeDefaults(flavor) {
2042
2042
  return AGENT_CODE_DEFAULTS[normalizeAgentKey(flavor)];
2043
2043
  }
2044
2044
 
2045
+ const SESSION_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
2046
+ const TERMINAL_ID_RE = /^[a-zA-Z0-9_-]{1,64}$/;
2047
+ function isValidSessionId(id) {
2048
+ return typeof id === "string" && SESSION_ID_RE.test(id);
2049
+ }
2050
+ function isValidTerminalId(id) {
2051
+ return typeof id === "string" && TERMINAL_ID_RE.test(id);
2052
+ }
2053
+
2054
+ function daemonControlHeaders(controlToken) {
2055
+ return {
2056
+ "Content-Type": "application/json",
2057
+ ...controlToken ? { Authorization: `Bearer ${controlToken}` } : {}
2058
+ };
2059
+ }
2060
+ async function daemonPost(path, body) {
2061
+ const state = await readDaemonState();
2062
+ if (!state?.httpPort) {
2063
+ const errorMessage = "No daemon running, no state file found";
2064
+ logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
2065
+ return {
2066
+ error: errorMessage
2067
+ };
2068
+ }
2069
+ try {
2070
+ process.kill(state.pid, 0);
2071
+ } catch (error) {
2072
+ const errorMessage = "Daemon is not running, file is stale";
2073
+ logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
2074
+ return {
2075
+ error: errorMessage
2076
+ };
2077
+ }
2078
+ try {
2079
+ const timeout = process.env.HAPPY_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.HAPPY_DAEMON_HTTP_TIMEOUT) : 1e4;
2080
+ const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
2081
+ method: "POST",
2082
+ // A state written by an old daemon has no token and must remain callable;
2083
+ // a new daemon always writes one and rejects requests without it.
2084
+ headers: daemonControlHeaders(state.controlToken),
2085
+ body: JSON.stringify(body || {}),
2086
+ // Mostly increased for stress test
2087
+ signal: AbortSignal.timeout(timeout)
2088
+ });
2089
+ if (!response.ok) {
2090
+ const failure = await response.json().catch(() => null);
2091
+ const reason = typeof failure?.error === "string" && failure.error ? failure.error : `HTTP ${response.status}`;
2092
+ const errorMessage = `Request failed: ${path}, ${reason}`;
2093
+ logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
2094
+ return {
2095
+ error: errorMessage
2096
+ };
2097
+ }
2098
+ return await response.json();
2099
+ } catch (error) {
2100
+ const errorMessage = `Request failed: ${path}, ${error instanceof Error ? error.message : "Unknown error"}`;
2101
+ logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
2102
+ return {
2103
+ error: errorMessage
2104
+ };
2105
+ }
2106
+ }
2107
+ const SESSION_STARTED_RETRY_TIMEOUT_MS = 3e3;
2108
+ const SESSION_STARTED_RETRY_INTERVAL_MS = 100;
2109
+ async function notifyDaemonSessionStarted(sessionId, metadata, encryption) {
2110
+ const payload = { sessionId, metadata, encryption };
2111
+ const deadline = Date.now() + SESSION_STARTED_RETRY_TIMEOUT_MS;
2112
+ let result;
2113
+ while (true) {
2114
+ result = await daemonPost("/session-started", payload);
2115
+ if (!result?.error) {
2116
+ return result;
2117
+ }
2118
+ if (Date.now() >= deadline) {
2119
+ return result;
2120
+ }
2121
+ await new Promise((resolve) => setTimeout(resolve, SESSION_STARTED_RETRY_INTERVAL_MS));
2122
+ }
2123
+ }
2124
+ async function listDaemonSessions() {
2125
+ const result = await daemonPost("/list");
2126
+ return result.children || [];
2127
+ }
2128
+ async function stopDaemonSession(sessionId) {
2129
+ const result = await daemonPost("/stop-session", { sessionId });
2130
+ return result.success || false;
2131
+ }
2132
+ async function spawnDaemonSession(directory, sessionId, opts) {
2133
+ const result = await daemonPost("/spawn-session", {
2134
+ directory,
2135
+ sessionId,
2136
+ spawnedBy: opts?.spawnedBy,
2137
+ permissionMode: opts?.permissionMode,
2138
+ agent: opts?.agent,
2139
+ environmentVariables: opts?.environmentVariables,
2140
+ resumeClaudeSessionId: opts?.resumeClaudeSessionId,
2141
+ resumeCodexThreadId: opts?.resumeCodexThreadId,
2142
+ parentSessionId: opts?.parentSessionId
2143
+ });
2144
+ return result;
2145
+ }
2146
+ const ROUTE_NOT_FOUND_RE = /, (Not Found|HTTP 404)$/;
2147
+ async function resumeDaemonSession(sessionId, opts) {
2148
+ const result = await daemonPost("/resume-session", {
2149
+ sessionId,
2150
+ ...opts?.model !== void 0 ? { model: opts.model } : {},
2151
+ ...opts?.permissionMode !== void 0 ? { permissionMode: opts.permissionMode } : {}
2152
+ });
2153
+ if (result?.error) {
2154
+ const raw = String(result.error);
2155
+ const error = ROUTE_NOT_FOUND_RE.test(raw) ? "daemon too old to resume sessions (no /resume-session route); upgrade very-happy-cli and run `very-happy daemon start`" : raw.replace(/^Request failed: \/resume-session, /, "");
2156
+ return { success: false, error };
2157
+ }
2158
+ if (result?.success === true && typeof result.sessionId === "string") {
2159
+ return { success: true, sessionId: result.sessionId };
2160
+ }
2161
+ return { success: false, error: typeof result?.error === "string" ? result.error : "unexpected daemon response" };
2162
+ }
2163
+ async function notifyDaemonSessionEvent(sessionId, event, spawnedBy) {
2164
+ return daemonPost("/session-event", { sessionId, event, spawnedBy });
2165
+ }
2166
+ async function notifyDaemonClaudeAuthFailed(sessionId) {
2167
+ return daemonPost("/session-event", { sessionId, event: "auth_failed" });
2168
+ }
2169
+ async function notifyDaemonTurnEvent(sessionId, event) {
2170
+ return daemonPost("/session-event", { sessionId, event });
2171
+ }
2172
+ function reportSessionEditToDaemon(edit) {
2173
+ void daemonPost("/session-edit", edit).then((result) => {
2174
+ if (result?.error) logger.debug(`[CONTROL CLIENT] session-edit not recorded: ${result.error}`);
2175
+ }).catch((error) => {
2176
+ logger.debug("[CONTROL CLIENT] session-edit failed:", error);
2177
+ });
2178
+ }
2179
+ async function listDaemonPeers() {
2180
+ const result = await daemonPost("/peers");
2181
+ if (result?.error) {
2182
+ const reason = String(result.error);
2183
+ if (/404|Not Found/i.test(reason)) {
2184
+ throw new Error("The running daemon does not support session peers; restart it on a CLI that does (`very-happy daemon start` after updating)");
2185
+ }
2186
+ throw new Error(reason);
2187
+ }
2188
+ return Array.isArray(result?.sessions) ? result.sessions : [];
2189
+ }
2190
+ async function stopDaemonHttp() {
2191
+ await daemonPost("/stop");
2192
+ }
2193
+ async function pushClipboardViaDaemon(text, terminalId) {
2194
+ const result = await daemonPost("/clipboard", { text, ...terminalId ? { terminalId } : {} });
2195
+ if (result?.error && result.delivered === void 0) {
2196
+ return { delivered: false, error: result.error };
2197
+ }
2198
+ return result;
2199
+ }
2200
+ async function pushFilePreviewViaDaemon(terminalId, path, mode = "file") {
2201
+ const result = await daemonPost("/file-preview", { terminalId, path, mode });
2202
+ if (result?.error) return { delivered: false, error: result.error };
2203
+ return { delivered: result?.delivered === true, ...result?.delivered === true ? {} : { error: "preview was not delivered" } };
2204
+ }
2205
+ async function setTerminalTitleViaDaemon(terminalId, title, ifAbsent = false) {
2206
+ const result = await daemonPost("/terminal-title", { terminalId, title, ifAbsent });
2207
+ if (result?.error) {
2208
+ return { ok: false, error: result.error };
2209
+ }
2210
+ return { ok: result?.status === "ok", error: result?.status === "ok" ? void 0 : "unexpected daemon response" };
2211
+ }
2212
+ async function checkIfDaemonRunningAndCleanupStaleState() {
2213
+ const state = await readDaemonState();
2214
+ if (!state) {
2215
+ return false;
2216
+ }
2217
+ try {
2218
+ process.kill(state.pid, 0);
2219
+ } catch {
2220
+ logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
2221
+ await cleanupDaemonState();
2222
+ return false;
2223
+ }
2224
+ if (state.httpPort) {
2225
+ try {
2226
+ const response = await fetch(`http://127.0.0.1:${state.httpPort}/list`, {
2227
+ method: "POST",
2228
+ headers: daemonControlHeaders(state.controlToken),
2229
+ body: "{}",
2230
+ signal: AbortSignal.timeout(2e3)
2231
+ });
2232
+ if (response.ok) {
2233
+ return true;
2234
+ }
2235
+ } catch {
2236
+ logger.debug(`[DAEMON RUN] PID ${state.pid} is alive but HTTP health check failed on port ${state.httpPort}, cleaning up stale state`);
2237
+ await cleanupDaemonState();
2238
+ return false;
2239
+ }
2240
+ }
2241
+ return true;
2242
+ }
2243
+ async function isDaemonRunningCurrentlyInstalledHappyVersion() {
2244
+ logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
2245
+ const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
2246
+ if (!runningDaemon) {
2247
+ logger.debug("[DAEMON CONTROL] No daemon running, returning false");
2248
+ return false;
2249
+ }
2250
+ const state = await readDaemonState();
2251
+ if (!state) {
2252
+ logger.debug("[DAEMON CONTROL] No daemon state found, returning false");
2253
+ return false;
2254
+ }
2255
+ const currentCliVersion = configuration.currentCliVersion;
2256
+ logger.debug(`[DAEMON CONTROL] Current CLI version: ${currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
2257
+ return currentCliVersion === state.startedWithCliVersion;
2258
+ }
2259
+ async function cleanupDaemonState() {
2260
+ try {
2261
+ await clearDaemonState();
2262
+ logger.debug("[DAEMON RUN] Daemon state file removed");
2263
+ } catch (error) {
2264
+ logger.debug("[DAEMON RUN] Error cleaning up daemon metadata", error);
2265
+ }
2266
+ }
2267
+ async function stopDaemon() {
2268
+ try {
2269
+ const state = await readDaemonState();
2270
+ if (!state) {
2271
+ logger.debug("No daemon state found");
2272
+ return;
2273
+ }
2274
+ logger.debug(`Stopping daemon with PID ${state.pid}`);
2275
+ try {
2276
+ await stopDaemonHttp();
2277
+ await waitForProcessDeath(state.pid, 2e3);
2278
+ logger.debug("Daemon stopped gracefully via HTTP");
2279
+ return;
2280
+ } catch (error) {
2281
+ logger.debug("HTTP stop failed, will force kill", error);
2282
+ }
2283
+ try {
2284
+ process.kill(state.pid, "SIGKILL");
2285
+ logger.debug("Force killed daemon");
2286
+ } catch (error) {
2287
+ logger.debug("Daemon already dead");
2288
+ }
2289
+ } catch (error) {
2290
+ logger.debug("Error stopping daemon", error);
2291
+ }
2292
+ }
2293
+ async function waitForProcessDeath(pid, timeout) {
2294
+ const start = Date.now();
2295
+ while (Date.now() - start < timeout) {
2296
+ try {
2297
+ process.kill(pid, 0);
2298
+ await new Promise((resolve) => setTimeout(resolve, 100));
2299
+ } catch {
2300
+ return;
2301
+ }
2302
+ }
2303
+ throw new Error("Process did not die within timeout");
2304
+ }
2305
+
2306
+ async function delay(ms) {
2307
+ return new Promise((resolve) => setTimeout(resolve, ms));
2308
+ }
2309
+ function exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount) {
2310
+ let maxDelayRet = minDelay + (maxDelay - minDelay) / maxFailureCount * Math.min(currentFailureCount, maxFailureCount);
2311
+ return Math.round(Math.random() * maxDelayRet);
2312
+ }
2313
+ function createBackoff(opts) {
2314
+ return async (callback) => {
2315
+ let currentFailureCount = 0;
2316
+ const minDelay = opts && opts.minDelay !== void 0 ? opts.minDelay : 250;
2317
+ const maxDelay = opts && opts.maxDelay !== void 0 ? opts.maxDelay : 1e3;
2318
+ const maxFailureCount = opts && opts.maxFailureCount !== void 0 ? opts.maxFailureCount : 50;
2319
+ while (true) {
2320
+ try {
2321
+ return await callback();
2322
+ } catch (e) {
2323
+ if (currentFailureCount < maxFailureCount) {
2324
+ currentFailureCount++;
2325
+ }
2326
+ if (opts && opts.onError) {
2327
+ opts.onError(e, currentFailureCount);
2328
+ }
2329
+ let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount);
2330
+ await delay(waitForRequest);
2331
+ }
2332
+ }
2333
+ };
2334
+ }
2335
+ let backoff = createBackoff({
2336
+ onError: (e, failuresCount) => {
2337
+ logger.debug(`[BACKOFF] retry ${failuresCount}:`, e?.message || e);
2338
+ }
2339
+ });
2340
+
2045
2341
  const CLIPBOARD_MAX_BYTES = 256 * 1024;
2046
2342
  const CLIPBOARD_HISTORY_MAX_BYTES = 32 * 1024;
2047
2343
  const CLIPBOARD_TOOL_NAME = "copy_to_clipboard";
@@ -2077,4 +2373,4 @@ function prepareClipboardHistoryText(input) {
2077
2373
  return { text, truncated: true, totalBytes: prepared.totalBytes };
2078
2374
  }
2079
2375
 
2080
- export { writeCredentialsLegacy as $, AUTOMATION_NAME_PATTERN as A, BUILTIN_TODO_SKILL as B, CLIPBOARD_TOOL_NAME as C, decrypt as D, decodeBase64 as E, readDaemonState as F, clearDaemonState as G, decryptBlob as H, RelayAssignmentResponseSchema as I, prepareClipboardText as J, prepareClipboardHistoryText as K, credentialRelayProblem as L, RelayCandidatesResponseSchema as M, readTrackedClaudeSessionIds as N, readTrackedCodexThreadIds as O, libsodiumEncryptForPublicKey as P, libsodiumPublicKeyFromSecretKey as Q, ReleaseDrainNoticeSchema as R, getRandomBytes as S, redactLogValue as T, STREAM_DELTA_MAX_CHARS as U, ensurePrivateDirectory as V, hardenPrivateFile as W, readCredentials as X, AGENT_VERSION_SOURCES as Y, readSettingsOutcome as Z, encodeBase64Url as _, CLIPBOARD_TOOL_TITLE as a, writeCredentialsDataKey as a0, decryptWithDataKey as a1, decryptLegacy as a2, persistSession as a3, getAgentCodeDefaults as a4, normalizeAgentKey as a5, isClaudeOpus55Model as a6, supportsClaudeOpus55 as a7, AUTOMATION_SUMMARY_MAX_CHARS as a8, AUTOMATION_CLAIM_MAX_RUNS as a9, AUTOMATION_DEFAULT_LEASE_MS as aa, acquireDaemonLock as ab, deletePersistedSessions as ac, writeDaemonState as ad, writeSettings as ae, releaseDaemonLock as af, ContextUsageSchema as ag, clearCredentials as ah, clearMachineId as ai, SandboxConfigSchema as aj, getLatestDaemonLog as ak, persistence as al, CLIPBOARD_TOOL_DESCRIPTION as b, configuration as c, readPersistedSessions as d, createEnvelope as e, readCredentialsForConfiguredRelay as f, createBuiltinTodoClient as g, CLIPBOARD_MAX_BYTES as h, isAutomationRunTerminal as i, encodeBase64 as j, BUILTIN_TODO_DISCOVERY as k, logger as l, ensurePrivateDirectorySync as m, hardenPrivateFileSync as n, normalizeHttpEndpoint as o, packageJson as p, AutomationNameSchema as q, readSettings as r, streamKeyOf as s, AutomationConcurrencySchema as t, updateSettings as u, AutomationActionSchema as v, writePrivateFileSync as w, AutomationTriggerSchema as x, AgentVersionsSchema as y, encrypt as z };
2376
+ export { readTrackedClaudeSessionIds as $, AUTOMATION_NAME_PATTERN as A, BUILTIN_TODO_SKILL as B, CLIPBOARD_TOOL_NAME as C, hardenPrivateFileSync as D, writePrivateFileSync as E, packageJson as F, normalizeHttpEndpoint as G, AutomationNameSchema as H, AutomationConcurrencySchema as I, AutomationActionSchema as J, AutomationTriggerSchema as K, encrypt as L, decodeBase64 as M, decrypt as N, listDaemonSessions as O, resumeDaemonSession as P, listDaemonPeers as Q, AgentVersionsSchema as R, backoff as S, ReleaseDrainNoticeSchema as T, decryptBlob as U, RelayAssignmentResponseSchema as V, notifyDaemonTurnEvent as W, prepareClipboardText as X, prepareClipboardHistoryText as Y, credentialRelayProblem as Z, RelayCandidatesResponseSchema as _, CLIPBOARD_TOOL_TITLE as a, readTrackedCodexThreadIds as a0, isValidTerminalId as a1, exponentialBackoffDelay as a2, libsodiumEncryptForPublicKey as a3, libsodiumPublicKeyFromSecretKey as a4, getRandomBytes as a5, redactLogValue as a6, notifyDaemonSessionEvent as a7, STREAM_DELTA_MAX_CHARS as a8, ensurePrivateDirectory as a9, releaseDaemonLock as aA, ContextUsageSchema as aB, clearCredentials as aC, clearMachineId as aD, SandboxConfigSchema as aE, getLatestDaemonLog as aF, persistence as aG, hardenPrivateFile as aa, notifyDaemonClaudeAuthFailed as ab, readCredentials as ac, readDaemonState as ad, AGENT_VERSION_SOURCES as ae, readSettingsOutcome as af, encodeBase64Url as ag, writeCredentialsLegacy as ah, writeCredentialsDataKey as ai, decryptWithDataKey as aj, decryptLegacy as ak, persistSession as al, getAgentCodeDefaults as am, normalizeAgentKey as an, isClaudeOpus55Model as ao, supportsClaudeOpus55 as ap, AUTOMATION_SUMMARY_MAX_CHARS as aq, AUTOMATION_CLAIM_MAX_RUNS as ar, AUTOMATION_DEFAULT_LEASE_MS as as, isDaemonRunningCurrentlyInstalledHappyVersion as at, stopDaemon as au, acquireDaemonLock as av, deletePersistedSessions as aw, writeDaemonState as ax, writeSettings as ay, cleanupDaemonState as az, CLIPBOARD_TOOL_DESCRIPTION as b, configuration as c, readPersistedSessions as d, createEnvelope as e, isAutomationRunTerminal as f, delay as g, readCredentialsForConfiguredRelay as h, isValidSessionId as i, createBuiltinTodoClient as j, checkIfDaemonRunningAndCleanupStaleState as k, logger as l, spawnDaemonSession as m, stopDaemonSession as n, CLIPBOARD_MAX_BYTES as o, pushClipboardViaDaemon as p, pushFilePreviewViaDaemon as q, readSettings as r, streamKeyOf as s, setTerminalTitleViaDaemon as t, updateSettings as u, notifyDaemonSessionStarted as v, encodeBase64 as w, BUILTIN_TODO_DISCOVERY as x, reportSessionEditToDaemon as y, ensurePrivateDirectorySync as z };