very-happy-cli 0.2.138 → 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-bqVGMkPh.mjs → AcpBackend-BwTEBAyP.mjs} +2 -2
  2. package/dist/{AcpBackend-BUoDXZnv.cjs → AcpBackend-ioCPQLt4.cjs} +2 -2
  3. package/dist/{AcpSessionManager-BBAQ8EQf.mjs → AcpSessionManager-Bh3KETfz.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DmmA52Ep.cjs → AcpSessionManager-o9T6W69-.cjs} +1 -1
  5. package/dist/{agentGuidance-WHZsBKpm.mjs → agentGuidance-DRnXdFAY.mjs} +1 -1
  6. package/dist/{agentGuidance-4HV-BOge.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-DpuZN2Rs.cjs → config-nyMHCx-i.cjs} +4 -4
  10. package/dist/{config-BRXqgJJj.mjs → config-o9NFsj8x.mjs} +4 -4
  11. package/dist/{index-Dbl5U_N0.mjs → index-BZr5l8TH.mjs} +93 -230
  12. package/dist/{index-DQe7ljLR.mjs → index-BkmfJyCf.mjs} +7 -7
  13. package/dist/{index-CRRK5P1r.cjs → index-C3NtU4Ch.cjs} +124 -266
  14. package/dist/{index-D8Q3OhdR.mjs → index-CTemcNko.mjs} +4 -4
  15. package/dist/{index-CHP265gs.cjs → index-gkFhwPXq.cjs} +4 -4
  16. package/dist/{index-3N-qu-RG.cjs → index-klhxcpC8.cjs} +7 -7
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.mjs +4 -4
  19. package/dist/{installTerminalHooks-DfUGgeyb.mjs → installTerminalHooks-8CHTgUC-.mjs} +4 -4
  20. package/dist/{installTerminalHooks-CWGkoWDD.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-CfzVummd.cjs → limits-C_RGzva6.cjs} +26 -7
  26. package/dist/{limits-DEsg_gHi.mjs → limits-DefXc1TL.mjs} +26 -8
  27. package/dist/{mcp-UnjB3I8_.mjs → mcp-8DcunR0J.mjs} +19 -19
  28. package/dist/{mcp-CN2UVfut.cjs → mcp-czNIfD59.cjs} +21 -21
  29. package/dist/{runGemini-BZ0xXRj-.mjs → runGemini-CDUObQTD.mjs} +6 -6
  30. package/dist/{runGemini-BUVjTJKn.cjs → runGemini-Dy3y-lD9.cjs} +7 -7
  31. package/dist/{runOpenClaw-BcUmnBpW.mjs → runOpenClaw-B6KMCsVB.mjs} +5 -5
  32. package/dist/{runOpenClaw-Bj2EgRHB.cjs → runOpenClaw-CJusckVM.cjs} +6 -6
  33. package/dist/{send-BkypdBpK.cjs → send-B4Qo-87h.cjs} +4 -4
  34. package/dist/{send-snK_NGAA.mjs → send-BT32Jiel.mjs} +4 -4
  35. package/dist/{sessions-9A-uoJL_.cjs → sessions-CN2y0xBl.cjs} +4 -4
  36. package/dist/{sessions-DxlC4C4g.mjs → sessions-Dh0Lg-5z.mjs} +4 -4
  37. package/dist/{spawn-DFfHCafP.mjs → spawn-DFIyobCt.mjs} +6 -6
  38. package/dist/{spawn-BORLpj60.cjs → spawn-jWj3xFhX.cjs} +8 -8
  39. package/dist/{teams-9x2FO9RW.cjs → teams-BhFspeAY.cjs} +4 -4
  40. package/dist/{teams-Cfr-2Yd-.mjs → teams-CZ9ICWeB.mjs} +4 -4
  41. package/dist/{todo-DydKcknX.cjs → todo-B0kvqUTQ.cjs} +3 -3
  42. package/dist/{todo-CU5cjQZU.mjs → todo-jL3ttna3.mjs} +3 -3
  43. package/dist/{types-D5EsIAOe.mjs → types-CrKeJuyF.mjs} +558 -13
  44. package/dist/{types-CRxf_3n8.cjs → types-CxGm_YJ-.cjs} +571 -13
  45. package/package.json +7 -7
@@ -1,13 +1,13 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
2
2
  import os, { homedir, userInfo, tmpdir } from 'node:os';
