very-happy-cli 0.2.149 → 0.2.151

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 (50) hide show
  1. package/dist/{AcpBackend-Baqb8dNm.mjs → AcpBackend-BjTA5gUB.mjs} +2 -2
  2. package/dist/{AcpBackend-CnZpEfGd.cjs → AcpBackend-blE9IEod.cjs} +2 -2
  3. package/dist/{AcpSessionManager-CC6mdFXs.mjs → AcpSessionManager-D-9mrp40.mjs} +1 -1
  4. package/dist/{AcpSessionManager-e2vxmIbj.cjs → AcpSessionManager-bF8PYu_B.cjs} +1 -1
  5. package/dist/{agentGuidance--QIi_rZ2.cjs → agentGuidance-BXa9ikrZ.cjs} +1 -1
  6. package/dist/{agentGuidance-oMVJvUl7.mjs → agentGuidance-Dvo3cZMW.mjs} +1 -1
  7. package/dist/{agentHome-CKSPsS8d.mjs → agentHome-CeFDNzvq.mjs} +2 -2
  8. package/dist/{agentHome-3kRCtzOL.cjs → agentHome-D6MSfwnp.cjs} +2 -2
  9. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  10. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  11. package/dist/{config-C8jpk6IA.mjs → config-C-A1XF62.mjs} +4 -4
  12. package/dist/{config-CzJZcvTO.cjs → config-Cadw1sAk.cjs} +4 -4
  13. package/dist/{index-DgK4KX-Z.cjs → index-BKGspSWk.cjs} +7 -7
  14. package/dist/{index-Dfkyar7S.mjs → index-CIyjJy8D.mjs} +338 -76
  15. package/dist/{index-CyfXsCXl.cjs → index-CqtfRzug.cjs} +338 -73
  16. package/dist/{index-DrVq0CqK.mjs → index-DEp6CmmS.mjs} +8 -8
  17. package/dist/{index-BPCXUrEe.mjs → index-DYQgcgL2.mjs} +4 -4
  18. package/dist/{index-DBN2Zpqc.cjs → index-vyRqLjDv.cjs} +4 -4
  19. package/dist/index.cjs +4 -4
  20. package/dist/index.mjs +4 -4
  21. package/dist/{installTerminalHooks-kdAoTfNT.cjs → installTerminalHooks-Bf_adbl3.cjs} +4 -4
  22. package/dist/{installTerminalHooks-COqkLz6f.mjs → installTerminalHooks-Ch_yTtEA.mjs} +4 -4
  23. package/dist/lib.cjs +2 -2
  24. package/dist/lib.mjs +2 -2
  25. package/dist/{limits-BXI1Gzvd.mjs → limits-DS-bnYYp.mjs} +7 -7
  26. package/dist/{limits-FZWhlsBu.cjs → limits-DwcY3kyi.cjs} +7 -7
  27. package/dist/{mcp-DoLZbbZp.cjs → mcp-CMxEZ6-0.cjs} +4 -4
  28. package/dist/{mcp-Dfnoirpi.mjs → mcp-zu8n8TkR.mjs} +4 -4
  29. package/dist/{piRuntime-BK6YZm2X.mjs → piRuntime-Cu6cXZ6N.mjs} +2 -2
  30. package/dist/{piRuntime-DmE4ysfG.cjs → piRuntime-D3ny0mVg.cjs} +2 -2
  31. package/dist/{piTerminal-Bczubq2n.mjs → piTerminal-M677UP7y.mjs} +6 -6
  32. package/dist/{piTerminal-qQAzQwIU.cjs → piTerminal-kDSd_r9X.cjs} +6 -6
  33. package/dist/{runGemini-ComTu0EB.cjs → runGemini-DuwdVV1O.cjs} +6 -6
  34. package/dist/{runGemini-Cce_pjTs.mjs → runGemini-DvJwQVGp.mjs} +6 -6
  35. package/dist/{runOpenClaw-DXP5_chb.mjs → runOpenClaw-BG82bF18.mjs} +5 -5
  36. package/dist/{runOpenClaw-M3YlDt8z.cjs → runOpenClaw-BsvtCE_H.cjs} +5 -5
  37. package/dist/{send-0LrSwGgr.cjs → send-aUkjhey7.cjs} +20 -6
  38. package/dist/{send-DDD57Cr_.mjs → send-o6WM-1G8.mjs} +20 -6
  39. package/dist/{sessions-2Bkau9T4.cjs → sessions-B3vP18kz.cjs} +56 -10
  40. package/dist/{sessions-ClBPEtz2.mjs → sessions-DlU3foY_.mjs} +56 -10
  41. package/dist/{spawn-Cm3Da8Ge.cjs → spawn-DH6_p4-f.cjs} +166 -27
  42. package/dist/spawn-DxHIIqhi.mjs +397 -0
  43. package/dist/{teams-DKKeIG-u.cjs → teams-BW8D1n3o.cjs} +4 -4
  44. package/dist/{teams-DyBjdXI3.mjs → teams-DoAA_CcN.mjs} +4 -4
  45. package/dist/{todo-8RgCTDxH.cjs → todo-Bcc2XxGh.cjs} +3 -3
  46. package/dist/{todo-jXHGGH89.mjs → todo-BhJbG5eq.mjs} +3 -3
  47. package/dist/{types-ipzMqciq.mjs → types-BN3eiipi.mjs} +46 -3
  48. package/dist/{types-t1gxALVE.cjs → types-CPubg05Z.cjs} +52 -2
  49. package/package.json +7 -7
  50. package/dist/spawn-7lLymdT1.mjs +0 -258
