very-happy-cli 0.2.137 → 0.2.139

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 (45) hide show
  1. package/dist/{AcpBackend-POcimRNQ.mjs → AcpBackend-BwTEBAyP.mjs} +2 -2
  2. package/dist/{AcpBackend-Bcmm51Ya.cjs → AcpBackend-ioCPQLt4.cjs} +2 -2
  3. package/dist/{AcpSessionManager-D54Juu2-.mjs → AcpSessionManager-Bh3KETfz.mjs} +1 -1
  4. package/dist/{AcpSessionManager-CjrwSZbh.cjs → AcpSessionManager-o9T6W69-.cjs} +1 -1
  5. package/dist/{agentGuidance-DulugsMk.mjs → agentGuidance-DRnXdFAY.mjs} +1 -1
  6. package/dist/{agentGuidance-D1tKg2UW.cjs → agentGuidance-Dxt2uz3R.cjs} +1 -1
  7. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  8. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  9. package/dist/{config-Dr3A4IGp.cjs → config-nyMHCx-i.cjs} +4 -4
  10. package/dist/{config-C1gQprK_.mjs → config-o9NFsj8x.mjs} +4 -4
  11. package/dist/{index-D5MRxCHs.mjs → index-BZr5l8TH.mjs} +185 -230
  12. package/dist/{index-B11rFE7f.mjs → index-BkmfJyCf.mjs} +7 -7
  13. package/dist/{index-B0KIJdSt.cjs → index-C3NtU4Ch.cjs} +216 -266
  14. package/dist/{index-C3agc8KT.mjs → index-CTemcNko.mjs} +4 -4
  15. package/dist/{index-hGg5G5uO.cjs → index-gkFhwPXq.cjs} +4 -4
  16. package/dist/{index-C3JqT3Ql.cjs → index-klhxcpC8.cjs} +7 -7
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.mjs +4 -4
  19. package/dist/{installTerminalHooks-DfZTXVWO.mjs → installTerminalHooks-8CHTgUC-.mjs} +4 -4
  20. package/dist/{installTerminalHooks-Culr259-.cjs → installTerminalHooks-DZUBgnFh.cjs} +4 -4
  21. package/dist/lib.cjs +2 -2
  22. package/dist/lib.d.cts +21 -0
  23. package/dist/lib.d.mts +21 -0
  24. package/dist/lib.mjs +2 -2
  25. package/dist/{limits-CJrrAhB4.cjs → limits-C_RGzva6.cjs} +26 -7
  26. package/dist/{limits-CAD4UF8w.mjs → limits-DefXc1TL.mjs} +26 -8
  27. package/dist/{mcp-CdnZbYKV.mjs → mcp-8DcunR0J.mjs} +19 -19
  28. package/dist/{mcp-5Jf6V4eb.cjs → mcp-czNIfD59.cjs} +21 -21
  29. package/dist/{runGemini-CwWEorS7.mjs → runGemini-CDUObQTD.mjs} +6 -6
  30. package/dist/{runGemini-GjBPzXEt.cjs → runGemini-Dy3y-lD9.cjs} +7 -7
  31. package/dist/{runOpenClaw-BZHHy9T7.mjs → runOpenClaw-B6KMCsVB.mjs} +5 -5
  32. package/dist/{runOpenClaw-DGe5c571.cjs → runOpenClaw-CJusckVM.cjs} +6 -6
  33. package/dist/{send-teYpfTtw.cjs → send-B4Qo-87h.cjs} +4 -4
  34. package/dist/{send-BN2PncAH.mjs → send-BT32Jiel.mjs} +4 -4
  35. package/dist/{sessions-qP1YUf5E.cjs → sessions-CN2y0xBl.cjs} +4 -4
  36. package/dist/{sessions-TLUfYdLX.mjs → sessions-Dh0Lg-5z.mjs} +4 -4
  37. package/dist/{spawn-D3P8wJ_r.mjs → spawn-DFIyobCt.mjs} +6 -6
  38. package/dist/{spawn-GekaZ7h2.cjs → spawn-jWj3xFhX.cjs} +8 -8
  39. package/dist/{teams-BSqAW2S5.cjs → teams-BhFspeAY.cjs} +4 -4
  40. package/dist/{teams-WSjEXh4d.mjs → teams-CZ9ICWeB.mjs} +4 -4
  41. package/dist/{todo-DsnXWl77.cjs → todo-B0kvqUTQ.cjs} +3 -3
  42. package/dist/{todo-BblDgT77.mjs → todo-jL3ttna3.mjs} +3 -3
  43. package/dist/{types-B8g_V-LQ.mjs → types-CrKeJuyF.mjs} +616 -13
  44. package/dist/{types-BAhQF6Xp.cjs → types-CxGm_YJ-.cjs} +629 -13
  45. package/package.json +7 -7
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var axios = require('axios');
4
- var persistence = require('./limits-CJrrAhB4.cjs');
4
+ var persistence = require('./limits-C_RGzva6.cjs');
5
5
  var node_events = require('node:events');
6
6
  var socket_ioClient = require('socket.io-client');
7
7
  var z = require('zod');
@@ -1004,6 +1004,15 @@ z.z.object({
1004
1004
  rpcAvailable: z.z.boolean(),
1005
1005
  detectedAt: z.z.number()
1006
1006
  }).optional(),
1007
+ /**
1008
+ * B-464 capability flag: this daemon answers `codex-list-history` /
1009
+ * `codex-import-session` (import a Codex CLI / desktop thread that was never
1010
+ * started through very-happy). Same trust rule (`detectedAt >= startedAt`).
1011
+ */
1012
+ codexHistory: z.z.object({
1013
+ rpcAvailable: z.z.boolean(),
1014
+ detectedAt: z.z.number()
1015
+ }).optional(),
1007
1016
  /** Relay-owned CLI compatibility/update policy last checked by this daemon. */
1008
1017
  cliUpdate: CliUpdateStateSchema.optional(),
