very-happy-cli 0.2.138 → 0.2.140

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-BUoDXZnv.cjs → AcpBackend-C0Lq4uoA.cjs} +2 -2
  2. package/dist/{AcpBackend-bqVGMkPh.mjs → AcpBackend-DsSYq8T-.mjs} +2 -2
  3. package/dist/{AcpSessionManager-BBAQ8EQf.mjs → AcpSessionManager-BAErE9s0.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DmmA52Ep.cjs → AcpSessionManager-DIqByTH7.cjs} +1 -1
  5. package/dist/{agentGuidance-4HV-BOge.cjs → agentGuidance-Dl3apItH.cjs} +1 -1
  6. package/dist/{agentGuidance-WHZsBKpm.mjs → agentGuidance-xk3v4_7E.mjs} +1 -1
  7. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  8. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  9. package/dist/{config-BRXqgJJj.mjs → config-BVoHul9V.mjs} +4 -4
  10. package/dist/{config-DpuZN2Rs.cjs → config-D0-jnCOC.cjs} +4 -4
  11. package/dist/{index-DQe7ljLR.mjs → index-BNydKYvS.mjs} +7 -7
  12. package/dist/{index-D8Q3OhdR.mjs → index-BPnAbcz_.mjs} +4 -4
  13. package/dist/{index-CHP265gs.cjs → index-BuQn9kUK.cjs} +4 -4
  14. package/dist/{index-Dbl5U_N0.mjs → index-D0IP7gXG.mjs} +116 -232
  15. package/dist/{index-3N-qu-RG.cjs → index-DJvzGXVN.cjs} +7 -7
  16. package/dist/{index-CRRK5P1r.cjs → index-__h-pCTK.cjs} +147 -268
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.mjs +4 -4
  19. package/dist/{installTerminalHooks-DfUGgeyb.mjs → installTerminalHooks-D2ice1a-.mjs} +4 -4
  20. package/dist/{installTerminalHooks-CWGkoWDD.cjs → installTerminalHooks-D3jfvbyY.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-CfzVummd.cjs → limits-Dbip6eVO.cjs} +26 -7
  26. package/dist/{limits-DEsg_gHi.mjs → limits-nhD0Wran.mjs} +26 -8
  27. package/dist/{mcp-CN2UVfut.cjs → mcp-DeapmTWN.cjs} +21 -21
  28. package/dist/{mcp-UnjB3I8_.mjs → mcp-V26C05fN.mjs} +19 -19
  29. package/dist/{runGemini-BUVjTJKn.cjs → runGemini-D34eCFpz.cjs} +7 -7
  30. package/dist/{runGemini-BZ0xXRj-.mjs → runGemini-sa5kAgPe.mjs} +6 -6
  31. package/dist/{runOpenClaw-Bj2EgRHB.cjs → runOpenClaw-B8F8D--R.cjs} +6 -6
  32. package/dist/{runOpenClaw-BcUmnBpW.mjs → runOpenClaw-Bdsq8KBa.mjs} +5 -5
  33. package/dist/{send-BkypdBpK.cjs → send-DP_S3wn0.cjs} +4 -4
  34. package/dist/{send-snK_NGAA.mjs → send-KICC_ACz.mjs} +4 -4
  35. package/dist/{sessions-DxlC4C4g.mjs → sessions-C6EvnC4B.mjs} +4 -4
  36. package/dist/{sessions-9A-uoJL_.cjs → sessions-CGBBgVZn.cjs} +4 -4
  37. package/dist/{spawn-BORLpj60.cjs → spawn-BhUK0Vt2.cjs} +8 -8
  38. package/dist/{spawn-DFfHCafP.mjs → spawn-C1OqXJNb.mjs} +6 -6
  39. package/dist/{teams-9x2FO9RW.cjs → teams-C29nEHFD.cjs} +4 -4
  40. package/dist/{teams-Cfr-2Yd-.mjs → teams-Dm5HGpNz.mjs} +4 -4
  41. package/dist/{todo-CU5cjQZU.mjs → todo-C-vovA0N.mjs} +3 -3
  42. package/dist/{todo-DydKcknX.cjs → todo-CeJX0z_2.cjs} +3 -3
  43. package/dist/{types-CRxf_3n8.cjs → types-DCVS050m.cjs} +571 -13
  44. package/dist/{types-D5EsIAOe.mjs → types-thyvQB2a.mjs} +558 -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-CfzVummd.cjs');
4
+ var persistence = require('./limits-Dbip6eVO.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-CRxf_3n8.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-DCVS050m.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
  }
@@ -13972,6 +14247,235 @@ class CodexAppServerClient {
13972
14247
  }
13973
14248
  }