@@ -1,15 +1,15 @@
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 { v as notifyDaemonSessionEvent, w as getProjectPath, o as BUILTIN_TODO_DISCOVERY, x as initializeSandbox, y as wrapCommand, t as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, u as delay, z as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, C as notifyDaemonClaudeAuthFailed, D as commandOnPath, c as checkIfDaemonRunningAndCleanupStaleState, E as deriveLocalCliUpdateSummary, F as AGENT_VERSION_SOURCES, G as isClaudeSessionId, H as getLocalHappyAgentCredentialPath, J as readLocalHappyAgentCredentials, K as parseExactVersion, L as compareExactVersions, N as extractCompleteLines, O as decideBackfill, P as parseTerminalHookPayload, Q as tmuxArgs, T as scrubTmuxClientEnv, U as decideMirrorBinding, V as MIRROR_BACKFILL_LINES_DEFAULT, W as mirrorLineKey, X as mirrorLocalId, Y as listDaemonSessions, Z as stopDaemonSession, _ as detectCLIAvailability, $ as detectResumeSupport, a0 as refreshAgentHomes, d as describeAgentHome, a1 as isDaemonRunningCurrentlyInstalledHappyVersion, a2 as stopDaemon, a3 as TurnActivityTracker, a4 as summarizeSpawnSessionForLog, a5 as locateClaudeConversation, a6 as agentHomeSpawnEnv, a7 as shellescape, A as ApiClient, a8 as fetchCliUpdateState, a9 as resolveCliUpdateCheckInterval, aa as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, ab as cleanupDaemonState, i as isValidSessionId, s as spawnDaemonSession, ac as listVhTerminals, ad as isValidTerminalId, ae as readVhTerminal, af as sendToVhTerminal, ag as ContextUsageSchema, ah as TITLE_PROMPT_PREFIX, ai as BOARD_ANALYZER_PROMPT_PREFIX, m as connectionState, aj as startOfflineReconnection, n as notifyDaemonSessionStarted, g as createEnvelope, ak as CodexAppServerClient } from './types-ipzMqciq.mjs';
5
- import { l as logger, F as ensurePrivateDirectory, G as hardenPrivateFile, c as configuration, p as packageJson, r as readSettings, H as readCredentials, x as credentialRelayProblem, o as readDaemonState, g as encodeBase64, I as readSettingsOutcome, u as updateSettings, J as encodeBase64Url, m as decodeBase64, K as writeCredentialsLegacy, L as writeCredentialsDataKey, e as readCredentialsForConfiguredRelay, j as encrypt, d as readPersistedSessions, w as writePrivateFileSync, M as decryptWithDataKey, N as decryptLegacy, O as persistSession, k as decrypt, h as ensurePrivateDirectorySync, P as acquireDaemonLock, Q as deletePersistedSessions, R as writeDaemonState, S as writeSettings, T as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, f as CLIPBOARD_MAX_BYTES, U as clearCredentials, V as clearMachineId, W as SandboxConfigSchema, X as getLatestDaemonLog } from './limits-BXI1Gzvd.mjs';
4
+ import { H as notifyDaemonSessionEvent, q as getProjectPath, z as BUILTIN_TODO_DISCOVERY, I as initializeSandbox, J as wrapCommand, E as contentLogMetadata, K as InvalidateSync, R as RawJSONLinesSchema, G as delay, L as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, N as notifyDaemonClaudeAuthFailed, O as commandOnPath, u as checkIfDaemonRunningAndCleanupStaleState, P as deriveLocalCliUpdateSummary, Q as AGENT_VERSION_SOURCES, T as isClaudeSessionId, U as getLocalHappyAgentCredentialPath, V as readLocalHappyAgentCredentials, W as parseExactVersion, X as compareExactVersions, Y as extractCompleteLines, Z as decideBackfill, _ as parseTerminalHookPayload, $ as tmuxArgs, a0 as scrubTmuxClientEnv, a1 as decideMirrorBinding, a2 as MIRROR_BACKFILL_LINES_DEFAULT, a3 as mirrorLineKey, a4 as mirrorLocalId, a5 as listDaemonSessions, w as stopDaemonSession, a6 as detectCLIAvailability, a7 as detectResumeSupport, a8 as refreshAgentHomes, d as describeAgentHome, a9 as isDaemonRunningCurrentlyInstalledHappyVersion, aa as stopDaemon, ab as TurnActivityTracker, ac as summarizeSpawnSessionForLog, ad as locateClaudeConversation, ae as agentHomeSpawnEnv, af as shellescape, A as ApiClient, ag as fetchCliUpdateState, ah as resolveCliUpdateCheckInterval, ai as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, aj as cleanupDaemonState, i as isValidSessionId, v as spawnDaemonSession, ak as listVhTerminals, al as isValidTerminalId, am as readVhTerminal, an as sendToVhTerminal, ao as ContextUsageSchema, ap as TITLE_PROMPT_PREFIX, aq as BOARD_ANALYZER_PROMPT_PREFIX, x as connectionState, ar as startOfflineReconnection, y as notifyDaemonSessionStarted, c as createEnvelope, C as CodexAppServerClient } from './types-BN3eiipi.mjs';
5
+ import { l as logger, F as ensurePrivateDirectory, G as hardenPrivateFile, c as configuration, p as packageJson, r as readSettings, H as readCredentials, x as credentialRelayProblem, o as readDaemonState, g as encodeBase64, I as readSettingsOutcome, u as updateSettings, J as encodeBase64Url, m as decodeBase64, K as writeCredentialsLegacy, L as writeCredentialsDataKey, e as readCredentialsForConfiguredRelay, j as encrypt, d as readPersistedSessions, w as writePrivateFileSync, M as decryptWithDataKey, N as decryptLegacy, O as persistSession, k as decrypt, h as ensurePrivateDirectorySync, P as acquireDaemonLock, Q as deletePersistedSessions, R as writeDaemonState, S as writeSettings, T as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, f as CLIPBOARD_MAX_BYTES, U as clearCredentials, V as clearMachineId, W as SandboxConfigSchema, X as getLatestDaemonLog } from './limits-DS-bnYYp.mjs';
6
6
  import spawn$1, { spawn } from 'cross-spawn';
7
- import path, { join, resolve, basename, isAbsolute, sep, dirname, delimiter, normalize } from 'node:path';
7
+ import path, { join, resolve, basename, delimiter, isAbsolute, dirname, sep, normalize } from 'node:path';
8
8
  import { createInterface } from 'node:readline';
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';
9
+ import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, realpathSync, unlinkSync, renameSync, writeFileSync, accessSync, constants as constants$1, openSync, fsyncSync, closeSync, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
10
10
  import { p as projectPath } from './projectPath-CSpqWdxp.mjs';
11
- 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-oMVJvUl7.mjs';
12
- import { readFile, writeFile, rename, unlink, mkdir, symlink, rm, readlink, lstat, stat, open as open$1, appendFile } from 'node:fs/promises';
11
+ 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-Dvo3cZMW.mjs';
12
+ import { readFile, writeFile, rename, unlink, mkdir, access as access$1, symlink, rm, readlink, lstat, constants, stat, open as open$1, appendFile } from 'node:fs/promises';
13
13
  import fs, { watch, access } from 'fs/promises';
14
14
  import { useStdout, useInput, Box, Text, render } from 'ink';
15
15
  import React, { useState, useRef, useEffect, useCallback } from 'react';
@@ -456,17 +456,23 @@ function readClaudeSettings() {
456
456
  }