1009
1018
  /**
@@ -1369,7 +1378,7 @@ class RpcHandlerManager {
1369
1378
  }
1370
1379
  }
1371
1380
 
1372
- const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BAhQF6Xp.cjs', document.baseURI).href))));
1381
+ const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-CxGm_YJ-.cjs', document.baseURI).href))));
1373
1382
  function projectPath() {
1374
1383
  const path$1 = path.resolve(__dirname$1, "..");
1375
1384
  return path$1;
@@ -2911,6 +2920,268 @@ const MAX_CHAT_ATTACHMENT_SOURCE_BYTES = 50 * 1024 * 1024;
2911
2920
  const CHAT_ATTACHMENT_ENCRYPTION_OVERHEAD_BYTES = 40;
2912
2921
  const MAX_CHAT_ATTACHMENT_ENCRYPTED_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES + CHAT_ATTACHMENT_ENCRYPTION_OVERHEAD_BYTES;
2913
2922
 
2923
+ const TURN_LEASE_MS = 6e4;
2924
+ const TURN_BUSY_TTL_MS = 15e4;
2925
+ class TurnActivityTracker {
2926
+ constructor(ttlMs = TURN_BUSY_TTL_MS) {
2927
+ this.ttlMs = ttlMs;
2928
+ }
2929
+ busy = /* @__PURE__ */ new Map();
2930
+ apply(sessionId, event, now = Date.now()) {
2931
+ if (event === "turn_started") this.busy.set(sessionId, now);
2932
+ else this.busy.delete(sessionId);
2933
+ }
2934
+ /** The wrapper is gone: whatever it reported no longer holds. */
2935
+ forget(sessionId) {
2936
+ this.busy.delete(sessionId);
2937
+ }
2938
+ /** Sessions whose last renewal is still inside the TTL. */
2939
+ activeSessions(now = Date.now()) {
2940
+ const out = [];
2941
+ for (const [id, at] of this.busy) {
2942
+ if (now - at <= this.ttlMs) out.push(id);
2943
+ else this.busy.delete(id);
2944
+ }
2945
+ return out;
2946
+ }
2947
+ hasActiveTurn(now = Date.now()) {
2948
+ return this.activeSessions(now).length > 0;
2949
+ }
2950
+ }
2951
+ class TurnReporter {
2952
+ constructor(send, leaseMs = TURN_LEASE_MS) {
2953
+ this.send = send;
2954
+ this.leaseMs = leaseMs;
2955
+ }
2956
+ thinking = false;
2957
+ lastStartedAt = 0;
2958
+ /** Called on every keepAlive tick; emits only on an edge or a due renewal. */
2959
+ observe(thinking, now = Date.now()) {
2960
+ if (thinking) {
2961
+ const due = !this.thinking || now - this.lastStartedAt >= this.leaseMs;
2962
+ this.thinking = true;
2963
+ if (!due) return null;
2964
+ this.lastStartedAt = now;
2965
+ this.send("turn_started");
2966
+ return "turn_started";
2967
+ }
2968
+ if (!this.thinking) return null;
2969
+ this.thinking = false;
2970
+ this.lastStartedAt = 0;
2971
+ this.send("turn_ended");
2972
+ return "turn_ended";
2973
+ }
2974
+ }
2975
+
2976
+ function daemonControlHeaders(controlToken) {
2977
+ return {
2978
+ "Content-Type": "application/json",
2979
+ ...controlToken ? { Authorization: `Bearer ${controlToken}` } : {}
2980
+ };
2981
+ }
2982
+ async function daemonPost(path, body) {
2983
+ const state = await persistence.readDaemonState();
2984
+ if (!state?.httpPort) {
2985
+ const errorMessage = "No daemon running, no state file found";
2986
+ persistence.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
2987
+ return {
2988
+ error: errorMessage
2989
+ };
2990
+ }
2991
+ try {
2992
+ process.kill(state.pid, 0);
2993
+ } catch (error) {
2994
+ const errorMessage = "Daemon is not running, file is stale";
2995
+ persistence.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
2996
+ return {
2997
+ error: errorMessage
2998
+ };
2999
+ }
3000
+ try {
3001
+ const timeout = process.env.HAPPY_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.HAPPY_DAEMON_HTTP_TIMEOUT) : 1e4;
3002
+ const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
3003
+ method: "POST",
3004
+ // A state written by an old daemon has no token and must remain callable;
3005
+ // a new daemon always writes one and rejects requests without it.
3006
+ headers: daemonControlHeaders(state.controlToken),
3007
+ body: JSON.stringify(body || {}),
3008
+ // Mostly increased for stress test
3009
+ signal: AbortSignal.timeout(timeout)
3010
+ });
3011
+ if (!response.ok) {
3012
+ const failure = await response.json().catch(() => null);
3013
+ const reason = typeof failure?.error === "string" && failure.error ? failure.error : `HTTP ${response.status}`;
3014
+ const errorMessage = `Request failed: ${path}, ${reason}`;
3015
+ persistence.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
3016
+ return {
3017
+ error: errorMessage
3018
+ };
3019
+ }
3020
+ return await response.json();
3021
+ } catch (error) {
3022
+ const errorMessage = `Request failed: ${path}, ${error instanceof Error ? error.message : "Unknown error"}`;
3023
+ persistence.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
3024
+ return {
3025
+ error: errorMessage
3026
+ };
3027
+ }
3028
+ }
3029
+ const SESSION_STARTED_RETRY_TIMEOUT_MS = 3e3;
3030
+ const SESSION_STARTED_RETRY_INTERVAL_MS = 100;
3031
+ async function notifyDaemonSessionStarted(sessionId, metadata, encryption) {
3032
+ const payload = { sessionId, metadata, encryption };
3033
+ const deadline = Date.now() + SESSION_STARTED_RETRY_TIMEOUT_MS;
3034
+ let result;
3035
+ while (true) {
3036
+ result = await daemonPost("/session-started", payload);
3037
+ if (!result?.error) {
3038
+ return result;
3039
+ }
3040
+ if (Date.now() >= deadline) {
3041
+ return result;
3042
+ }
3043
+ await new Promise((resolve) => setTimeout(resolve, SESSION_STARTED_RETRY_INTERVAL_MS));
3044
+ }
3045
+ }
3046
+ async function listDaemonSessions() {
3047
+ const result = await daemonPost("/list");
3048
+ return result.children || [];
3049
+ }
3050
+ async function stopDaemonSession(sessionId) {
3051
+ const result = await daemonPost("/stop-session", { sessionId });
3052
+ return result.success || false;
3053
+ }
3054
+ async function spawnDaemonSession(directory, sessionId, opts) {
3055
+ const result = await daemonPost("/spawn-session", {
3056
+ directory,
3057
+ sessionId,
3058
+ spawnedBy: opts?.spawnedBy,
3059
+ permissionMode: opts?.permissionMode,
3060
+ agent: opts?.agent,
3061
+ environmentVariables: opts?.environmentVariables
3062
+ });
3063
+ return result;
3064
+ }
3065
+ async function notifyDaemonSessionEvent(sessionId, event, spawnedBy) {
3066
+ return daemonPost("/session-event", { sessionId, event, spawnedBy });
3067
+ }
3068
+ async function notifyDaemonClaudeAuthFailed(sessionId) {
3069
+ return daemonPost("/session-event", { sessionId, event: "auth_failed" });
3070
+ }
3071
+ async function notifyDaemonTurnEvent(sessionId, event) {
3072
+ return daemonPost("/session-event", { sessionId, event });
3073
+ }
3074
+ async function stopDaemonHttp() {
3075
+ await daemonPost("/stop");
3076
+ }
3077
+ async function pushClipboardViaDaemon(text) {
3078
+ const result = await daemonPost("/clipboard", { text });
3079
+ if (result?.error && result.delivered === void 0) {
3080
+ return { delivered: false, error: result.error };
3081
+ }
3082
+ return result;
3083
+ }
3084
+ async function setTerminalTitleViaDaemon(terminalId, title, ifAbsent = false) {
3085
+ const result = await daemonPost("/terminal-title", { terminalId, title, ifAbsent });
3086
+ if (result?.error) {
3087
+ return { ok: false, error: result.error };
3088
+ }
3089
+ return { ok: result?.status === "ok", error: result?.status === "ok" ? void 0 : "unexpected daemon response" };
3090
+ }
3091
+ async function checkIfDaemonRunningAndCleanupStaleState() {
3092
+ const state = await persistence.readDaemonState();
3093
+ if (!state) {
3094
+ return false;
3095
+ }
3096
+ try {
3097
+ process.kill(state.pid, 0);
3098
+ } catch {
3099
+ persistence.logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
3100
+ await cleanupDaemonState();
3101
+ return false;
3102
+ }
3103
+ if (state.httpPort) {
3104
+ try {
3105
+ const response = await fetch(`http://127.0.0.1:${state.httpPort}/list`, {
3106
+ method: "POST",
3107
+ headers: daemonControlHeaders(state.controlToken),
3108
+ body: "{}",
3109
+ signal: AbortSignal.timeout(2e3)
3110
+ });
3111
+ if (response.ok) {
3112
+ return true;
3113
+ }
3114
+ } catch {
3115
+ persistence.logger.debug(`[DAEMON RUN] PID ${state.pid} is alive but HTTP health check failed on port ${state.httpPort}, cleaning up stale state`);
3116
+ await cleanupDaemonState();
3117
+ return false;
3118
+ }
3119
+ }
3120
+ return true;
3121
+ }
3122
+ async function isDaemonRunningCurrentlyInstalledHappyVersion() {
3123
+ persistence.logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
3124
+ const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
3125
+ if (!runningDaemon) {
3126
+ persistence.logger.debug("[DAEMON CONTROL] No daemon running, returning false");
3127
+ return false;
3128
+ }
3129
+ const state = await persistence.readDaemonState();
3130
+ if (!state) {
3131
+ persistence.logger.debug("[DAEMON CONTROL] No daemon state found, returning false");
3132
+ return false;
3133
+ }
3134
+ const currentCliVersion = persistence.configuration.currentCliVersion;
3135
+ persistence.logger.debug(`[DAEMON CONTROL] Current CLI version: ${currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
3136
+ return currentCliVersion === state.startedWithCliVersion;
3137
+ }
3138
+ async function cleanupDaemonState() {
3139
+ try {
3140
+ await persistence.clearDaemonState();
3141
+ persistence.logger.debug("[DAEMON RUN] Daemon state file removed");
3142
+ } catch (error) {
3143
+ persistence.logger.debug("[DAEMON RUN] Error cleaning up daemon metadata", error);
3144
+ }
3145
+ }
3146
+ async function stopDaemon() {
3147
+ try {
3148
+ const state = await persistence.readDaemonState();
3149
+ if (!state) {
3150
+ persistence.logger.debug("No daemon state found");
3151
+ return;
3152
+ }
3153
+ persistence.logger.debug(`Stopping daemon with PID ${state.pid}`);
3154
+ try {
3155
+ await stopDaemonHttp();
3156
+ await waitForProcessDeath(state.pid, 2e3);
3157
+ persistence.logger.debug("Daemon stopped gracefully via HTTP");
3158
+ return;
3159
+ } catch (error) {
3160
+ persistence.logger.debug("HTTP stop failed, will force kill", error);
3161
+ }
3162
+ try {
3163
+ process.kill(state.pid, "SIGKILL");
3164
+ persistence.logger.debug("Force killed daemon");
3165
+ } catch (error) {
3166
+ persistence.logger.debug("Daemon already dead");
3167
+ }
3168
+ } catch (error) {
3169
+ persistence.logger.debug("Error stopping daemon", error);
3170
+ }
3171
+ }
3172
+ async function waitForProcessDeath(pid, timeout) {
3173
+ const start = Date.now();
3174
+ while (Date.now() - start < timeout) {
3175
+ try {
3176
+ process.kill(pid, 0);
3177
+ await new Promise((resolve) => setTimeout(resolve, 100));
3178
+ } catch {
3179
+ return;
3180
+ }
3181
+ }
3182
+ throw new Error("Process did not die within timeout");
3183
+ }
3184
+
2914
3185
  class ApiSessionClient extends node_events.EventEmitter {
2915
3186
  token;
2916
3187
  sessionId;
@@ -3692,10 +3963,14 @@ class ApiSessionClient extends node_events.EventEmitter {
3692
3963
  /**
3693
3964
  * Send a ping message to keep the connection alive
3694
3965
  */
3966
+ /** B-466: turn edges (and a periodic lease) reported to the local daemon so
3967
+ * auto-update waits for "no turn in flight" instead of "no sessions". */
3968
+ turnReporter = new TurnReporter((event) => void notifyDaemonTurnEvent(this.sessionId, event).catch((error) => persistence.logger.debug("[API] Failed to report turn event to daemon:", error)));
3695
3969
  keepAlive(thinking, mode) {
3696
3970
  if (process.env.DEBUG) {
3697
3971
  persistence.logger.debug(`[API] Sending keep alive message: ${thinking}`);
3698
3972
  }
3973
+ this.turnReporter.observe(thinking);
3699
3974
  this.socket.volatile.emit("session-alive", {
3700
3975
  sid: this.sessionId,
3701
3976
  time: Date.now(),
@@ -12384,7 +12659,7 @@ function isVeryHappyOneShotPrompt(firstPrompt) {
12384
12659
  return VERY_HAPPY_ONE_SHOT_PROMPT_PREFIXES.some((prefix) => text.startsWith(prefix));
12385
12660
  }
12386
12661
 
12387
- const UUID_RE$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12662
+ const UUID_RE$2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12388
12663
  async function listClaudeProjectDirs(projectsRoot) {
12389
12664
  let names;
12390
12665
  try {
@@ -12403,7 +12678,7 @@ async function listClaudeProjectDirs(projectsRoot) {
12403
12678
  }
12404
12679
  return dirs;
12405
12680
  }
12406
- async function collectCandidates(projectDirs, exclude) {
12681
+ async function collectCandidates$1(projectDirs, exclude) {
12407
12682
  const out = [];
12408
12683
  for (const dir of projectDirs) {
12409
12684
  let names;
@@ -12415,7 +12690,7 @@ async function collectCandidates(projectDirs, exclude) {
12415
12690
  for (const name of names) {
12416
12691
  if (!name.endsWith(".jsonl")) continue;
12417
12692
  const id = name.slice(0, -".jsonl".length);
12418
- if (!UUID_RE$1.test(id) || exclude.has(id.toLowerCase())) continue;
12693
+ if (!UUID_RE$2.test(id) || exclude.has(id.toLowerCase())) continue;
12419
12694
  const path = path$1.join(dir, name);
12420
12695
  try {
12421
12696
  const s = await promises.stat(path);
@@ -12434,7 +12709,7 @@ async function collectCandidates(projectDirs, exclude) {
12434
12709
  return true;
12435
12710
  });
12436
12711
  }
12437
- async function readHead(path, bytes) {
12712
+ async function readHead$1(path, bytes) {
12438
12713
  const fh = await promises.open(path, "r");
12439
12714
  try {
12440
12715
  const buffer = Buffer.alloc(bytes);
@@ -12444,11 +12719,11 @@ async function readHead(path, bytes) {
12444
12719
  await fh.close();
12445
12720
  }
12446
12721
  }
12447
- function oneLine(text, max) {
12722
+ function oneLine$1(text, max) {
12448
12723
  const collapsed = text.replace(/\s+/g, " ").trim();
12449
12724
  return collapsed.length > max ? `${collapsed.slice(0, max - 1)}\u2026` : collapsed;
12450
12725
  }
12451
- function parseTimestamp(raw) {
12726
+ function parseTimestamp$1(raw) {
12452
12727
  if (typeof raw === "number" && Number.isFinite(raw)) return raw;
12453
12728
  if (typeof raw === "string") {
12454
12729
  const t = Date.parse(raw);
@@ -12475,7 +12750,7 @@ function parseClaudeHistoryHead(head, promptChars) {
12475
12750
  }
12476
12751
  if (!parsed || typeof parsed !== "object") continue;
12477
12752
  if (parsed.type === "summary" && typeof parsed.summary === "string" && !summary) {
12478
- const s = oneLine(parsed.summary, promptChars);
12753
+ const s = oneLine$1(parsed.summary, promptChars);
12479
12754
  if (s) summary = s;
12480
12755
  continue;
12481
12756
  }
@@ -12483,7 +12758,7 @@ function parseClaudeHistoryHead(head, promptChars) {
12483
12758
  if (typeof parsed.entrypoint === "string" && parsed.entrypoint && !entrypoint) entrypoint = parsed.entrypoint;
12484
12759
  if (typeof parsed.gitBranch === "string" && parsed.gitBranch && !gitBranch) gitBranch = parsed.gitBranch;
12485
12760
  if (typeof parsed.version === "string" && parsed.version && !version) version = parsed.version;
12486
- if (startedAt === null) startedAt = parseTimestamp(parsed.timestamp);
12761
+ if (startedAt === null) startedAt = parseTimestamp$1(parsed.timestamp);
12487
12762
  if (!firstPrompt && parsed.type === "user" && !parsed.isSidechain) {
12488
12763
  const content = parsed.message?.content;
12489
12764
  let text = null;
@@ -12495,7 +12770,7 @@ function parseClaudeHistoryHead(head, promptChars) {
12495
12770
  }
12496
12771
  if (text) {
12497
12772
  if (isVeryHappyOneShotPrompt(text)) return null;
12498
- const cleaned = oneLine(stripHarnessTags(text), promptChars);
12773
+ const cleaned = oneLine$1(stripHarnessTags(text), promptChars);
12499
12774
  if (cleaned) firstPrompt = cleaned;
12500
12775
  }
12501
12776
  }
@@ -12521,7 +12796,7 @@ async function listClaudeSessionHistory(options) {
12521
12796
  const headBytes = Math.max(4096, options.headBytes ?? 64 * 1024);
12522
12797
  const promptChars = Math.max(20, options.promptChars ?? 200);
12523
12798
  const exclude = new Set(Array.from(options.exclude ?? [], (id) => id.toLowerCase()));
12524
- const candidates = await collectCandidates(options.projectDirs, exclude);
12799
+ const candidates = await collectCandidates$1(options.projectDirs, exclude);
12525
12800
  const entries = [];
12526
12801
  let scanned = 0;
12527
12802
  let truncated = false;
@@ -12533,7 +12808,7 @@ async function listClaudeSessionHistory(options) {
12533
12808
  scanned += 1;
12534
12809
  let head;
12535
12810
  try {
12536
- head = await readHead(candidate.path, headBytes);
12811
+ head = await readHead$1(candidate.path, headBytes);
12537
12812
  } catch {
12538
12813
  continue;
12539
12814
  }
@@ -12980,6 +13255,9 @@ class CodexAppServerClient {
12980
13255
  // Tracks in-flight interruptTurn() RPCs so sendTurnAndWait can wait for them
12981
13256
  // before starting a new turn (prevents stale turn/interrupt from aborting the next turn).
12982
13257
  pendingInterrupt = null;
13258
+ // In-flight idle release (B-461); ensureConnected() waits for it so a message
13259
+ // arriving mid-release cannot spawn a second app-server against a dying one.
13260
+ idleRelease = null;
12983
13261
  notificationProtocol = "unknown";
12984
13262
  completedTurnIds = /* @__PURE__ */ new Set();
12985
13263
  rawFileChangesByItemId = /* @__PURE__ */ new Map();
@@ -13313,6 +13591,53 @@ class CodexAppServerClient {
13313
13591
  async disconnect() {
13314
13592
  await this.disconnectInternal();
13315
13593
  }
13594
+ /** True while an app-server child process is owned by this client. */
13595
+ get hasProcess() {
13596
+ return this.process !== null;
13597
+ }
13598
+ /**
13599
+ * Drop the app-server process while the session is idle, keeping the thread
13600
+ * id and defaults so the next turn can `thread/resume` it (B-461). Refuses —
13601
+ * returning false — whenever anything is still in flight: an active turn, a
13602
+ * pending interrupt, or any outstanding RPC. See codexIdleRelease.ts.
13603
+ */
13604
+ async releaseIdleProcess() {
13605
+ if (this.idleRelease) return this.idleRelease;
13606
+ if (!this.connected && !this.process) return false;
13607
+ if (this.pendingTurnCompletion || this.pendingInterrupt || this.pending.size > 0) {
13608
+ persistence.logger.debug("[CodexAppServer] Idle release skipped: work still in flight");
13609
+ return false;
13610
+ }
13611
+ const run = (async () => {
13612
+ const threadId = this._threadId;
13613
+ persistence.logger.debug(`[CodexAppServer] Releasing idle app-server; thread=${threadId ?? "none"} kept for resume`);
13614
+ await this.disconnectInternal({ preserveThreadState: !!threadId });
13615
+ return true;
13616
+ })();
13617
+ this.idleRelease = run;
13618
+ try {
13619
+ return await run;
13620
+ } finally {
13621
+ if (this.idleRelease === run) this.idleRelease = null;
13622
+ }
13623
+ }
13624
+ /**
13625
+ * Make sure an app-server is running and, if a thread is remembered, resumed
13626
+ * in it. Cheap no-op while connected. Covers both the idle release above and
13627
+ * an app-server that exited on its own between turns.
13628
+ */
13629
+ async ensureConnected() {
13630
+ if (this.idleRelease) await this.idleRelease;
13631
+ if (this.connected) return;
13632
+ const threadId = this._threadId;
13633
+ if (this.process) {
13634
+ await this.disconnectInternal({ preserveThreadState: !!threadId });
13635
+ }
13636
+ await this.connect();
13637
+ if (threadId) {
13638
+ await this.resumeThread({ threadId });
13639
+ }
13640
+ }
13316
13641
  buildThreadConfig(mcpServers) {
13317
13642
  return mcpServers ? { mcp_servers: mcpServers } : null;
13318
13643
  }
@@ -13327,6 +13652,7 @@ class CodexAppServerClient {
13327
13652
  }
13328
13653
  // ─── Thread management ──────────────────────────────────────
13329
13654
  async startThread(opts) {
13655
+ await this.ensureConnected();
13330
13656
  const params = {
13331
13657
  model: opts.model ?? null,
13332
13658
  modelProvider: null,
@@ -13354,6 +13680,7 @@ class CodexAppServerClient {
13354
13680
  if (!threadId) {
13355
13681
  throw new Error("No thread available to resume.");
13356
13682
  }
13683
+ await this.ensureConnected();
13357
13684
  const defaults = this.threadDefaults ?? {};
13358
13685
  const params = {
13359
13686
  threadId,
@@ -13381,6 +13708,7 @@ class CodexAppServerClient {
13381
13708
  return { threadId: result.thread.id, model: result.model };
13382
13709
  }
13383
13710
  async forkThread(opts) {
13711
+ await this.ensureConnected();
13384
13712
  const defaults = this.threadDefaults ?? {};
13385
13713
  const params = {
13386
13714
  threadId: opts.threadId,
@@ -13409,6 +13737,7 @@ class CodexAppServerClient {
13409
13737
  return { threadId: result.thread.id, model: result.model, thread: result.thread };
13410
13738
  }
13411
13739
  async readThread(opts) {
13740
+ await this.ensureConnected();
13412
13741
  const params = {
13413
13742
  threadId: opts.threadId,
13414
13743
  includeTurns: opts.includeTurns ?? true
@@ -13416,6 +13745,7 @@ class CodexAppServerClient {
13416
13745
  return await this.request("thread/read", params);
13417
13746
  }
13418
13747
  async rollbackThread(opts) {
13748
+ await this.ensureConnected();
13419
13749
  const params = {
13420
13750
  threadId: opts.threadId,
13421
13751
  numTurns: opts.numTurns
@@ -13423,6 +13753,7 @@ class CodexAppServerClient {
13423
13753
  return await this.request("thread/rollback", params);
13424
13754
  }
13425
13755
  async injectItems(opts) {
13756
+ await this.ensureConnected();
13426
13757
  const params = {
13427
13758
  threadId: opts.threadId,
13428
13759
  items: opts.items
@@ -13527,6 +13858,7 @@ class CodexAppServerClient {
13527
13858
  if (!this._threadId) {
13528
13859
  throw new Error("No active thread. Call startThread first.");
13529
13860
  }
13861
+ await this.ensureConnected();
13530
13862
  const input = [
13531
13863
  { type: "text", text: prompt },
13532
13864
  ...(opts?.images ?? []).map((path) => ({ type: "localImage", path }))
@@ -13572,6 +13904,7 @@ class CodexAppServerClient {
13572
13904
  await this.pendingInterrupt;
13573
13905
  await new Promise((resolve) => setTimeout(resolve, 0));
13574
13906
  }
13907
+ await this.ensureConnected();
13575
13908
  const timeoutMs = opts?.turnTimeoutMs ?? CodexAppServerClient.TURN_TIMEOUT_MS;
13576
13909
  let timer = null;
13577
13910
  const completion = new Promise((resolve) => {
@@ -13914,6 +14247,235 @@ class CodexAppServerClient {
13914
14247
  }
13915
14248
  }
13916
14249
 
14250
+ const UUID_RE$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14251
+ const ROLLOUT_RE = /^rollout-.*-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
14252
+ const VERY_HAPPY_CODEX_ORIGINATOR = "happy-codex";
14253
+ function getCodexHomeDir() {
14254
+ return process.env.CODEX_HOME || path$1.join(os$1.homedir(), ".codex");
14255
+ }
14256
+ function getCodexSessionsRoot(codexHome = getCodexHomeDir()) {
14257
+ return path$1.join(codexHome, "sessions");
14258
+ }
14259
+ function getCodexSessionIndexPath(codexHome = getCodexHomeDir()) {
14260
+ return path$1.join(codexHome, "session_index.jsonl");
14261
+ }
14262
+ async function collectCandidates(root, exclude) {
14263
+ const out = [];
14264
+ async function walk(dir, depth) {
14265
+ let names;
14266
+ try {
14267
+ names = await promises.readdir(dir);
14268
+ } catch {
14269
+ return;
14270
+ }
14271
+ for (const name of names) {
14272
+ const full = path$1.join(dir, name);
14273
+ const match = ROLLOUT_RE.exec(name);
14274
+ if (match) {
14275
+ const id = match[1].toLowerCase();
14276
+ if (exclude.has(id)) continue;
14277
+ try {
14278
+ const s = await promises.stat(full);
14279
+ if (!s.isFile() || s.size === 0) continue;
14280
+ out.push({ path: full, id, updatedAt: s.mtimeMs, sizeBytes: s.size });
14281
+ } catch {
14282
+ }
14283
+ continue;
14284
+ }
14285
+ if (name.endsWith(".jsonl") || depth >= 4) continue;
14286
+ try {
14287
+ if ((await promises.stat(full)).isDirectory()) await walk(full, depth + 1);
14288
+ } catch {
14289
+ }
14290
+ }
14291
+ }
14292
+ await walk(root, 0);
14293
+ out.sort((a, b) => b.updatedAt - a.updatedAt);
14294
+ const seen = /* @__PURE__ */ new Set();
14295
+ return out.filter((c) => {
14296
+ if (seen.has(c.id)) return false;
14297
+ seen.add(c.id);
14298
+ return true;
14299
+ });
14300
+ }
14301
+ async function readHead(path, bytes) {
14302
+ const fh = await promises.open(path, "r");
14303
+ try {
14304
+ const buffer = Buffer.alloc(bytes);
14305
+ const { bytesRead } = await fh.read(buffer, 0, bytes, 0);
14306
+ return buffer.subarray(0, bytesRead).toString("utf-8");
14307
+ } finally {
14308
+ await fh.close();
14309
+ }
14310
+ }
14311
+ function oneLine(text, max) {
14312
+ const collapsed = text.replace(/\s+/g, " ").trim();
14313
+ return collapsed.length > max ? `${collapsed.slice(0, max - 1)}\u2026` : collapsed;
14314
+ }
14315
+ function parseTimestamp(raw) {
14316
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
14317
+ if (typeof raw === "string") {
14318
+ const t = Date.parse(raw);
14319
+ if (Number.isFinite(t)) return t;
14320
+ }
14321
+ return null;
14322
+ }
14323
+ function isHarnessUserText(text) {
14324
+ const t = text.trimStart();
14325
+ return t.startsWith("# AGENTS.md instructions") || t.startsWith("<environment_context>") || t.startsWith("<user_instructions>") || t.startsWith("<INSTRUCTIONS>");
14326
+ }
14327
+ function stripHarnessBlocks(text) {
14328
+ return text.replace(/<(environment_context|user_instructions|INSTRUCTIONS|turn_aborted|system-reminder)>[\s\S]*?<\/\1>/g, " ").replace(/^# AGENTS\.md instructions[^\n]*\n/m, " ");
14329
+ }
14330
+ function textFromUserContent(content) {
14331
+ if (typeof content === "string") return content;
14332
+ if (!Array.isArray(content)) return null;
14333
+ const parts = [];
14334
+ for (const part of content) {
14335
+ if (!part || typeof part !== "object") continue;
14336
+ const type = part.type;
14337
+ const text = part.text;
14338
+ if ((type === "input_text" || type === "text") && typeof text === "string") parts.push(text);
14339
+ }
14340
+ return parts.length > 0 ? parts.join("\n") : null;
14341
+ }
14342
+ function parseCodexHistoryHead(head, promptChars) {
14343
+ let id;
14344
+ let cwd;
14345
+ let firstPrompt;
14346
+ let fallbackPrompt;
14347
+ let startedAt = null;
14348
+ let entrypoint;
14349
+ let originator;
14350
+ let gitBranch;
14351
+ let version;
14352
+ let sawMeta = false;
14353
+ for (const line of head.split("\n")) {
14354
+ if (line.length === 0) continue;
14355
+ let parsed;
14356
+ try {
14357
+ parsed = JSON.parse(line);
14358
+ } catch {
14359
+ continue;
14360
+ }
14361
+ if (!parsed || typeof parsed !== "object") continue;
14362
+ const payload = parsed.payload;
14363
+ if (parsed.type === "session_meta" && payload && typeof payload === "object") {
14364
+ sawMeta = true;
14365
+ if (typeof payload.id === "string" && UUID_RE$1.test(payload.id)) id = payload.id.toLowerCase();
14366
+ if (typeof payload.cwd === "string" && payload.cwd) cwd = payload.cwd;
14367
+ if (typeof payload.source === "string" && payload.source) entrypoint = payload.source;
14368
+ if (typeof payload.originator === "string" && payload.originator) originator = payload.originator;
14369
+ if (typeof payload.cli_version === "string" && payload.cli_version) version = payload.cli_version;
14370
+ const branch = payload.git && typeof payload.git === "object" ? payload.git.branch : void 0;
14371
+ if (typeof branch === "string" && branch) gitBranch = branch;
14372
+ startedAt = parseTimestamp(payload.timestamp) ?? parseTimestamp(parsed.timestamp);
14373
+ continue;
14374
+ }
14375
+ if (!sawMeta) return null;
14376
+ if (firstPrompt) break;
14377
+ if (!payload || typeof payload !== "object") continue;
14378
+ if (parsed.type === "event_msg") {
14379
+ if (payload.type === "user_message" && typeof payload.message === "string") {
14380
+ const cleaned = oneLine(stripHarnessBlocks(payload.message), promptChars);
14381
+ if (cleaned) firstPrompt = cleaned;
14382
+ } else if (payload.type === "item_completed" && payload.item && payload.item.type === "UserMessage") {
14383
+ const text = textFromUserContent(payload.item.content);
14384
+ const cleaned = text ? oneLine(stripHarnessBlocks(text), promptChars) : "";
14385
+ if (cleaned) firstPrompt = cleaned;
14386
+ }
14387
+ continue;
14388
+ }
14389
+ if (parsed.type === "response_item" && payload.type === "message" && payload.role === "user" && !fallbackPrompt) {
14390
+ const text = textFromUserContent(payload.content);
14391
+ if (text && !isHarnessUserText(text)) {
14392
+ const cleaned = oneLine(stripHarnessBlocks(text), promptChars);
14393
+ if (cleaned) fallbackPrompt = cleaned;
14394
+ }
14395
+ }
14396
+ }
14397
+ if (!sawMeta || !cwd) return null;
14398
+ const prompt = firstPrompt ?? fallbackPrompt;
14399
+ if (!prompt) return null;
14400
+ return {
14401
+ ...id ? { id } : {},
14402
+ cwd,
14403
+ firstPrompt: prompt,
14404
+ startedAt: startedAt ?? 0,
14405
+ ...entrypoint ? { entrypoint } : {},
14406
+ ...originator ? { originator } : {},
14407
+ ...gitBranch ? { gitBranch } : {},
14408
+ ...version ? { version } : {}
14409
+ };
14410
+ }
14411
+ function parseCodexSessionIndex(text) {
14412
+ const names = /* @__PURE__ */ new Map();
14413
+ for (const line of text.split("\n")) {
14414
+ if (!line.trim()) continue;
14415
+ let parsed;
14416
+ try {
14417
+ parsed = JSON.parse(line);
14418
+ } catch {
14419
+ continue;
14420
+ }
14421
+ if (!parsed || typeof parsed !== "object") continue;
14422
+ if (typeof parsed.id !== "string" || !UUID_RE$1.test(parsed.id)) continue;
14423
+ if (typeof parsed.thread_name !== "string" || !parsed.thread_name.trim()) continue;
14424
+ names.set(parsed.id.toLowerCase(), parsed.thread_name.trim());
14425
+ }
14426
+ return names;
14427
+ }
14428
+ async function readSessionIndex(path) {
14429
+ if (!path) return /* @__PURE__ */ new Map();
14430
+ try {
14431
+ return parseCodexSessionIndex(await promises.readFile(path, "utf-8"));
14432
+ } catch {
14433
+ return /* @__PURE__ */ new Map();
14434
+ }
14435
+ }
14436
+ async function listCodexSessionHistory(options) {
14437
+ const limit = Math.max(1, Math.min(options.limit ?? 60, 500));
14438
+ const headBytes = Math.max(4096, options.headBytes ?? 256 * 1024);
14439
+ const promptChars = Math.max(20, options.promptChars ?? 200);
14440
+ const exclude = new Set(Array.from(options.exclude ?? [], (id) => id.toLowerCase()));
14441
+ const [candidates, names] = await Promise.all([
14442
+ collectCandidates(options.sessionsRoot, exclude),
14443
+ readSessionIndex(options.sessionIndexPath)
14444
+ ]);
14445
+ const entries = [];
14446
+ let scanned = 0;
14447
+ let truncated = false;
14448
+ for (const candidate of candidates) {
14449
+ if (entries.length >= limit) {
14450
+ truncated = true;
14451
+ break;
14452
+ }
14453
+ scanned += 1;
14454
+ let head;
14455
+ try {
14456
+ head = await readHead(candidate.path, headBytes);
14457
+ } catch {
14458
+ continue;
14459
+ }
14460
+ const parsed = parseCodexHistoryHead(head, promptChars);
14461
+ if (!parsed) continue;
14462
+ if (parsed.originator === VERY_HAPPY_CODEX_ORIGINATOR) continue;
14463
+ if (options.directory && parsed.cwd !== options.directory) continue;
14464
+ const codexThreadId = parsed.id ?? candidate.id;
14465
+ if (exclude.has(codexThreadId)) continue;
14466
+ const { id: _id, ...rest } = parsed;
14467
+ const summary = names.get(codexThreadId);
14468
+ entries.push({
14469
+ codexThreadId,
14470
+ updatedAt: Math.round(candidate.updatedAt),
14471
+ sizeBytes: candidate.sizeBytes,
14472
+ ...rest,
14473
+ ...summary ? { summary: oneLine(summary, promptChars) } : {}
14474
+ });
14475
+ }
14476
+ return { entries, truncated, scanned };
14477
+ }
14478
+
13917
14479
  function selectLowestLatencyRelay(candidates, probes) {
13918
14480
  const rttById = new Map(probes.map((probe) => [probe.relayId, probe.rttMs]));
13919
14481
  let selected = null;
@@ -14631,6 +15193,45 @@ class ApiMachineClient {
14631
15193
  });
14632
15194
  return { type: "success", ...result };
14633
15195
  });
15196
+ this.rpcHandlerManager.registerHandler("codex-list-history", async (params) => {
15197
+ const { directory, limit, exclude } = params || {};
15198
+ if (directory !== void 0 && (typeof directory !== "string" || directory.length === 0)) {
15199
+ throw new Error("directory must be a non-empty string when provided");
15200
+ }
15201
+ const excludeIds = [
15202
+ ...Array.isArray(exclude) ? exclude.filter((id) => typeof id === "string" && UUID_RE.test(id)) : [],
15203
+ ...persistence.readTrackedCodexThreadIds()
15204
+ ];
15205
+ const result = await listCodexSessionHistory({
15206
+ sessionsRoot: getCodexSessionsRoot(),
15207
+ sessionIndexPath: getCodexSessionIndexPath(),
15208
+ ...typeof directory === "string" ? { directory } : {},
15209
+ limit: typeof limit === "number" && Number.isFinite(limit) ? Math.max(1, Math.min(Math.floor(limit), 200)) : 60,
15210
+ exclude: excludeIds
15211
+ });
15212
+ return { type: "success", ...result };
15213
+ });
15214
+ this.rpcHandlerManager.registerHandler("codex-import-session", async (params) => {
15215
+ const { directory, codexThreadId, approvedNewDirectoryCreation, permissionMode, title } = params || {};
15216
+ if (typeof directory !== "string" || directory.length === 0) {
15217
+ throw new Error("directory is required");
15218
+ }
15219
+ if (typeof codexThreadId !== "string" || !UUID_RE.test(codexThreadId)) {
15220
+ throw new Error("codexThreadId must be a valid UUID");
15221
+ }
15222
+ const result = await spawnSession({
15223
+ directory,
15224
+ machineId: this.machine.id,
15225
+ approvedNewDirectoryCreation: approvedNewDirectoryCreation === true,
15226
+ agent: "codex",
15227
+ importCodexThreadId: codexThreadId.toLowerCase(),
15228
+ permissionMode: typeof permissionMode === "string" ? permissionMode : void 0,
15229
+ importTitle: typeof title === "string" ? title : void 0
15230
+ });
15231
+ if (result.type !== "success") return result;
15232
+ persistence.logger.debug(`[API MACHINE] Importing Codex thread ${codexThreadId} as session ${result.sessionId}`);
15233
+ return { type: "success", sessionId: result.sessionId };
15234
+ });
14634
15235
  this.rpcHandlerManager.registerHandler("codex-fork-thread", async (params) => {
14635
15236
  const directory = requireNonEmptyString(params?.directory, "directory");
14636
15237
  const codexThreadId = requireNonEmptyString(params?.codexThreadId, "codexThreadId");
@@ -14979,6 +15580,8 @@ class ApiMachineClient {
14979
15580
  tmuxSessions: { rpcAvailable: true, detectedAt: now, killAttached: true },
14980
15581
  // B-290 capability flag: `claude-list-history` (same restamp discipline).
14981
15582
  claudeHistory: { rpcAvailable: true, detectedAt: now },
15583
+ // B-464 capability flag: `codex-list-history` / `codex-import-session`.
15584
+ codexHistory: { rpcAvailable: true, detectedAt: now },
14982
15585
  // B-084: closed records survive daemon restarts (persisted
14983
15586
  // in closed-terminals.json), so the connect snapshot ships
14984
15587
  // them too — not just the incremental pushes.
@@ -16351,7 +16954,10 @@ exports.MIRROR_BACKFILL_LINES_DEFAULT = MIRROR_BACKFILL_LINES_DEFAULT;
16351
16954
  exports.RawJSONLinesSchema = RawJSONLinesSchema;
16352
16955
  exports.STREAM_DELTA_MAX_CHARS = STREAM_DELTA_MAX_CHARS;
16353
16956
  exports.TITLE_PROMPT_PREFIX = TITLE_PROMPT_PREFIX;
16957
+ exports.TurnActivityTracker = TurnActivityTracker;
16354
16958
  exports.api = api;
16959
+ exports.checkIfDaemonRunningAndCleanupStaleState = checkIfDaemonRunningAndCleanupStaleState;
16960
+ exports.cleanupDaemonState = cleanupDaemonState;
16355
16961
  exports.commandOnPath = commandOnPath;
16356
16962
  exports.compareExactVersions = compareExactVersions;
16357
16963
  exports.connectionState = connectionState;
@@ -16371,21 +16977,31 @@ exports.getLocalHappyAgentCredentialPath = getLocalHappyAgentCredentialPath;
16371
16977
  exports.getProjectPath = getProjectPath;
16372
16978
  exports.initializeSandbox = initializeSandbox;
16373
16979
  exports.isClaudeSessionId = isClaudeSessionId;
16980
+ exports.isDaemonRunningCurrentlyInstalledHappyVersion = isDaemonRunningCurrentlyInstalledHappyVersion;
16374
16981
  exports.isValidSessionId = isValidSessionId;
16375
16982
  exports.isValidTerminalId = isValidTerminalId;
16983
+ exports.listDaemonSessions = listDaemonSessions;
16376
16984
  exports.listVhTerminals = listVhTerminals;
16377
16985
  exports.mirrorLineKey = mirrorLineKey;
16378
16986
  exports.mirrorLocalId = mirrorLocalId;
16987
+ exports.notifyDaemonClaudeAuthFailed = notifyDaemonClaudeAuthFailed;
16988
+ exports.notifyDaemonSessionEvent = notifyDaemonSessionEvent;
16989
+ exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
16379
16990
  exports.parseExactVersion = parseExactVersion;
16380
16991
  exports.parseTerminalHookPayload = parseTerminalHookPayload;
16381
16992
  exports.projectPath = projectPath;
16993
+ exports.pushClipboardViaDaemon = pushClipboardViaDaemon;
16382
16994
  exports.readLocalHappyAgentCredentials = readLocalHappyAgentCredentials;
16383
16995
  exports.readVhTerminal = readVhTerminal;
16384
16996
  exports.resolveCliUpdateCheckInterval = resolveCliUpdateCheckInterval;
16385
16997
  exports.scrubTmuxClientEnv = scrubTmuxClientEnv;
16386
16998
  exports.sendToVhTerminal = sendToVhTerminal;
16999
+ exports.setTerminalTitleViaDaemon = setTerminalTitleViaDaemon;
16387
17000
  exports.shellescape = shellescape;
17001
+ exports.spawnDaemonSession = spawnDaemonSession;
16388
17002
  exports.startOfflineReconnection = startOfflineReconnection;
17003
+ exports.stopDaemon = stopDaemon;
17004
+ exports.stopDaemonSession = stopDaemonSession;
16389
17005
  exports.streamKeyOf = streamKeyOf;
16390
17006
  exports.summarizeSpawnSessionForLog = summarizeSpawnSessionForLog;
16391
17007
  exports.tmuxArgs = tmuxArgs;