13974
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
+
13975
14479
  function selectLowestLatencyRelay(candidates, probes) {
13976
14480
  const rttById = new Map(probes.map((probe) => [probe.relayId, probe.rttMs]));
13977
14481
  let selected = null;
@@ -14689,6 +15193,45 @@ class ApiMachineClient {
14689
15193
  });
14690
15194
  return { type: "success", ...result };
14691
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
+ });
14692
15235
  this.rpcHandlerManager.registerHandler("codex-fork-thread", async (params) => {
14693
15236
  const directory = requireNonEmptyString(params?.directory, "directory");
14694
15237
  const codexThreadId = requireNonEmptyString(params?.codexThreadId, "codexThreadId");
@@ -15037,6 +15580,8 @@ class ApiMachineClient {
15037
15580
  tmuxSessions: { rpcAvailable: true, detectedAt: now, killAttached: true },
15038
15581
  // B-290 capability flag: `claude-list-history` (same restamp discipline).
15039
15582
  claudeHistory: { rpcAvailable: true, detectedAt: now },
15583
+ // B-464 capability flag: `codex-list-history` / `codex-import-session`.
15584
+ codexHistory: { rpcAvailable: true, detectedAt: now },
15040
15585
  // B-084: closed records survive daemon restarts (persisted
15041
15586
  // in closed-terminals.json), so the connect snapshot ships
15042
15587
  // them too — not just the incremental pushes.
@@ -16409,7 +16954,10 @@ exports.MIRROR_BACKFILL_LINES_DEFAULT = MIRROR_BACKFILL_LINES_DEFAULT;
16409
16954
  exports.RawJSONLinesSchema = RawJSONLinesSchema;
16410
16955
  exports.STREAM_DELTA_MAX_CHARS = STREAM_DELTA_MAX_CHARS;
16411
16956
  exports.TITLE_PROMPT_PREFIX = TITLE_PROMPT_PREFIX;
16957
+ exports.TurnActivityTracker = TurnActivityTracker;
16412
16958
  exports.api = api;
16959
+ exports.checkIfDaemonRunningAndCleanupStaleState = checkIfDaemonRunningAndCleanupStaleState;
16960
+ exports.cleanupDaemonState = cleanupDaemonState;
16413
16961
  exports.commandOnPath = commandOnPath;
16414
16962
  exports.compareExactVersions = compareExactVersions;
16415
16963
  exports.connectionState = connectionState;
@@ -16429,21 +16977,31 @@ exports.getLocalHappyAgentCredentialPath = getLocalHappyAgentCredentialPath;
16429
16977
  exports.getProjectPath = getProjectPath;
16430
16978
  exports.initializeSandbox = initializeSandbox;
16431
16979
  exports.isClaudeSessionId = isClaudeSessionId;
16980
+ exports.isDaemonRunningCurrentlyInstalledHappyVersion = isDaemonRunningCurrentlyInstalledHappyVersion;
16432
16981
  exports.isValidSessionId = isValidSessionId;
16433
16982
  exports.isValidTerminalId = isValidTerminalId;
16983
+ exports.listDaemonSessions = listDaemonSessions;
16434
16984
  exports.listVhTerminals = listVhTerminals;
16435
16985
  exports.mirrorLineKey = mirrorLineKey;
16436
16986
  exports.mirrorLocalId = mirrorLocalId;
16987
+ exports.notifyDaemonClaudeAuthFailed = notifyDaemonClaudeAuthFailed;
16988
+ exports.notifyDaemonSessionEvent = notifyDaemonSessionEvent;
16989
+ exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
16437
16990
  exports.parseExactVersion = parseExactVersion;
16438
16991
  exports.parseTerminalHookPayload = parseTerminalHookPayload;
16439
16992
  exports.projectPath = projectPath;
16993
+ exports.pushClipboardViaDaemon = pushClipboardViaDaemon;
16440
16994
  exports.readLocalHappyAgentCredentials = readLocalHappyAgentCredentials;
16441
16995
  exports.readVhTerminal = readVhTerminal;
16442
16996
  exports.resolveCliUpdateCheckInterval = resolveCliUpdateCheckInterval;
16443
16997
  exports.scrubTmuxClientEnv = scrubTmuxClientEnv;
16444
16998
  exports.sendToVhTerminal = sendToVhTerminal;
16999
+ exports.setTerminalTitleViaDaemon = setTerminalTitleViaDaemon;
16445
17000
  exports.shellescape = shellescape;
17001
+ exports.spawnDaemonSession = spawnDaemonSession;
16446
17002
  exports.startOfflineReconnection = startOfflineReconnection;
17003
+ exports.stopDaemon = stopDaemon;
17004
+ exports.stopDaemonSession = stopDaemonSession;
16447
17005
  exports.streamKeyOf = streamKeyOf;
16448
17006
  exports.summarizeSpawnSessionForLog = summarizeSpawnSessionForLog;
16449
17007
  exports.tmuxArgs = tmuxArgs;