457
457
  function shouldIncludeCoAuthoredBy() {
458
458
  const settings = readClaudeSettings();
459
- if (!settings || settings.includeCoAuthoredBy === void 0) {
459
+ if (!settings) {
460
+ return true;
461
+ }
462
+ if (settings.attribution?.commit === "") {
463
+ return false;
464
+ }
465
+ if (settings.includeCoAuthoredBy === void 0) {
460
466
  return true;
461
467
  }
462
468
  return settings.includeCoAuthoredBy;
463
469
  }
464
470
 
465
471
  const BASE_SYSTEM_PROMPT = (() => trimIdent(`
466
- ALWAYS when you start a new chat - you must call a tool "mcp__happy__change_title" to set a chat title. When you think chat title is not relevant anymore - call the tool again to change it. When chat name is too generic and you have a change to make it more specific - call the tool again to change it. This title is needed to easily find the chat in the future. Help human.
472
+ This chat is titled automatically from the user's first message. Call "mcp__happy__change_title" only when the topic has clearly moved on or the current title is too generic to find this chat later. Do not call it as a ritual at the start of a chat, and not for a quick question you can just answer.
467
473
  `))() + "\n\n" + AGENT_GUIDANCE + "\n\n" + BUILTIN_TODO_DISCOVERY;
468
474
  const CO_AUTHORED_CREDITS = (() => trimIdent(`
469
- When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
475
+ When you yourself create a git commit, instead of just giving co-credit to Claude, also give credit to Happy like so:
470
476
 
471
477
  <main commit message>
472
478
 
@@ -474,6 +480,8 @@ const CO_AUTHORED_CREDITS = (() => trimIdent(`
474
480
  via [Very Happy](https://github.com/Mereithhh/very-happy)
475
481
 
476
482
  Co-Authored-By: Claude <noreply@anthropic.com>
483
+
484
+ Do not add these lines to commit message text you only draft for the user.
477
485
  `))();
478
486
  const systemPrompt = (() => {
479
487
  const includeCoAuthored = shouldIncludeCoAuthoredBy();
@@ -870,7 +878,7 @@ async function createSessionScanner(opts) {
870
878
  let processedMessageKeys = /* @__PURE__ */ new Set();
871
879
  let deadSessions = /* @__PURE__ */ new Set();
872
880
  if (opts.sessionId) {
873
- let messages = await readSessionLog(projectDir, opts.sessionId);
881
+ let messages = await readSessionLog$1(projectDir, opts.sessionId);
874
882
  logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
875
883
  for (let m of messages) {
876
884
  processedMessageKeys.add(messageKey(m));
@@ -893,7 +901,7 @@ async function createSessionScanner(opts) {
893
901
  }
894
902
  }
895
903
  for (let session of sessions) {
896
- const sessionMessages = await readSessionLog(projectDir, session);
904
+ const sessionMessages = await readSessionLog$1(projectDir, session);
897
905
  let skipped = 0;
898
906
  let sent = 0;
899
907
  for (let file of sessionMessages) {
@@ -970,7 +978,7 @@ async function createSessionScanner(opts) {
970
978
  return;
971
979
  }
972
980
  if (options?.treatExistingAsProcessed) {
973
- const existing = await readSessionLog(projectDir, sessionId);
981
+ const existing = await readSessionLog$1(projectDir, sessionId);
974
982
  logger.debug(`[SESSION_SCANNER] Pre-marking ${existing.length} existing messages as processed for new session ${sessionId}`);
975
983
  for (const m of existing) {
976
984
  processedMessageKeys.add(messageKey(m));
@@ -998,7 +1006,7 @@ function messageKey(message) {
998
1006
  throw Error();
999
1007
  }
1000
1008
  }
1001
- async function readSessionLog(projectDir, sessionId) {
1009
+ async function readSessionLog$1(projectDir, sessionId) {
1002
1010
  const expectedSessionFile = join(projectDir, `${sessionId}.jsonl`);
1003
1011
  logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
1004
1012
  let file;
@@ -5104,6 +5112,61 @@ function shareSafeEnvironmentInfo(env = process.env, runtime = process) {
5104
5112
  };
5105
5113
  }
5106
5114
 
5115
+ const NPM_SAFE_ARG = /^[A-Za-z0-9@=.,+/_-]+$/;
5116
+ function globalPrefixForPackage(packageDir) {
5117
+ if (!isAbsolute(packageDir)) return null;
5118
+ const dir = resolve(packageDir);
5119
+ const nodeModules = dirname(dir);
5120
+ const lib = dirname(nodeModules);
5121
+ const prefix = dirname(lib);
5122
+ if (basename(dir) !== "very-happy-cli" || basename(nodeModules) !== "node_modules" || basename(lib) !== "lib") return null;
5123
+ if (prefix === lib || resolve(prefix) === "/" || !NPM_SAFE_ARG.test(prefix)) return null;
5124
+ return prefix;
5125
+ }
5126
+ function packageDirForBin(realBin) {
5127
+ const packageDir = dirname(dirname(realBin));
5128
+ return basename(dirname(realBin)) === "bin" && basename(packageDir) === "very-happy-cli" ? packageDir : null;
5129
+ }
5130
+ function installLocationWarnings(facts, version) {
5131
+ const warnings = [];
5132
+ const running = resolve(facts.runningPackageDir);
5133
+ const packages = [...new Set(facts.onPath.map(packageDirForBin).filter((dir) => Boolean(dir)).map((dir) => resolve(dir)))];
5134
+ if (packages.length > 1) {
5135
+ warnings.push(`Found ${packages.length} very-happy installs on PATH (first one wins): ${packages.join(", ")}. Updating one does not update the others; remove the ones you do not use.`);
5136
+ }
5137
+ if (packages[0] && packages[0] !== running) {
5138
+ warnings.push(`The very-happy command runs ${packages[0]}, but this CLI runs from ${running}.`);
5139
+ }
5140
+ const prefix = globalPrefixForPackage(running);
5141
+ if (prefix && facts.npmGlobalPrefix && resolve(facts.npmGlobalPrefix) !== resolve(prefix)) {
5142
+ const target = version ? `very-happy-cli@${version}` : "very-happy-cli@latest";
5143
+ warnings.push(`npm install -g writes to ${facts.npmGlobalPrefix}, but this CLI runs from ${prefix}; a plain update will not take effect. Update with: npm install -g --prefix ${prefix} --allow-scripts=very-happy-cli,node-pty ${target} && very-happy daemon start`);
5144
+ }
5145
+ return warnings;
5146
+ }
5147
+ function gatherInstallLocationFacts(runningPackageDir, env = process.env) {
5148
+ if (process.platform === "win32") return null;
5149
+ const onPath = [];
5150
+ for (const dir of (env.PATH ?? "").split(delimiter)) {
5151
+ if (!dir) continue;
5152
+ try {
5153
+ const candidate = join(dir, "very-happy");
5154
+ if (!statSync(candidate).isFile()) continue;
5155
+ const real = realpathSync(candidate);
5156
+ if (!onPath.includes(real)) onPath.push(real);
5157
+ } catch {
5158
+ }
5159
+ }
5160
+ let npmGlobalPrefix = null;
5161
+ try {
5162
+ const result = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8", timeout: 5e3, env });
5163
+ const out = result.status === 0 ? result.stdout.trim() : "";
5164
+ npmGlobalPrefix = out && isAbsolute(out) && !out.includes("\n") ? out : null;
5165
+ } catch {
5166
+ }
5167
+ return { runningPackageDir, onPath, npmGlobalPrefix };
5168
+ }
5169
+
5107
5170
  function printCliUpdateStatus(state) {
5108
5171
  const installed = configuration.currentCliVersion;
5109
5172
  const running = state?.startedWithCliVersion ?? null;
@@ -5121,6 +5184,10 @@ function printCliUpdateStatus(state) {
5121
5184
  console.log(chalk.yellow(" \u25B3 A newer CLI is available."));
5122
5185
  }
5123
5186
  if (summary.installCommand) console.log(` Update: ${summary.installCommand}`);
5187
+ const facts = gatherInstallLocationFacts(projectPath());
5188
+ for (const warning of facts ? installLocationWarnings(facts, state?.cliUpdate?.recommendedVersion ?? void 0) : []) {
5189
+ console.log(chalk.yellow(` \u26A0 ${warning}`));
5190
+ }
5124
5191
  }
5125
5192
  function getEnvironmentInfo() {
5126
5193
  return shareSafeEnvironmentInfo();
@@ -5313,8 +5380,8 @@ ${typeLabels[type] || type}:`));
5313
5380
  }
5314
5381
  console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
5315
5382
  try {
5316
- const { resolveAgentHomesForCli } = await import('./agentHome-CKSPsS8d.mjs');
5317
- const { describeAgentHome } = await import('./types-ipzMqciq.mjs').then(function (n) { return n.al; });
5383
+ const { resolveAgentHomesForCli } = await import('./agentHome-CeFDNzvq.mjs');
5384
+ const { describeAgentHome } = await import('./types-BN3eiipi.mjs').then(function (n) { return n.as; });
5318
5385
  const homes = await resolveAgentHomesForCli();
5319
5386
  console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
5320
5387
  console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
@@ -6025,7 +6092,9 @@ async function sendUserMessage(sessionId, persisted, text, client, options = {})
6025
6092
  },
6026
6093
  meta: {
6027
6094
  sentFrom: options.sentFrom ?? "cli",
6028
- ...options.model ? { model: options.model } : {}
6095
+ ...options.model !== void 0 ? { model: options.model } : {},
6096
+ ...options.permissionMode !== void 0 ? { permissionMode: options.permissionMode } : {},
6097
+ ...options.effort !== void 0 ? { effort: options.effort } : {}
6029
6098
  }
6030
6099
  };