3
3
  import { randomUUID, randomInt, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
4
- import { j as getProjectPath, g as BUILTIN_TODO_DISCOVERY, p as projectPath, k as initializeSandbox, w as wrapCommand, d as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, h as delay, l as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, m as commandOnPath, n as deriveLocalCliUpdateSummary, o as AGENT_VERSION_SOURCES, q as isClaudeSessionId, r as getLocalHappyAgentCredentialPath, t as readLocalHappyAgentCredentials, u as parseExactVersion, v as compareExactVersions, x as extractCompleteLines, y as decideBackfill, z as parseTerminalHookPayload, C as tmuxArgs, D as scrubTmuxClientEnv, E as decideMirrorBinding, F as MIRROR_BACKFILL_LINES_DEFAULT, G as mirrorLineKey, H as mirrorLocalId, J as detectCLIAvailability, K as detectResumeSupport, L as summarizeSpawnSessionForLog, N as shellescape, A as ApiClient, O as fetchCliUpdateState, P as resolveCliUpdateCheckInterval, Q as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, i as isValidSessionId, T as listVhTerminals, U as isValidTerminalId, V as readVhTerminal, W as sendToVhTerminal, X as ContextUsageSchema, Y as TITLE_PROMPT_PREFIX, Z as BOARD_ANALYZER_PROMPT_PREFIX, f as connectionState, _ as startOfflineReconnection, c as createEnvelope, $ as CodexAppServerClient } from './types-D5EsIAOe.mjs';
5
- import { z as readDaemonState, l as logger, c as configuration, A as clearDaemonState, B as ensurePrivateDirectory, D as hardenPrivateFile, p as packageJson, g as readSettings, E as readCredentials, s as credentialRelayProblem, i as encodeBase64, F as readSettingsOutcome, G as updateSettings, H as encodeBase64Url, m as decodeBase64, I as writeCredentialsLegacy, J as writeCredentialsDataKey, d as readCredentialsForConfiguredRelay, j as encrypt, r as readPersistedSessions, w as writePrivateFileSync, K as decryptWithDataKey, L as decryptLegacy, M as persistSession, k as decrypt, f as ensurePrivateDirectorySync, N as acquireDaemonLock, O as deletePersistedSessions, P as writeDaemonState, Q as writeSettings, R as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, e as CLIPBOARD_MAX_BYTES, S as clearCredentials, T as clearMachineId, U as SandboxConfigSchema, V as getLatestDaemonLog } from './limits-DEsg_gHi.mjs';
4
+ import { o as notifyDaemonSessionEvent, q as getProjectPath, l as BUILTIN_TODO_DISCOVERY, p as projectPath, r as initializeSandbox, w as wrapCommand, j as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, m as delay, t as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, u as notifyDaemonClaudeAuthFailed, v as commandOnPath, c as checkIfDaemonRunningAndCleanupStaleState, x as deriveLocalCliUpdateSummary, y as AGENT_VERSION_SOURCES, z as isClaudeSessionId, C as getLocalHappyAgentCredentialPath, D as readLocalHappyAgentCredentials, E as parseExactVersion, F as compareExactVersions, G as extractCompleteLines, H as decideBackfill, J as parseTerminalHookPayload, K as tmuxArgs, L as scrubTmuxClientEnv, N as decideMirrorBinding, O as MIRROR_BACKFILL_LINES_DEFAULT, P as mirrorLineKey, Q as mirrorLocalId, T as listDaemonSessions, U as stopDaemonSession, V as detectCLIAvailability, W as detectResumeSupport, X as isDaemonRunningCurrentlyInstalledHappyVersion, Y as stopDaemon, Z as TurnActivityTracker, _ as summarizeSpawnSessionForLog, $ as shellescape, A as ApiClient, a0 as fetchCliUpdateState, a1 as resolveCliUpdateCheckInterval, a2 as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, a3 as cleanupDaemonState, i as isValidSessionId, s as spawnDaemonSession, a4 as listVhTerminals, a5 as isValidTerminalId, a6 as readVhTerminal, a7 as sendToVhTerminal, a8 as ContextUsageSchema, a9 as TITLE_PROMPT_PREFIX, aa as BOARD_ANALYZER_PROMPT_PREFIX, k as connectionState, ab as startOfflineReconnection, n as notifyDaemonSessionStarted, d as createEnvelope, ac as CodexAppServerClient } from './types-CrKeJuyF.mjs';
5
+ import { l as logger, D as ensurePrivateDirectory, E as hardenPrivateFile, c as configuration, p as packageJson, g as readSettings, F as readCredentials, u as credentialRelayProblem, o as readDaemonState, i as encodeBase64, G as readSettingsOutcome, H as updateSettings, I as encodeBase64Url, m as decodeBase64, J as writeCredentialsLegacy, K as writeCredentialsDataKey, d as readCredentialsForConfiguredRelay, j as encrypt, r as readPersistedSessions, w as writePrivateFileSync, L as decryptWithDataKey, M as decryptLegacy, N as persistSession, k as decrypt, f as ensurePrivateDirectorySync, O as acquireDaemonLock, P as deletePersistedSessions, Q as writeDaemonState, R as writeSettings, S as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, e as CLIPBOARD_MAX_BYTES, T as clearCredentials, U as clearMachineId, V as SandboxConfigSchema, W as getLatestDaemonLog } from './limits-DefXc1TL.mjs';
6
6
  import spawn$1, { spawn } from 'cross-spawn';
7
7
  import path, { join, resolve, basename, isAbsolute, dirname, sep, delimiter, normalize } from 'node:path';
8
8
  import { createInterface } from 'node:readline';
9
9
  import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, unlinkSync, renameSync, writeFileSync, accessSync, constants, openSync, fsyncSync, closeSync, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
10
- import { f as trimIdent, A as AGENT_GUIDANCE, t as teamScopePath, T as TeamConnectionPreflightError, e as createTeamsClient, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, g as TEAM_TOOL_NAMES, r as registerTeamsTools, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, h as REPORT_PROGRESS_TOOL_TITLE, i as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-WHZsBKpm.mjs';
10
+ import { f as trimIdent, A as AGENT_GUIDANCE, t as teamScopePath, T as TeamConnectionPreflightError, e as createTeamsClient, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, g as TEAM_TOOL_NAMES, r as registerTeamsTools, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, h as REPORT_PROGRESS_TOOL_TITLE, i as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-DRnXdFAY.mjs';
11
11
  import { readFile, writeFile, rename, unlink, mkdir, symlink, rm, readlink, lstat, stat, open as open$1, appendFile } from 'node:fs/promises';
12
12
  import fs, { watch, access } from 'fs/promises';
13
13
  import { useStdout, useInput, Box, Text, render } from 'ink';
@@ -93,212 +93,6 @@ function withoutServerSnapshot(response) {
93
93
  return { ...response, metadataVersion: 0, agentStateVersion: 0 };
94
94
  }
95
95
 
96
- function daemonControlHeaders(controlToken) {
97
- return {
98
- "Content-Type": "application/json",
99
- ...controlToken ? { Authorization: `Bearer ${controlToken}` } : {}
100
- };
101
- }
102
- async function daemonPost(path, body) {
103
- const state = await readDaemonState();
104
- if (!state?.httpPort) {
105
- const errorMessage = "No daemon running, no state file found";
106
- logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
107
- return {
108
- error: errorMessage
109
- };
110
- }
111
- try {
112
- process.kill(state.pid, 0);
113
- } catch (error) {
114
- const errorMessage = "Daemon is not running, file is stale";
115
- logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
116
- return {
117
- error: errorMessage
118
- };
119
- }
120
- try {
121
- const timeout = process.env.HAPPY_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.HAPPY_DAEMON_HTTP_TIMEOUT) : 1e4;
122
- const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
123
- method: "POST",
124
- // A state written by an old daemon has no token and must remain callable;
125
- // a new daemon always writes one and rejects requests without it.
126
- headers: daemonControlHeaders(state.controlToken),
127
- body: JSON.stringify(body || {}),
128
- // Mostly increased for stress test
129
- signal: AbortSignal.timeout(timeout)
130
- });
131
- if (!response.ok) {
132
- const failure = await response.json().catch(() => null);
133
- const reason = typeof failure?.error === "string" && failure.error ? failure.error : `HTTP ${response.status}`;
134
- const errorMessage = `Request failed: ${path}, ${reason}`;
135
- logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
136
- return {
137
- error: errorMessage
138
- };
139
- }
140
- return await response.json();
141
- } catch (error) {
142
- const errorMessage = `Request failed: ${path}, ${error instanceof Error ? error.message : "Unknown error"}`;
143
- logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
144
- return {
145
- error: errorMessage
146
- };
147
- }
148
- }
149
- const SESSION_STARTED_RETRY_TIMEOUT_MS = 3e3;
150
- const SESSION_STARTED_RETRY_INTERVAL_MS = 100;
151
- async function notifyDaemonSessionStarted(sessionId, metadata, encryption) {
152
- const payload = { sessionId, metadata, encryption };
153
- const deadline = Date.now() + SESSION_STARTED_RETRY_TIMEOUT_MS;
154
- let result;
155
- while (true) {
156
- result = await daemonPost("/session-started", payload);
157
- if (!result?.error) {
158
- return result;
159
- }
160
- if (Date.now() >= deadline) {
161
- return result;
162
- }
163
- await new Promise((resolve) => setTimeout(resolve, SESSION_STARTED_RETRY_INTERVAL_MS));
164
- }
165
- }
166
- async function listDaemonSessions() {
167
- const result = await daemonPost("/list");
168
- return result.children || [];
169
- }
170
- async function stopDaemonSession(sessionId) {
171
- const result = await daemonPost("/stop-session", { sessionId });
172
- return result.success || false;
173
- }
174
- async function spawnDaemonSession(directory, sessionId, opts) {
175
- const result = await daemonPost("/spawn-session", {
176
- directory,
177
- sessionId,
178
- spawnedBy: opts?.spawnedBy,
179
- permissionMode: opts?.permissionMode,
180
- agent: opts?.agent,
181
- environmentVariables: opts?.environmentVariables
182
- });
183
- return result;
184
- }
185
- async function notifyDaemonSessionEvent(sessionId, event, spawnedBy) {
186
- return daemonPost("/session-event", { sessionId, event, spawnedBy });
187
- }
188
- async function notifyDaemonClaudeAuthFailed(sessionId) {
189
- return daemonPost("/session-event", { sessionId, event: "auth_failed" });
190
- }
191
- async function stopDaemonHttp() {
192
- await daemonPost("/stop");
193
- }
194
- async function pushClipboardViaDaemon(text) {
195
- const result = await daemonPost("/clipboard", { text });
196
- if (result?.error && result.delivered === void 0) {
197
- return { delivered: false, error: result.error };
198
- }
199
- return result;
200
- }
201
- async function setTerminalTitleViaDaemon(terminalId, title, ifAbsent = false) {
202
- const result = await daemonPost("/terminal-title", { terminalId, title, ifAbsent });
203
- if (result?.error) {
204
- return { ok: false, error: result.error };
205
- }
206
- return { ok: result?.status === "ok", error: result?.status === "ok" ? void 0 : "unexpected daemon response" };
207
- }
208
- async function checkIfDaemonRunningAndCleanupStaleState() {
209
- const state = await readDaemonState();
210
- if (!state) {
211
- return false;
212
- }
213
- try {
214
- process.kill(state.pid, 0);
215
- } catch {
216
- logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
217
- await cleanupDaemonState();
218
- return false;
219
- }
220
- if (state.httpPort) {
221
- try {
222
- const response = await fetch(`http://127.0.0.1:${state.httpPort}/list`, {
223
- method: "POST",
224
- headers: daemonControlHeaders(state.controlToken),
225
- body: "{}",
226
- signal: AbortSignal.timeout(2e3)
227
- });
228
- if (response.ok) {
229
- return true;
230
- }
231
- } catch {
232
- logger.debug(`[DAEMON RUN] PID ${state.pid} is alive but HTTP health check failed on port ${state.httpPort}, cleaning up stale state`);
233
- await cleanupDaemonState();
234
- return false;
235
- }
236
- }
237
- return true;
238
- }
239
- async function isDaemonRunningCurrentlyInstalledHappyVersion() {
240
- logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
241
- const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
242
- if (!runningDaemon) {
243
- logger.debug("[DAEMON CONTROL] No daemon running, returning false");
244
- return false;
245
- }
246
- const state = await readDaemonState();
247
- if (!state) {
248
- logger.debug("[DAEMON CONTROL] No daemon state found, returning false");
249
- return false;
250
- }
251
- const currentCliVersion = configuration.currentCliVersion;
252
- logger.debug(`[DAEMON CONTROL] Current CLI version: ${currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
253
- return currentCliVersion === state.startedWithCliVersion;
254
- }
255
- async function cleanupDaemonState() {
256
- try {
257
- await clearDaemonState();
258
- logger.debug("[DAEMON RUN] Daemon state file removed");
259
- } catch (error) {
260
- logger.debug("[DAEMON RUN] Error cleaning up daemon metadata", error);
261
- }
262
- }
263
- async function stopDaemon() {
264
- try {
265
- const state = await readDaemonState();
266
- if (!state) {
267
- logger.debug("No daemon state found");
268
- return;
269
- }
270
- logger.debug(`Stopping daemon with PID ${state.pid}`);
271
- try {
272
- await stopDaemonHttp();
273
- await waitForProcessDeath(state.pid, 2e3);
274
- logger.debug("Daemon stopped gracefully via HTTP");
275
- return;
276
- } catch (error) {
277
- logger.debug("HTTP stop failed, will force kill", error);
278
- }
279
- try {
280
- process.kill(state.pid, "SIGKILL");
281
- logger.debug("Force killed daemon");
282
- } catch (error) {
283
- logger.debug("Daemon already dead");
284
- }
285
- } catch (error) {
286
- logger.debug("Error stopping daemon", error);
287
- }
288
- }
289
- async function waitForProcessDeath(pid, timeout) {
290
- const start = Date.now();
291
- while (Date.now() - start < timeout) {
292
- try {
293
- process.kill(pid, 0);
294
- await new Promise((resolve) => setTimeout(resolve, 100));
295
- } catch {
296
- return;
297
- }
298
- }
299
- throw new Error("Process did not die within timeout");
300
- }
301
-
302
96
  class Session {
303
97
  path;
304
98
  logPath;
@@ -6259,6 +6053,7 @@ function startDaemonControlServer({
6259
6053
  onHappySessionWebhook,
6260
6054
  onSessionStateEvent,
6261
6055
  onClaudeAuthFailed,
6056
+ onSessionTurnEvent,
6262
6057
  pushClipboard,
6263
6058
  onTerminalHook,
6264
6059
  setTerminalTitle
@@ -6315,7 +6110,7 @@ function startDaemonControlServer({
6315
6110
  schema: {
6316
6111
  body: z.object({
6317
6112
  sessionId: z.string(),
6318
- event: z.enum(["completed", "needs_input", "auth_failed"]),
6113
+ event: z.enum(["completed", "needs_input", "auth_failed", "turn_started", "turn_ended"]),
6319
6114
  spawnedBy: z.string().optional()
6320
6115
  }),
6321
6116
  response: {
@@ -6331,6 +6126,10 @@ function startDaemonControlServer({
6331
6126
  onClaudeAuthFailed?.(sessionId);
6332
6127
  return { status: "ok" };
6333
6128
  }
6129
+ if (event === "turn_started" || event === "turn_ended") {
6130
+ onSessionTurnEvent?.(sessionId, event);
6131
+ return { status: "ok" };
6132
+ }
6334
6133
  onSessionStateEvent?.(sessionId, event, spawnedBy);
6335
6134
  return { status: "ok" };
6336
6135
  });
@@ -10225,6 +10024,7 @@ async function startDaemon() {
10225
10024
  const { credentials, machineId } = await authAndSetupMachineIfNeeded();
10226
10025
  logger.debug("[DAEMON RUN] Auth and machine setup complete");
10227
10026
  const pidToTrackedSession = /* @__PURE__ */ new Map();
10027
+ const turnActivity = new TurnActivityTracker();
10228
10028
  const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
10229
10029
  let teamWorker;
10230
10030
  const persisted = readPersistedSessions();
@@ -10496,6 +10296,9 @@ async function startDaemon() {
10496
10296
  if (options.resumeCodexThreadId) {
10497
10297
  extraEnv.HAPPY_FORK_CODEX_THREAD_ID = options.resumeCodexThreadId;
10498
10298
  }
10299
+ if (options.importCodexThreadId && UUID_RE.test(options.importCodexThreadId)) {
10300
+ extraEnv.HAPPY_IMPORT_CODEX_THREAD_ID = options.importCodexThreadId;
10301
+ }
10499
10302
  if (options.variant === "assistant") {
10500
10303
  extraEnv.HAPPY_SESSION_VARIANT = "assistant";
10501
10304
  }
@@ -11060,6 +10863,7 @@ async function startDaemon() {
11060
10863
  const onChildExited = (pid) => {
11061
10864
  const session = pidToTrackedSession.get(pid);
11062
10865
  if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
10866
+ if (session?.happySessionId) turnActivity.forget(session.happySessionId);
11063
10867
  if (session?.happySessionId && session.encryption) {
11064
10868
  sessionIdToFinishedSession.set(session.happySessionId, session);
11065
10869
  logger.debug(`[DAEMON RUN] Process PID ${pid} exited, preserved session ${session.happySessionId} for resume`);
@@ -11141,6 +10945,7 @@ async function startDaemon() {
11141
10945
  onHappySessionWebhook,
11142
10946
  onSessionStateEvent,
11143
10947
  onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
10948
+ onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
11144
10949
  pushClipboard: (text) => {
11145
10950
  if (!apiMachineRef) {
11146
10951
  return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
@@ -11253,7 +11058,10 @@ async function startDaemon() {
11253
11058
  const updateController = createUpdateController({
11254
11059
  policy: () => fetchCliUpdateState(configuration.serverUrl, packageJson.version),
11255
11060
  enabled: async () => ((await readSettings()).cliAutoUpdate ?? "idle") !== "off",
11256
- idle: () => pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals(),
11061
+ // B-466: "idle" = no agent turn in flight. Idle wrappers survive a
11062
+ // handover (they talk to the server themselves) and web terminals live
11063
+ // in tmux, so neither is a reason to hold an update.
11064
+ idle: () => !turnActivity.hasActiveTurn(),
11257
11065
  install: installCliSafely,
11258
11066
  publish: (state) => {
11259
11067
  cliUpdateStateRef = state;
@@ -11308,7 +11116,7 @@ async function startDaemon() {
11308
11116
  } catch {
11309
11117
  }
11310
11118
  }
11311
- if (bundleReplaced && !teamWorker?.busy && !updateController.isRunning() && pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals()) {
11119
+ if (bundleReplaced && !teamWorker?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
11312
11120
  const preflight = await preflightNewBundle(bundlePath);
11313
11121
  if (preflight.action === "hold") {
11314
11122
  if (lastHandoverHold !== preflight.reason) {
@@ -11318,7 +11126,7 @@ async function startDaemon() {
11318
11126
  updateController.holdHandover(preflight.reason);
11319
11127
  return;
11320
11128
  }
11321
- if (teamWorker?.busy || updateController.isRunning() || pidToTrackedSession.size > 0 || apiMachine.hasLiveTerminals()) return;
11129
+ if (teamWorker?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
11322
11130
  lastHandoverHold = null;
11323
11131
  logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
11324
11132
  teamWorker?.stop();
@@ -12476,7 +12284,7 @@ function registerSideQuestionHandler(rpc, deps) {
12476
12284
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
12477
12285
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
12478
12286
  const run = deps.run ?? (async (input) => {
12479
- const { query } = await import('./index-D8Q3OhdR.mjs');
12287
+ const { query } = await import('./index-CTemcNko.mjs');
12480
12288
  return runSideQuestion(query, input);
12481
12289
  });
12482
12290
  const slots = /* @__PURE__ */ new Map();
@@ -16572,6 +16380,43 @@ async function resumeExistingThread(opts) {
16572
16380
  }
16573
16381
  }
16574
16382
 
16383
+ const IMPORT_CODEX_THREAD_ENV = "HAPPY_IMPORT_CODEX_THREAD_ID";
16384
+ async function importCodexThread(opts) {
16385
+ let forked;
16386
+ try {
16387
+ forked = await opts.client.forkThread({
16388
+ threadId: opts.threadId,
16389
+ cwd: opts.cwd,
16390
+ mcpServers: opts.mcpServers
16391
+ });
16392
+ } catch (error) {
16393
+ const reason = error instanceof Error ? error.message : String(error);
16394
+ throw new Error(`Failed to import Codex thread ${opts.threadId}: ${reason}`);
16395
+ }
16396
+ if (!forked.threadId || forked.threadId === opts.threadId) {
16397
+ throw new Error(`Failed to import Codex thread ${opts.threadId}: Codex did not create an independent copy`);
16398
+ }
16399
+ opts.session.updateMetadata((currentMetadata) => ({
16400
+ ...currentMetadata,
16401
+ codexThreadId: forked.threadId,
16402
+ importedFromCodexThreadId: opts.threadId
16403
+ }));
16404
+ let thread = forked.thread;
16405
+ if (!Array.isArray(thread?.turns)) {
16406
+ thread = (await opts.client.readThread({ threadId: forked.threadId, includeTurns: true })).thread;
16407
+ }
16408
+ const envelopes = mapCodexThreadToSessionEnvelopes(thread);
16409
+ for (const envelope of envelopes) {
16410
+ opts.session.sendSessionProtocolMessage(envelope);
16411
+ }
16412
+ opts.messageBuffer.addMessage(`Imported thread ${trimIdent(opts.threadId)} as ${trimIdent(forked.threadId)}`, "status");
16413
+ opts.session.sendSessionEvent({
16414
+ type: "message",
16415
+ message: `Imported Codex thread ${opts.threadId} (continuing as ${forked.threadId})`
16416
+ });
16417
+ return { threadId: forked.threadId, model: forked.model, replayed: envelopes.length };
16418
+ }
16419
+
16575
16420
  function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, notify }) {
16576
16421
  if (shouldExit) {
16577
16422
  return false;
@@ -16786,6 +16631,12 @@ async function runCodex(opts) {
16786
16631
  ...forkedFromMessageId ? { forkedFromMessageId } : {}
16787
16632
  });
16788
16633
  metadata.attachmentKinds = [...CLAUDE_ATTACHMENT_KINDS];
16634
+ const importCodexThreadId = process.env[IMPORT_CODEX_THREAD_ENV];
16635
+ if (importCodexThreadId) {
16636
+ metadata.importedFromCodexThreadId = importCodexThreadId;
16637
+ const importTitle = process.env.HAPPY_IMPORT_TITLE;
16638
+ if (importTitle) metadata.summary = { text: importTitle, updatedAt: Date.now() };
16639
+ }
16789
16640
  const reconnectSessionId = process.env.HAPPY_RECONNECT_SESSION_ID;
16790
16641
  const reconnectKeyBase64 = process.env.HAPPY_RECONNECT_ENCRYPTION_KEY;
16791
16642
  const reconnectVariant = process.env.HAPPY_RECONNECT_ENCRYPTION_VARIANT;
@@ -17304,6 +17155,18 @@ async function runCodex(opts) {
17304
17155
  first = false;
17305
17156
  appendSystemPromptInjected = true;
17306
17157
  }
17158
+ if (!reconnectSessionId && !opts.resumeThreadId && importCodexThreadId) {
17159
+ await importCodexThread({
17160
+ client,
17161
+ session,
17162
+ messageBuffer,
17163
+ threadId: importCodexThreadId,
17164
+ cwd: process.cwd(),
17165
+ mcpServers
17166
+ });
17167
+ first = false;
17168
+ appendSystemPromptInjected = true;
17169
+ }
17307
17170
  const forkCodexThreadId = process.env.HAPPY_FORK_CODEX_THREAD_ID;
17308
17171
  if (!reconnectSessionId && forkCodexThreadId) {
17309
17172
  try {
@@ -17657,7 +17520,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17657
17520
  process.exit(0);
17658
17521
  } else if (subcommand === "install-terminal-hooks") {
17659
17522
  try {
17660
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-DfUGgeyb.mjs');
17523
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-8CHTgUC-.mjs');
17661
17524
  const command = parseTerminalHooksArgs(args.slice(1));
17662
17525
  if (command.action === "help") {
17663
17526
  console.log(TERMINAL_HOOKS_HELP);
@@ -17674,7 +17537,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17674
17537
  }
17675
17538
  } else if (subcommand === "spawn") {
17676
17539
  try {
17677
- const { handleSpawnCommand } = await import('./spawn-DFfHCafP.mjs');
17540
+ const { handleSpawnCommand } = await import('./spawn-DFIyobCt.mjs');
17678
17541
  await handleSpawnCommand(args.slice(1));
17679
17542
  } catch (error) {
17680
17543
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17686,7 +17549,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17686
17549
  return;
17687
17550
  } else if (subcommand === "sessions") {
17688
17551
  try {
17689
- const { handleSessionsCommand } = await import('./sessions-DxlC4C4g.mjs');
17552
+ const { handleSessionsCommand } = await import('./sessions-Dh0Lg-5z.mjs');
17690
17553
  await handleSessionsCommand(args.slice(1));
17691
17554
  } catch (error) {
17692
17555
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17698,7 +17561,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17698
17561
  return;
17699
17562
  } else if (subcommand === "send") {
17700
17563
  try {
17701
- const { handleSendCommand } = await import('./send-snK_NGAA.mjs');
17564
+ const { handleSendCommand } = await import('./send-BT32Jiel.mjs');
17702
17565
  await handleSendCommand(args.slice(1));
17703
17566
  } catch (error) {
17704
17567
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17804,9 +17667,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17804
17667
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
17805
17668
  const projectId = args[3];
17806
17669
  try {
17807
- const { saveGoogleCloudProjectToConfig } = await import('./config-BRXqgJJj.mjs');
17808
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-DEsg_gHi.mjs').then(function (n) { return n.W; });
17809
- const { ApiClient: ApiClient2 } = await import('./types-D5EsIAOe.mjs').then(function (n) { return n.a0; });
17670
+ const { saveGoogleCloudProjectToConfig } = await import('./config-o9NFsj8x.mjs');
17671
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-DefXc1TL.mjs').then(function (n) { return n.X; });
17672
+ const { ApiClient: ApiClient2 } = await import('./types-CrKeJuyF.mjs').then(function (n) { return n.ad; });
17810
17673
  let userEmail = void 0;
17811
17674
  try {
17812
17675
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -17837,7 +17700,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17837
17700
  }
17838
17701
  if (geminiSubcommand === "project" && args[2] === "get") {
17839
17702
  try {
17840
- const { readGeminiLocalConfig } = await import('./config-BRXqgJJj.mjs');
17703
+ const { readGeminiLocalConfig } = await import('./config-o9NFsj8x.mjs');
17841
17704
  const config = readGeminiLocalConfig();
17842
17705
  if (config.googleCloudProject) {
17843
17706
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -17877,7 +17740,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17877
17740
  process.exit(0);
17878
17741
  }
17879
17742
  try {
17880
- const { runGemini } = await import('./runGemini-BZ0xXRj-.mjs');
17743
+ const { runGemini } = await import('./runGemini-CDUObQTD.mjs');
17881
17744
  let startedBy = void 0;
17882
17745
  for (let i = 1; i < args.length; i++) {
17883
17746
  if (args[i] === "--started-by") {
@@ -17907,7 +17770,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17907
17770
  }
17908
17771
  };
17909
17772
  try {
17910
- const { runAcp, resolveAcpAgentConfig } = await import('./index-DQe7ljLR.mjs');
17773
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-BkmfJyCf.mjs');
17911
17774
  const parsed = parsePiRunnerArgs(args.slice(1));
17912
17775
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
17913
17776
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -17935,7 +17798,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17935
17798
  return;
17936
17799
  } else if (subcommand === "acp") {
17937
17800
  try {
17938
- const { runAcp, resolveAcpAgentConfig } = await import('./index-DQe7ljLR.mjs');
17801
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-BkmfJyCf.mjs');
17939
17802
  let startedBy = void 0;
17940
17803
  let verbose = false;
17941
17804
  const acpArgs = [];
@@ -17975,7 +17838,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17975
17838
  return;
17976
17839
  } else if (subcommand === "openclaw") {
17977
17840
  try {
17978
- const { runOpenClaw } = await import('./runOpenClaw-BcUmnBpW.mjs');
17841
+ const { runOpenClaw } = await import('./runOpenClaw-B6KMCsVB.mjs');
17979
17842
  let startedBy = void 0;
17980
17843
  let verbose = false;
17981
17844
  let gatewayUrl;
@@ -18026,7 +17889,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18026
17889
  return;
18027
17890
  } else if (subcommand === "todo") {
18028
17891
  try {
18029
- const { handleTodoCommand } = await import('./todo-CU5cjQZU.mjs');
17892
+ const { handleTodoCommand } = await import('./todo-jL3ttna3.mjs');
18030
17893
  await handleTodoCommand(args.slice(1));
18031
17894
  } catch (error) {
18032
17895
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -18035,7 +17898,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18035
17898
  return;
18036
17899
  } else if (subcommand === "teams") {
18037
17900
  try {
18038
- const { handleTeamsCommand } = await import('./teams-Cfr-2Yd-.mjs');
17901
+ const { handleTeamsCommand } = await import('./teams-CZ9ICWeB.mjs');
18039
17902
  await handleTeamsCommand(args.slice(1));
18040
17903
  } catch (error) {
18041
17904
  console.error(error instanceof Error ? error.message : "Teams command failed");
@@ -18044,7 +17907,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18044
17907
  return;
18045
17908
  } else if (subcommand === "mcp" && args.length === 1) {
18046
17909
  try {
18047
- const { handleMcpCommand } = await import('./mcp-UnjB3I8_.mjs');
17910
+ const { handleMcpCommand } = await import('./mcp-8DcunR0J.mjs');
18048
17911
  await handleMcpCommand();
18049
17912
  } catch (error) {
18050
17913
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -18406,4 +18269,4 @@ ${chalk.bold("Examples:")}
18406
18269
  }
18407
18270
  }
18408
18271
 
18409
- export { ALLOWED_SPAWN_PERMISSION_MODES as A, hashObject as B, startHappyServer as C, DEFAULT_RECENT_LIMIT as D, registerAgentAttachmentDownloads as E, TitleGenerator as F, GEMINI_MODEL_ENV as G, CLAUDE_ATTACHMENT_KINDS as H, stageClaudeAttachments as I, appendStagedAttachmentsToPrompt as J, BasePermissionHandler as K, writeSessionModeFile as L, MAX_READ_LIMIT as M, normalizeAcpPermissionMode as N, removeSessionModeFile as O, PI_TEAMS_EXTENSION as P, GEMINI_API_KEY_ENV as Q, GOOGLE_API_KEY_ENV as R, SPAWN_AGENTS as S, TEAM_SKILL as T, BaseReasoningProcessor as U, MessageBuffer as V, CHANGE_TITLE_INSTRUCTION as W, sessionWebUrl as a, sanitizeSpawnPermissionMode as b, checkIfDaemonRunningAndCleanupStaleState as c, sendUserMessage as d, listSessions as e, stopSession as f, resolvePermissionRequest as g, archiveSession as h, isValidSpawnOrigin as i, DEFAULT_GEMINI_MODEL as j, setTerminalTitleViaDaemon as k, listAccountSessions as l, registerAssistantSessionTools as m, initialMachineMetadata as n, createSessionMetadata as o, pushClipboardViaDaemon as p, query as q, readSessionTranscript as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, setupOfflineReconnection as u, notifyDaemonSessionStarted as v, waitForSessionKey as w, MessageQueue2 as x, registerKillSessionHandler as y, StreamRelay as z };
18272
+ export { ALLOWED_SPAWN_PERMISSION_MODES as A, appendStagedAttachmentsToPrompt as B, CLAUDE_ATTACHMENT_KINDS as C, DEFAULT_RECENT_LIMIT as D, BasePermissionHandler as E, writeSessionModeFile as F, GEMINI_MODEL_ENV as G, normalizeAcpPermissionMode as H, removeSessionModeFile as I, GEMINI_API_KEY_ENV as J, GOOGLE_API_KEY_ENV as K, BaseReasoningProcessor as L, MAX_READ_LIMIT as M, MessageBuffer as N, CHANGE_TITLE_INSTRUCTION as O, PI_TEAMS_EXTENSION as P, SPAWN_AGENTS as S, TEAM_SKILL as T, sanitizeSpawnPermissionMode as a, sendUserMessage as b, listSessions as c, stopSession as d, resolvePermissionRequest as e, archiveSession as f, DEFAULT_GEMINI_MODEL as g, registerAssistantSessionTools as h, isValidSpawnOrigin as i, initialMachineMetadata as j, createSessionMetadata as k, listAccountSessions as l, setupOfflineReconnection as m, MessageQueue2 as n, registerKillSessionHandler as o, StreamRelay as p, query as q, readSessionTranscript as r, sessionWebUrl as s, tmuxSupportsSessionEnv as t, hashObject as u, startHappyServer as v, waitForSessionKey as w, registerAgentAttachmentDownloads as x, TitleGenerator as y, stageClaudeAttachments as z };
@@ -1,9 +1,9 @@
1
- import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-bqVGMkPh.mjs';
2
- export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-bqVGMkPh.mjs';
3
- import { c as configuration, g as readSettings, i as encodeBase64, l as logger } from './limits-DEsg_gHi.mjs';
4
- import { A as AcpSessionManager } from './AcpSessionManager-BBAQ8EQf.mjs';
5
- import { f as connectionState, A as ApiClient, p as projectPath, g as BUILTIN_TODO_DISCOVERY } from './types-D5EsIAOe.mjs';
6
- import { P as PI_TEAMS_EXTENSION, n as initialMachineMetadata, o as createSessionMetadata, u as setupOfflineReconnection, v as notifyDaemonSessionStarted, z as StreamRelay, x as MessageQueue2, B as hashObject, C as startHappyServer, E as registerAgentAttachmentDownloads, F as TitleGenerator, y as registerKillSessionHandler, H as CLAUDE_ATTACHMENT_KINDS, I as stageClaudeAttachments, J as appendStagedAttachmentsToPrompt, K as BasePermissionHandler, L as writeSessionModeFile, N as normalizeAcpPermissionMode, O as removeSessionModeFile } from './index-Dbl5U_N0.mjs';
1
+ import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-BwTEBAyP.mjs';
2
+ export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-BwTEBAyP.mjs';
3
+ import { c as configuration, g as readSettings, i as encodeBase64, l as logger } from './limits-DefXc1TL.mjs';
4
+ import { A as AcpSessionManager } from './AcpSessionManager-Bh3KETfz.mjs';
5
+ import { k as connectionState, A as ApiClient, n as notifyDaemonSessionStarted, p as projectPath, l as BUILTIN_TODO_DISCOVERY } from './types-CrKeJuyF.mjs';
6
+ import { P as PI_TEAMS_EXTENSION, j as initialMachineMetadata, k as createSessionMetadata, m as setupOfflineReconnection, p as StreamRelay, n as MessageQueue2, u as hashObject, v as startHappyServer, x as registerAgentAttachmentDownloads, y as TitleGenerator, o as registerKillSessionHandler, C as CLAUDE_ATTACHMENT_KINDS, z as stageClaudeAttachments, B as appendStagedAttachmentsToPrompt, E as BasePermissionHandler, F as writeSessionModeFile, H as normalizeAcpPermissionMode, I as removeSessionModeFile } from './index-BZr5l8TH.mjs';
7
7
  import { mkdir, chmod, writeFile, rename } from 'node:fs/promises';
8
8
  import { randomUUID } from 'node:crypto';
9
9
  import { join } from 'node:path';
@@ -32,7 +32,7 @@ import 'fs';
32
32
  import 'node:readline';
33
33
  import '@anthropic-ai/sandbox-runtime';
34
34
  import 'expo-server-sdk';
35
- import './agentGuidance-WHZsBKpm.mjs';
35
+ import './agentGuidance-DRnXdFAY.mjs';
36
36
  import 'ink';
37
37
  import 'react';
38
38
  import '@anthropic-ai/claude-agent-sdk';