6031
6100
  const encrypted = encodeBase64(encrypt(
@@ -6336,13 +6405,22 @@ function startDaemonControlServer({
6336
6405
  permissionMode: z.string().optional(),
6337
6406
  // B-069: spawn-origin tag ('assistant' = session_spawn). Recorded on
6338
6407
  // the TrackedSession; old clients never send it.
6339
- spawnedBy: z.string().optional()
6408
+ spawnedBy: z.string().optional(),
6409
+ // B-492: `very-happy spawn --fork` — same fields the web's fork flow
6410
+ // sends over the machine RPC. Old CLIs never send them.
6411
+ resumeClaudeSessionId: z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i).optional(),
6412
+ resumeCodexThreadId: z.string().min(1).max(200).optional(),
6413
+ parentSessionId: z.string().min(1).max(200).optional()
6340
6414
  }),
6341
6415
  response: {
6342
6416
  200: z.object({
6343
6417
  success: z.boolean(),
6344
6418
  sessionId: z.string().optional(),
6345
- approvedNewDirectoryCreation: z.boolean().optional()
6419
+ approvedNewDirectoryCreation: z.boolean().optional(),
6420
+ // B-492: true when resumeClaudeSessionId / resumeCodexThreadId was
6421
+ // honoured. A caller that asked for a fork and does not get this
6422
+ // back is talking to an older daemon that spawned a fresh session.
6423
+ resumed: z.boolean().optional()
6346
6424
  }),
6347
6425
  409: z.object({
6348
6426
  success: z.boolean(),
@@ -6357,13 +6435,14 @@ function startDaemonControlServer({
6357
6435
  }
6358
6436
  }
6359
6437
  }, async (request, reply) => {
6360
- const { directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy } = request.body;
6438
+ const { directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId } = request.body;
6361
6439
  if (!directory && variant !== "assistant") {
6362
6440
  reply.code(500);
6363
6441
  return { success: false, error: "directory is required" };
6364
6442
  }
6365
6443
  logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}, agent=${agent || "default"}, variant=${variant || "none"}, forceNew=${forceNew === true}, spawnedBy=${spawnedBy || "unset"}`);
6366
- const result = await spawnSession({ directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy });
6444
+ const result = await spawnSession({ directory, sessionId, agent, environmentVariables, variant, forceNew, permissionMode, spawnedBy, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId });
6445
+ const resumed = Boolean(resumeClaudeSessionId || resumeCodexThreadId);
6367
6446
  switch (result.type) {
6368
6447
  case "success":
6369
6448
  if (!result.sessionId) {
@@ -6376,7 +6455,8 @@ function startDaemonControlServer({
6376
6455
  return {
6377
6456
  success: true,
6378
6457
  sessionId: result.sessionId,
6379
- approvedNewDirectoryCreation: true
6458
+ approvedNewDirectoryCreation: true,
6459
+ ...resumed ? { resumed: true } : {}
6380
6460
  };
6381
6461
  case "requestToApproveDirectoryCreation":
6382
6462
  reply.code(409);
@@ -7617,8 +7697,8 @@ function serialTask(task) {
7617
7697
  };
7618
7698
  }
7619
7699
 
7620
- function autoUpdateInstallArgs(version) {
7621
- return ["i", "-g", "--allow-scripts=very-happy-cli,node-pty", `very-happy-cli@${version}`];
7700
+ function autoUpdateInstallArgs(version, prefix) {
7701
+ return ["i", "-g", ...prefix ? [`--prefix=${prefix}`] : [], "--allow-scripts=very-happy-cli,node-pty", `very-happy-cli@${version}`];
7622
7702
  }
7623
7703
 
7624
7704
  const processDefaults = {
@@ -7678,7 +7758,34 @@ const runNpmProcess = (args, deps = processDefaults) => new Promise((resolveResu
7678
7758
  child.on("close", (code) => done({ code: timedOut ? null : code, stdout, stderr, terminationConfirmed: !timedOut || treeKilled }));
7679
7759
  });
7680
7760
  const runNpm = (args) => runNpmProcess(args);
7681
- const defaults = { npm: runNpm, fs: { lstat, readlink, unlink, rm, readFile, symlink }, platform: process.platform };
7761
+ const defaults = {
7762
+ npm: runNpm,
7763
+ fs: { lstat, readlink, unlink, rm, readFile, symlink, access: access$1 },
7764
+ platform: process.platform,
7765
+ runningPackageDir: projectPath()
7766
+ };
7767
+ async function runningVersion(deps) {
7768
+ if (!deps.runningPackageDir) return null;
7769
+ try {
7770
+ const manifest = JSON.parse(await deps.fs.readFile(join(deps.runningPackageDir, "package.json"), "utf8"));
7771
+ return manifest?.name === "very-happy-cli" && typeof manifest.version === "string" ? manifest.version : null;
7772
+ } catch {
7773
+ return null;
7774
+ }
7775
+ }
7776
+ async function verifyRunningCopy(code, version, deps) {
7777
+ if (code !== 0 || !deps.runningPackageDir) return code;
7778
+ return await runningVersion(deps) === version ? 0 : { failed: "installed_elsewhere" };
7779
+ }
7780
+ async function writable(path, deps) {
7781
+ if (!deps.fs.access) return true;
7782
+ try {
7783
+ await deps.fs.access(path, constants.W_OK);
7784
+ return true;
7785
+ } catch (error) {
7786
+ return error.code === "ENOENT";
7787
+ }
7788
+ }
7682
7789
  function ownedCliLink(link, target, packageDir) {
7683
7790
  const resolved = resolve(dirname(link), target);
7684
7791
  return resolved.startsWith(join(packageDir, "bin") + sep);
@@ -7686,14 +7793,27 @@ function ownedCliLink(link, target, packageDir) {
7686
7793
  async function installCliSafely(version, deps = defaults) {
7687
7794
  const parsed = parseExactVersion(version);
7688
7795
  if (!parsed) throw new Error("Invalid exact CLI version");
7689
- const args = autoUpdateInstallArgs(parsed.exact);
7690
7796
  if (deps.platform === "win32") {
7691
- const result2 = await deps.npm(args);
7692
- return result2.terminationConfirmed === false ? "blocked" : result2.code;
7797
+ const result2 = await deps.npm(autoUpdateInstallArgs(parsed.exact));
7798
+ return result2.terminationConfirmed === false ? "blocked" : verifyRunningCopy(result2.code, parsed.exact, deps);
7693
7799
  }
7694
- const rootResult = await deps.npm(["root", "-g"]);
7800
+ let pinnedPrefix;
7801
+ if (deps.runningPackageDir) {
7802
+ const running = globalPrefixForPackage(deps.runningPackageDir);
7803
+ const runningStat = running ? await deps.fs.lstat(deps.runningPackageDir).catch(() => null) : null;
7804
+ if (!running || !runningStat?.isDirectory() || runningStat.isSymbolicLink() || !await runningVersion(deps)) {
7805
+ return { manual: "install_location_unverified" };
7806
+ }
7807
+ for (const path of [dirname(deps.runningPackageDir), deps.runningPackageDir, join(running, "bin")]) {
7808
+ if (!await writable(path, deps)) return { manual: "install_location_not_writable" };
7809
+ }
7810
+ pinnedPrefix = running;
7811
+ }
7812
+ const prefixArgs = pinnedPrefix ? [`--prefix=${pinnedPrefix}`] : [];
7813
+ const args = autoUpdateInstallArgs(parsed.exact, pinnedPrefix);
7814
+ const rootResult = await deps.npm(["root", "-g", ...prefixArgs]);
7695
7815
  if (rootResult.terminationConfirmed === false) return "blocked";
7696
- const prefixResult = await deps.npm(["prefix", "-g"]);
7816
+ const prefixResult = await deps.npm(["prefix", "-g", ...prefixArgs]);
7697
7817
  if (prefixResult.terminationConfirmed === false) return "blocked";
7698
7818
  const root = rootResult.stdout.trim();
7699
7819
  const prefix = prefixResult.stdout.trim();
@@ -7702,6 +7822,9 @@ async function installCliSafely(version, deps = defaults) {
7702
7822
  }
7703
7823
  if (resolve(root) !== resolve(prefix, "lib/node_modules")) throw new Error("Unexpected npm root/prefix relationship");
7704
7824
  const packageDir = join(root, "very-happy-cli");
7825
+ if (deps.runningPackageDir && resolve(packageDir) !== resolve(deps.runningPackageDir)) {
7826
+ return { manual: "install_location_unverified" };
7827
+ }
7705
7828
  const packageStat = await deps.fs.lstat(packageDir).catch((error) => {
7706
7829
  if (error.code === "ENOENT") return null;
7707
7830
  throw error;
@@ -7736,7 +7859,7 @@ async function installCliSafely(version, deps = defaults) {
7736
7859
  return "blocked";
7737
7860
  }
7738
7861
  result = first.code;
7739
- if (first.code === 0 || first.code === null) return first.code;
7862
+ if (first.code === 0 || first.code === null) return await verifyRunningCopy(first.code, parsed.exact, deps);
7740
7863
  if (!/\b(?:EEXIST|ENOTEMPTY)\b/.test(first.stderr ?? "")) return first.code;
7741
7864
  const failedStat = await deps.fs.lstat(packageDir).catch((error) => {
7742
7865
  if (error.code === "ENOENT") return null;
@@ -7752,7 +7875,7 @@ async function installCliSafely(version, deps = defaults) {
7752
7875
  safeToRestore = false;
7753
7876
  return "blocked";
7754
7877
  }
7755
- return result;
7878
+ return await verifyRunningCopy(result, parsed.exact, deps);
7756
7879
  } finally {
7757
7880
  if (result !== 0 && safeToRestore) {
7758
7881
  for (const { link, target } of ownLinks) {
@@ -7788,7 +7911,7 @@ function createUpdateController(deps) {
7788
7911
  async function evaluate() {
7789
7912
  if (!policy || running) return;
7790
7913
  const target = manualVersion ?? policy.autoUpdateVersion ?? null;
7791
- if (outcome?.version === target && ["failed", "installed"].includes(outcome.state)) {
7914
+ if (outcome?.version === target && ["failed", "installed", "manual_required"].includes(outcome.state)) {
7792
7915
  const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail, manualVersion]);
7793
7916
  if (fingerprint !== lastPublished) {
7794
7917
  lastPublished = fingerprint;
@@ -7826,6 +7949,11 @@ function createUpdateController(deps) {
7826
7949
  publish("installing", target);
7827
7950
  try {
7828
7951
  const code = await deps.install(target);
7952
+ if (code && typeof code === "object") {
7953
+ if ("manual" in code) publish("manual_required", target, code.manual);
7954
+ else publish("failed", target, code.failed);
7955
+ return;
7956
+ }
7829
7957
  blocked = code === "blocked";
7830
7958
  publish(
7831
7959
  blocked ? "manual_required" : code === 0 ? "installed" : "failed",
@@ -8219,7 +8347,7 @@ class ClaudeAuthService {
8219
8347
  const bin = join(dir, "security");
8220
8348
  try {
8221
8349
  if (!statSync(bin).isFile()) return null;
8222
- accessSync(bin, constants.X_OK);
8350
+ accessSync(bin, constants$1.X_OK);
8223
8351
  return dir;
8224
8352
  } catch {
8225
8353
  return null;
@@ -9260,6 +9388,8 @@ ${goal}`;
9260
9388
 
9261
9389
  const MAX_LINE_CHARS = 500;
9262
9390
  const MAX_ARGS_CHARS = 160;
9391
+ const DEFAULT_LIMITS = { line: MAX_LINE_CHARS, args: MAX_ARGS_CHARS };
9392
+ const NO_LIMITS = { line: Number.POSITIVE_INFINITY, args: Number.POSITIVE_INFINITY };
9263
9393
  function truncateText(text, max = MAX_LINE_CHARS) {
9264
9394
  const chars = Array.from(text);
9265
9395
  if (chars.length <= max) return text;
@@ -9275,12 +9405,12 @@ function safeJson(value, max) {
9275
9405
  return "[unserializable]";
9276
9406
  }
9277
9407
  }
9278
- function formatMessageBody(body) {
9408
+ function formatMessageBody(body, limits = DEFAULT_LIMITS) {
9279
9409
  if (!body || typeof body !== "object") return null;
9280
9410
  const b = body;
9281
9411
  if (b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string") {
9282
9412
  const text = squash(b.content.text);
9283
- return text.length > 0 ? `user: ${truncateText(text)}` : null;
9413
+ return text.length > 0 ? `user: ${truncateText(text, limits.line)}` : null;
9284
9414
  }
9285
9415
  if (b.role === "session" && b.content && typeof b.content === "object") {
9286
9416
  const env = b.content;
@@ -9292,18 +9422,18 @@ function formatMessageBody(body) {
9292
9422
  const text = squash(String(ev.text ?? ""));
9293
9423
  if (text.length === 0) return null;
9294
9424
  const role = env.role === "user" ? "user" : "assistant";
9295
- return `${role}: ${truncateText(text)}`;
9425
+ return `${role}: ${truncateText(text, limits.line)}`;
9296
9426
  }
9297
9427
  case "tool-call-start": {
9298
9428
  const name = String(ev.name ?? "tool");
9299
9429
  const title = squash(String(ev.title ?? ""));
9300
- const args = ev.args ? safeJson(ev.args, MAX_ARGS_CHARS) : "";
9430
+ const args = ev.args ? safeJson(ev.args, limits.args) : "";
9301
9431
  const detail = title || args;
9302
- return `[tool] ${name}${detail ? `: ${truncateText(detail, MAX_ARGS_CHARS)}` : ""}`;
9432
+ return `[tool] ${name}${detail ? `: ${truncateText(detail, limits.args)}` : ""}`;
9303
9433
  }
9304
9434
  case "service": {
9305
9435
  const text = squash(String(ev.text ?? ""));
9306
- return text.length > 0 ? `[service] ${truncateText(text, MAX_ARGS_CHARS)}` : null;
9436
+ return text.length > 0 ? `[service] ${truncateText(text, limits.args)}` : null;
9307
9437
  }
9308
9438
  case "turn-end": {
9309
9439
  if (ev.status && ev.status !== "completed") return `[turn ${ev.status}]`;
@@ -9318,12 +9448,12 @@ function formatMessageBody(body) {
9318
9448
  switch (d.type) {
9319
9449
  case "message": {
9320
9450
  const text = squash(String(d.message ?? ""));
9321
- return text.length > 0 ? `assistant: ${truncateText(text)}` : null;
9451
+ return text.length > 0 ? `assistant: ${truncateText(text, limits.line)}` : null;
9322
9452
  }
9323
9453
  case "tool-call":
9324
- return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input, MAX_ARGS_CHARS)}`;
9454
+ return `[tool] ${String(d.name ?? "tool")}: ${safeJson(d.input, limits.args)}`;
9325
9455
  case "permission-request":
9326
- return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")), MAX_ARGS_CHARS)}`;
9456
+ return `[permission] ${String(d.toolName ?? "")}: ${truncateText(squash(String(d.description ?? "")), limits.args)}`;
9327
9457
  default:
9328
9458
  return null;
9329
9459
  }
@@ -9334,22 +9464,111 @@ function formatMessageBody(body) {
9334
9464
  const texts = msg.content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => squash(c.text)).filter((t) => t.length > 0);
9335
9465
  if (texts.length > 0) {
9336
9466
  const role = msg.role === "user" ? "user" : "assistant";
9337
- return `${role}: ${truncateText(texts.join(" "))}`;
9467
+ return `${role}: ${truncateText(texts.join(" "), limits.line)}`;
9338
9468
  }
9339
9469
  }
9340
9470
  return null;
9341
9471
  }
9342
9472
  return null;
9343
9473
  }
9344
- function formatTranscript(bodies) {
9474
+ function formatTranscript(bodies, options = {}) {
9475
+ const limits = options.full ? NO_LIMITS : DEFAULT_LIMITS;
9345
9476
  const lines = [];
9346
9477
  for (const body of bodies) {
9347
- const line = formatMessageBody(body);
9478
+ const line = formatMessageBody(body, limits);
9348
9479
  if (line !== null) lines.push(line);
9349
9480
  }
9350
9481
  return lines.join("\n");
9351
9482
  }
9352
9483
 
9484
+ function asRecord(value) {
9485
+ return value && typeof value === "object" ? value : null;
9486
+ }
9487
+ function sessionEnvelope(body) {
9488
+ const b = asRecord(body);
9489
+ if (!b || b.role !== "session") return null;
9490
+ const env = asRecord(b.content);
9491
+ if (!env) return null;
9492
+ const ev = asRecord(env.ev);
9493
+ return ev && typeof ev.t === "string" ? { role: env.role, ev } : null;
9494
+ }
9495
+ function isUserPrompt(body) {
9496
+ const b = asRecord(body);
9497
+ return !!b && b.role === "user" && b.content?.type === "text" && typeof b.content.text === "string";
9498
+ }
9499
+ function agentPiece(body) {
9500
+ const env = sessionEnvelope(body);
9501
+ if (env && env.role !== "user") {
9502
+ if (env.ev.t === "text" && !env.ev.thinking) {
9503
+ const text = String(env.ev.text ?? "");
9504
+ return text.trim().length > 0 ? { kind: "text", text } : null;
9505
+ }
9506
+ if (env.ev.t === "tool-call-start") return { kind: "tool" };
9507
+ return null;
9508
+ }
9509
+ const b = asRecord(body);
9510
+ if (b?.role === "agent" && b.content?.type === "acp") {
9511
+ const d = asRecord(b.content.data);
9512
+ if (d?.type === "message") {
9513
+ const text = String(d.message ?? "");
9514
+ return text.trim().length > 0 ? { kind: "text", text } : null;
9515
+ }
9516
+ if (d?.type === "tool-call") return { kind: "tool" };
9517
+ }
9518
+ return null;
9519
+ }
9520
+ function analyzeLatestTurn(entries) {
9521
+ const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
9522
+ let userIndex = -1;
9523
+ for (let i = entries.length - 1; i >= 0; i--) {
9524
+ if (isUserPrompt(entries[i].body)) {
9525
+ userIndex = i;
9526
+ break;
9527
+ }
9528
+ }
9529
+ let ended = false;
9530
+ let status = null;
9531
+ let error = null;
9532
+ let texts = [];
9533
+ let lastMarker = null;
9534
+ let startedAfterPrompt = false;
9535
+ const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
9536
+ for (let i = userIndex + 1; i < entries.length; i++) {
9537
+ const body = entries[i].body;
9538
+ const env = sessionEnvelope(body);
9539
+ if (env?.ev?.t === "turn-start") {
9540
+ lastMarker = "start";
9541
+ startedAfterPrompt = true;
9542
+ ended = false;
9543
+ status = null;
9544
+ error = null;
9545
+ texts = [];
9546
+ continue;
9547
+ }
9548
+ if (env?.ev?.t === "turn-end") {
9549
+ lastMarker = "end";
9550
+ if (userIndex >= 0 && emitsStarts && !startedAfterPrompt) continue;
9551
+ ended = true;
9552
+ status = typeof env.ev.status === "string" ? env.ev.status : null;
9553
+ error = typeof env.ev.error === "string" ? env.ev.error : null;
9554
+ continue;
9555
+ }
9556
+ const piece = agentPiece(body);
9557
+ if (!piece) continue;
9558
+ if (piece.kind === "tool") texts = [];
9559
+ else texts.push(piece.text);
9560
+ }
9561
+ if (userIndex < 0) ended = lastMarker === "end";
9562
+ return {
9563
+ userSeq: userIndex >= 0 ? entries[userIndex].seq : null,
9564
+ ended,
9565
+ status: ended ? status : null,
9566
+ error: ended ? error : null,
9567
+ answer: texts.join("\n\n").trim(),
9568
+ lastSeq
9569
+ };
9570
+ }
9571
+
9353
9572
  const USER_SOCKET_CONNECT_TIMEOUT_MS = 15e3;
9354
9573
  async function openUserScopedSocket(token, options = {}) {
9355
9574
  const socket = io(configuration.serverUrl, {
@@ -9695,19 +9914,21 @@ async function bearerToken() {
9695
9914
  if (!credentials) throw new Error("CLI is not authenticated (no ~/.happy/access.key)");
9696
9915
  return credentials.token;
9697
9916
  }
9698
- async function readSessionTranscript(sessionId, limit) {
9917
+ function requirePersisted(sessionId) {
9699
9918
  const persisted = readPersistedSessions()[sessionId];
9700
9919
  if (!persisted) {
9701
9920
  throw new Error(
9702
9921
  `No local key for session ${sessionId} \u2014 it was not spawned by this machine's daemon (or is older than 14 days).`
9703
9922
  );
9704
9923
  }
9705
- const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
9924
+ return persisted;
9925
+ }
9926
+ async function readSessionLog(sessionId, persisted, limit) {
9706
9927
  const token = await bearerToken();
9707
9928
  const response = await axios.get(
9708
9929
  `${configuration.serverUrl}/v3/sessions/${encodeURIComponent(sessionId)}/messages`,
9709
9930
  {
9710
- params: { before_seq: 2147483647, limit: bounded },
9931
+ params: { before_seq: 2147483647, limit },
9711
9932
  headers: {
9712
9933
  "Authorization": `Bearer ${token}`,
9713
9934
  "X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
@@ -9718,21 +9939,62 @@ async function readSessionTranscript(sessionId, limit) {
9718
9939
  const messages = Array.isArray(response.data?.messages) ? response.data.messages : [];
9719
9940
  messages.reverse();
9720
9941
  const key = decodeBase64(persisted.encryptionKey);
9721
- const live = await listDaemonSessions();
9722
- const bodies = messages.map((message) => {
9723
- if (message.content?.t !== "encrypted") return null;
9942
+ return messages.map((message) => {
9943
+ if (message.content?.t !== "encrypted") return { seq: message.seq, body: null };
9724
9944
  try {
9725
- return decrypt(key, persisted.encryptionVariant, decodeBase64(message.content.c));
9945
+ return { seq: message.seq, body: decrypt(key, persisted.encryptionVariant, decodeBase64(message.content.c)) };
9726
9946
  } catch {
9727
- return null;
9947
+ return { seq: message.seq, body: null };
9728
9948
  }
9729
9949
  });
9950
+ }
9951
+ const TURN_WINDOW = 500;
9952
+ async function readSessionTranscript(sessionId, limit, options = {}) {
9953
+ const persisted = requirePersisted(sessionId);
9954
+ const bounded = Math.max(1, Math.min(MAX_READ_LIMIT, Math.floor(limit)));
9955
+ const entries = await readSessionLog(sessionId, persisted, Math.max(bounded, TURN_WINDOW));
9956
+ const shown = entries.slice(-bounded);
9957
+ const live = await listDaemonSessions();
9730
9958
  return {
9731
9959
  summary: toSummary(sessionId, persisted, sessionLiveness(live, sessionId)),
9732
- messageCount: messages.length,
9733
- transcript: formatTranscript(bodies)
9960
+ messageCount: shown.length,
9961
+ transcript: formatTranscript(shown.map((entry) => entry.body), { full: options.full }),
9962
+ turn: analyzeLatestTurn(entries)
9734
9963
  };
9735
9964
  }
9965
+ async function readSessionMetadata(sessionId, persisted) {
9966
+ const token = await bearerToken();
9967
+ const response = await axios.get(`${configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
9968
+ headers: {
9969
+ "Authorization": `Bearer ${token}`,
9970
+ "X-Happy-Client": `${SESSION_OPS_CLIENT}/${configuration.currentCliVersion}`
9971
+ },
9972
+ timeout: 15e3
9973
+ });
9974
+ const ciphertext = response.data?.session?.metadata;
9975
+ if (typeof ciphertext !== "string" || ciphertext.length === 0) return null;
9976
+ return decrypt(decodeBase64(persisted.encryptionKey), persisted.encryptionVariant, decodeBase64(ciphertext));
9977
+ }
9978
+ class TurnWaitTimeoutError extends Error {
9979
+ constructor(sessionId, timeoutMs, turn) {
9980
+ super(`Session ${sessionId}: the latest turn did not end within ${Math.round(timeoutMs / 1e3)}s`);
9981
+ this.sessionId = sessionId;
9982
+ this.timeoutMs = timeoutMs;
9983
+ this.turn = turn;
9984
+ }
9985
+ }
9986
+ async function waitForTurnEnd(sessionId, options) {
9987
+ const persisted = requirePersisted(sessionId);
9988
+ const pollMs = options.pollMs ?? 3e3;
9989
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
9990
+ const deadline = Date.now() + options.timeoutMs;
9991
+ while (true) {
9992
+ const turn = analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW));
9993
+ if (turn.ended) return turn;
9994
+ if (Date.now() + pollMs > deadline) throw new TurnWaitTimeoutError(sessionId, options.timeoutMs, turn);
9995
+ await sleep(pollMs);
9996
+ }
9997
+ }
9736
9998
  async function stopSession(sessionId) {
9737
9999
  return await stopDaemonSession(sessionId);
9738
10000
  }
@@ -11433,7 +11695,7 @@ async function retainIntent(path, input) {
11433
11695
  await mkdir(dirname(path), { recursive: true, mode: 448 });
11434
11696
  let file;
11435
11697
  try {
11436
- file = await open$1(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
11698
+ file = await open$1(path, constants$1.O_WRONLY | constants$1.O_CREAT | constants$1.O_EXCL, 384);
11437
11699
  } catch (error) {
11438
11700
  if (error.code !== "EEXIST") throw new Error("Cannot retain team connection request");
11439
11701
  }
@@ -11446,7 +11708,7 @@ async function retainIntent(path, input) {
11446
11708
  }
11447
11709
  return { fresh: true };
11448
11710
  }
11449
- const existing = await open$1(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
11711
+ const existing = await open$1(path, constants$1.O_RDONLY | (constants$1.O_NOFOLLOW ?? 0));
11450
11712
  try {
11451
11713
  const stat = await existing.stat();
11452
11714
  if (!stat.isFile() || process.platform !== "win32" && (stat.mode & 63) !== 0 || typeof process.getuid === "function" && stat.uid !== process.getuid()) throw new Error("Unsafe team connection receipt");
@@ -12470,7 +12732,7 @@ function registerSideQuestionHandler(rpc, deps) {
12470
12732
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
12471
12733
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
12472
12734
  const fork = deps.run ?? (async (input) => {
12473
- const { query } = await import('./index-BPCXUrEe.mjs');
12735
+ const { query } = await import('./index-DYQgcgL2.mjs');
12474
12736
  return runSideQuestion(query, input);
12475
12737
  });
12476
12738
  const run = async (input, live) => {
@@ -17758,7 +18020,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17758
18020
  return;
17759
18021
  } else if (subcommand === "install-terminal-hooks") {
17760
18022
  try {
17761
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-COqkLz6f.mjs');
18023
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-Ch_yTtEA.mjs');
17762
18024
  const command = parseTerminalHooksArgs(args.slice(1));
17763
18025
  if (command.action === "help") {
17764
18026
  console.log(TERMINAL_HOOKS_HELP);
@@ -17775,7 +18037,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17775
18037
  }
17776
18038
  } else if (subcommand === "agent-home") {
17777
18039
  try {
17778
- const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-CKSPsS8d.mjs');
18040
+ const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-CeFDNzvq.mjs');
17779
18041
  await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
17780
18042
  process.exit(0);
17781
18043
  } catch (error) {
@@ -17784,7 +18046,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17784
18046
  }
17785
18047
  } else if (subcommand === "spawn") {
17786
18048
  try {
17787
- const { handleSpawnCommand } = await import('./spawn-7lLymdT1.mjs');
18049
+ const { handleSpawnCommand } = await import('./spawn-DxHIIqhi.mjs');
17788
18050
  await handleSpawnCommand(args.slice(1));
17789
18051
  } catch (error) {
17790
18052
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17796,7 +18058,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17796
18058
  return;
17797
18059
  } else if (subcommand === "sessions") {
17798
18060
  try {
17799
- const { handleSessionsCommand } = await import('./sessions-ClBPEtz2.mjs');
18061
+ const { handleSessionsCommand } = await import('./sessions-DlU3foY_.mjs');
17800
18062
  await handleSessionsCommand(args.slice(1));
17801
18063
  } catch (error) {
17802
18064
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17808,7 +18070,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17808
18070
  return;
17809
18071
  } else if (subcommand === "send") {
17810
18072
  try {
17811
- const { handleSendCommand } = await import('./send-DDD57Cr_.mjs');
18073
+ const { handleSendCommand } = await import('./send-o6WM-1G8.mjs');
17812
18074
  await handleSendCommand(args.slice(1));
17813
18075
  } catch (error) {
17814
18076
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -17914,9 +18176,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17914
18176
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
17915
18177
  const projectId = args[3];
17916
18178
  try {
17917
- const { saveGoogleCloudProjectToConfig } = await import('./config-C8jpk6IA.mjs');
17918
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-BXI1Gzvd.mjs').then(function (n) { return n.Y; });
17919
- const { ApiClient: ApiClient2 } = await import('./types-ipzMqciq.mjs').then(function (n) { return n.am; });
18179
+ const { saveGoogleCloudProjectToConfig } = await import('./config-C-A1XF62.mjs');
18180
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-DS-bnYYp.mjs').then(function (n) { return n.Y; });
18181
+ const { ApiClient: ApiClient2 } = await import('./types-BN3eiipi.mjs').then(function (n) { return n.at; });
17920
18182
  let userEmail = void 0;
17921
18183
  try {
17922
18184
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -17947,7 +18209,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17947
18209
  }
17948
18210
  if (geminiSubcommand === "project" && args[2] === "get") {
17949
18211
  try {
17950
- const { readGeminiLocalConfig } = await import('./config-C8jpk6IA.mjs');
18212
+ const { readGeminiLocalConfig } = await import('./config-C-A1XF62.mjs');
17951
18213
  const config = readGeminiLocalConfig();
17952
18214
  if (config.googleCloudProject) {
17953
18215
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -17987,7 +18249,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17987
18249
  process.exit(0);
17988
18250
  }
17989
18251
  try {
17990
- const { runGemini } = await import('./runGemini-Cce_pjTs.mjs');
18252
+ const { runGemini } = await import('./runGemini-DvJwQVGp.mjs');
17991
18253
  let startedBy = void 0;
17992
18254
  for (let i = 1; i < args.length; i++) {
17993
18255
  if (args[i] === "--started-by") {
@@ -18010,7 +18272,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18010
18272
  } else if (subcommand === "pi") {
18011
18273
  if (args[1] === "--terminal") {
18012
18274
  try {
18013
- const { runPiTerminal } = await import('./piTerminal-Bczubq2n.mjs');
18275
+ const { runPiTerminal } = await import('./piTerminal-M677UP7y.mjs');
18014
18276
  process.exitCode = await runPiTerminal(args.slice(2));
18015
18277
  } catch (error) {
18016
18278
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18027,7 +18289,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18027
18289
  }
18028
18290
  };
18029
18291
  try {
18030
- const { runAcp, resolveAcpAgentConfig } = await import('./index-DrVq0CqK.mjs');
18292
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-DEp6CmmS.mjs');
18031
18293
  const parsed = parsePiRunnerArgs(args.slice(1));
18032
18294
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
18033
18295
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -18055,7 +18317,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18055
18317
  return;
18056
18318
  } else if (subcommand === "acp") {
18057
18319
  try {
18058
- const { runAcp, resolveAcpAgentConfig } = await import('./index-DrVq0CqK.mjs');
18320
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-DEp6CmmS.mjs');
18059
18321
  let startedBy = void 0;
18060
18322
  let verbose = false;
18061
18323
  const acpArgs = [];
@@ -18095,7 +18357,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18095
18357
  return;
18096
18358
  } else if (subcommand === "openclaw") {
18097
18359
  try {
18098
- const { runOpenClaw } = await import('./runOpenClaw-DXP5_chb.mjs');
18360
+ const { runOpenClaw } = await import('./runOpenClaw-BG82bF18.mjs');
18099
18361
  let startedBy = void 0;
18100
18362
  let verbose = false;
18101
18363
  let gatewayUrl;
@@ -18146,7 +18408,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18146
18408
  return;
18147
18409
  } else if (subcommand === "todo") {
18148
18410
  try {
18149
- const { handleTodoCommand } = await import('./todo-jXHGGH89.mjs');
18411
+ const { handleTodoCommand } = await import('./todo-BhJbG5eq.mjs');
18150
18412
  await handleTodoCommand(args.slice(1));
18151
18413
  } catch (error) {
18152
18414
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -18155,7 +18417,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18155
18417
  return;
18156
18418
  } else if (subcommand === "teams") {
18157
18419
  try {
18158
- const { handleTeamsCommand } = await import('./teams-DyBjdXI3.mjs');
18420
+ const { handleTeamsCommand } = await import('./teams-DoAA_CcN.mjs');
18159
18421
  await handleTeamsCommand(args.slice(1));
18160
18422
  } catch (error) {
18161
18423
  console.error(error instanceof Error ? error.message : "Teams command failed");
@@ -18164,7 +18426,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18164
18426
  return;
18165
18427
  } else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
18166
18428
  try {
18167
- const { handleMcpCommand } = await import('./mcp-Dfnoirpi.mjs');
18429
+ const { handleMcpCommand } = await import('./mcp-zu8n8TkR.mjs');
18168
18430
  await handleMcpCommand(args[1] === "--terminal-tools");
18169
18431
  } catch (error) {
18170
18432
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -18532,4 +18794,4 @@ ${chalk.bold("Examples:")}
18532
18794
  }
18533
18795
  }
18534
18796
 
18535
- 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, TEAM_SKILL as J, GEMINI_API_KEY_ENV as K, GOOGLE_API_KEY_ENV as L, MAX_READ_LIMIT as M, BaseReasoningProcessor as N, MessageBuffer as O, PI_TEAMS_EXTENSION as P, CHANGE_TITLE_INSTRUCTION as Q, SPAWN_AGENTS as S, TitleGenerator 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, checkPreviewPath as h, isValidSpawnOrigin as i, registerAssistantSessionTools as j, initialMachineMetadata as k, listAccountSessions as l, createSessionMetadata as m, setupOfflineReconnection as n, StreamRelay as o, MessageQueue2 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, registerKillSessionHandler as y, stageClaudeAttachments as z };
18797
+ export { ALLOWED_SPAWN_PERMISSION_MODES as A, TitleGenerator as B, registerKillSessionHandler as C, DEFAULT_RECENT_LIMIT as D, CLAUDE_ATTACHMENT_KINDS as E, stageClaudeAttachments as F, GEMINI_MODEL_ENV as G, appendStagedAttachmentsToPrompt as H, BasePermissionHandler as I, writeSessionModeFile as J, normalizeAcpPermissionMode as K, removeSessionModeFile as L, MAX_READ_LIMIT as M, TEAM_SKILL as N, GEMINI_API_KEY_ENV as O, PI_TEAMS_EXTENSION as P, GOOGLE_API_KEY_ENV as Q, BaseReasoningProcessor as R, SPAWN_AGENTS as S, TurnWaitTimeoutError as T, MessageBuffer as U, CHANGE_TITLE_INSTRUCTION as V, listSessions as a, resolvePermissionRequest as b, archiveSession as c, sendUserMessage as d, sessionWebUrl as e, DEFAULT_GEMINI_MODEL as f, checkPreviewPath as g, registerAssistantSessionTools as h, readSessionMetadata as i, isValidSpawnOrigin as j, sanitizeSpawnPermissionMode as k, listAccountSessions as l, waitForSessionKey as m, initialMachineMetadata as n, createSessionMetadata as o, setupOfflineReconnection as p, query as q, readSessionTranscript as r, stopSession as s, tmuxSupportsSessionEnv as t, StreamRelay as u, MessageQueue2 as v, waitForTurnEnd as w, hashObject as x, startHappyServer as y, registerAgentAttachmentDownloads as z };