very-happy-cli 0.2.155 → 0.2.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/{AcpBackend-DmFcRh54.cjs → AcpBackend-BHe1_F1h.cjs} +2 -2
  2. package/dist/{AcpBackend-DVB0nzgR.mjs → AcpBackend-C3nc1u-s.mjs} +2 -2
  3. package/dist/{AcpSessionManager-CWaSYTnT.cjs → AcpSessionManager-BVxkQnNf.cjs} +1 -1
  4. package/dist/{AcpSessionManager-dGDgNb-p.mjs → AcpSessionManager-dN3k1LbI.mjs} +1 -1
  5. package/dist/{agentGuidance-un7YPryB.cjs → agentGuidance-C-CmV03f.cjs} +1 -1
  6. package/dist/{agentGuidance-BWxFC6UL.mjs → agentGuidance-C604n0Zq.mjs} +1 -1
  7. package/dist/{agentHome-aucT8BHw.cjs → agentHome-CmIJNV7f.cjs} +2 -2
  8. package/dist/{agentHome-ByLb2HFg.mjs → agentHome-D2XGsoqs.mjs} +2 -2
  9. package/dist/{auto-BQnD6gVx.mjs → auto-B1I6G_Nf.mjs} +6 -6
  10. package/dist/{auto-P9RoJaY0.cjs → auto-CMDq4oIu.cjs} +6 -6
  11. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  12. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  13. package/dist/{config-BoePh9PX.cjs → config-BYHSRY2E.cjs} +4 -4
  14. package/dist/{config-BPZ2Y6o8.mjs → config-BkTu25bH.mjs} +4 -4
  15. package/dist/{index-BjzdXb9x.mjs → index-B5QbzEga.mjs} +8 -8
  16. package/dist/{index-DRrLaljt.cjs → index-B_-7NpGZ.cjs} +7 -7
  17. package/dist/{index-D_fYUxVg.cjs → index-BbtR-Qto.cjs} +114 -51
  18. package/dist/{index--KCpVs1c.cjs → index-CB8QnjvS.cjs} +4 -4
  19. package/dist/{index-B4oXnbYw.mjs → index-DAAreiRF.mjs} +110 -47
  20. package/dist/{index-DVVRqaVG.mjs → index-PYO8Mdz4.mjs} +4 -4
  21. package/dist/index.cjs +4 -4
  22. package/dist/index.mjs +4 -4
  23. package/dist/{install-DP5aPNEX.mjs → install-CNtXzwT_.mjs} +1 -1
  24. package/dist/{install-CGigiocs.cjs → install-CO1ps6Wt.cjs} +1 -1
  25. package/dist/{installTerminalHooks-XrcPvfBs.cjs → installTerminalHooks-BOAnQ1Qe.cjs} +4 -4
  26. package/dist/{installTerminalHooks-tIg7mANc.mjs → installTerminalHooks-CMpOV3RU.mjs} +4 -4
  27. package/dist/lib.cjs +2 -2
  28. package/dist/lib.d.cts +24 -8
  29. package/dist/lib.d.mts +24 -8
  30. package/dist/lib.mjs +2 -2
  31. package/dist/{limits-C_DW0M0q.mjs → limits-DmMdchTz.mjs} +7 -7
  32. package/dist/{limits-DkfHJ3nS.cjs → limits-o29KXBgj.cjs} +7 -7
  33. package/dist/{mcp-o7QglAQM.mjs → mcp-C_uejotl.mjs} +4 -4
  34. package/dist/{mcp-BqlXl10F.cjs → mcp-CwK1nI7Y.cjs} +4 -4
  35. package/dist/{piRuntime-MjiJI-nV.mjs → piRuntime-Cu2-8nOT.mjs} +2 -2
  36. package/dist/{piRuntime-BxP4B_zY.cjs → piRuntime-Dj5WnqzJ.cjs} +2 -2
  37. package/dist/{piTerminal-DYzRcwgb.cjs → piTerminal-D-BIbov9.cjs} +6 -6
  38. package/dist/{piTerminal-Dpq6yqKU.mjs → piTerminal-DanZeBrO.mjs} +6 -6
  39. package/dist/{runGemini-8Pxh94Fm.cjs → runGemini--QqK-Ap5.cjs} +6 -6
  40. package/dist/{runGemini-CDlcQ-OA.mjs → runGemini-CZ_lhMWY.mjs} +6 -6
  41. package/dist/{runOpenClaw-B5FSZIbK.cjs → runOpenClaw-C0xLhTvk.cjs} +5 -5
  42. package/dist/{runOpenClaw-YkhsgPs0.mjs → runOpenClaw-CxgLpm38.mjs} +5 -5
  43. package/dist/{send-DJStmLBv.mjs → send-BaOkDgH0.mjs} +2 -2
  44. package/dist/{send-CwrYXTPr.cjs → send-CTcGMVuG.cjs} +2 -2
  45. package/dist/{sessions-JaVXlSqj.mjs → sessions-Bn304xF1.mjs} +2 -2
  46. package/dist/{sessions-MX21NJXd.cjs → sessions-OmvyJb0n.cjs} +2 -2
  47. package/dist/{spawn-BaPnjClT.mjs → spawn-BKNabutV.mjs} +4 -4
  48. package/dist/{spawn-cIoglKVf.cjs → spawn-CC6V2KiA.cjs} +4 -4
  49. package/dist/{teams-DXV5Pwwt.mjs → teams-CLbS2_4Q.mjs} +5 -5
  50. package/dist/{teams-Dqk7F3D-.cjs → teams-XtrH3_h3.cjs} +5 -5
  51. package/dist/{todo-2Z1REhfx.mjs → todo-BcPbNKt2.mjs} +2 -2
  52. package/dist/{todo-Buuqn9gm.cjs → todo-CEDvKNXK.cjs} +2 -2
  53. package/dist/{types-CkbfdRzq.mjs → types-BEVzYSoV.mjs} +49 -10
  54. package/dist/{types-a2xsH2UI.cjs → types-CphWjqHc.cjs} +49 -10
  55. package/package.json +7 -7
@@ -1,14 +1,14 @@
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 { g as getProjectPath, k as initializeSandbox, w as wrapCommand, j as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, l as AsyncLock, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, m as commandOnPath, n as deriveLocalCliUpdateSummary, o as isClaudeSessionId, q as getLocalHappyAgentCredentialPath, s as readLocalHappyAgentCredentials, t as parseExactVersion, u as compareExactVersions, v as extractCompleteLines, x as decideBackfill, y as parseTerminalHookPayload, z as tmuxArgs, B as scrubTmuxClientEnv, D as decideMirrorBinding, E as MIRROR_BACKFILL_LINES_DEFAULT, G as mirrorLineKey, H as mirrorLocalId, J as detectCLIAvailability, K as detectResumeSupport, L as refreshAgentHomes, d as describeAgentHome, T as TurnActivityTracker, N as summarizeSpawnSessionForLog, O as locateClaudeConversation, P as agentHomeSpawnEnv, Q as shellescape, A as ApiClient, S as fetchCliUpdateState, U as resolveCliUpdateCheckInterval, V as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, W as listVhTerminals, X as readVhTerminal, Y as sendToVhTerminal, Z as TITLE_PROMPT_PREFIX, _ as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, $ as startOfflineReconnection, C as CodexAppServerClient } from './types-CkbfdRzq.mjs';
5
- import { l as logger, a7 as notifyDaemonSessionEvent, x as BUILTIN_TODO_DISCOVERY, g as delay, a8 as STREAM_DELTA_MAX_CHARS, a9 as ensurePrivateDirectory, aa as hardenPrivateFile, c as configuration, ab as notifyDaemonClaudeAuthFailed, F as packageJson, r as readSettings, ac as readCredentials, Z as credentialRelayProblem, k as checkIfDaemonRunningAndCleanupStaleState, ad as readDaemonState, ae as AGENT_VERSION_SOURCES, w as encodeBase64, af as readSettingsOutcome, u as updateSettings, ag as encodeBase64Url, M as decodeBase64, ah as writeCredentialsLegacy, ai as writeCredentialsDataKey, d as readPersistedSessions, E as writePrivateFileSync, aj as decryptWithDataKey, ak as decryptLegacy, al as persistSession, z as ensurePrivateDirectorySync, am as getAgentCodeDefaults, an as normalizeAgentKey, ao as isClaudeOpus55Model, ap as supportsClaudeOpus55, aq as AUTOMATION_SUMMARY_MAX_CHARS, ar as AUTOMATION_CLAIM_MAX_RUNS, as as AUTOMATION_DEFAULT_LEASE_MS, f as isAutomationRunTerminal, at as isDaemonRunningCurrentlyInstalledHappyVersion, au as stopDaemon, av as acquireDaemonLock, aw as deletePersistedSessions, N as decrypt, ax as writeDaemonState, ay as writeSettings, az as cleanupDaemonState, aA as releaseDaemonLock, i as isValidSessionId, m as spawnDaemonSession, a1 as isValidTerminalId, aB as ContextUsageSchema, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, o as CLIPBOARD_MAX_BYTES, v as notifyDaemonSessionStarted, y as reportSessionEditToDaemon, aC as clearCredentials, aD as clearMachineId, h as readCredentialsForConfiguredRelay, aE as SandboxConfigSchema, e as createEnvelope, O as listDaemonSessions, n as stopDaemonSession, aF as getLatestDaemonLog } from './limits-C_DW0M0q.mjs';
4
+ import { g as getProjectPath, k as initializeSandbox, w as wrapCommand, j as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, l as AsyncLock, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, m as commandOnPath, n as deriveLocalCliUpdateSummary, o as isClaudeSessionId, q as getLocalHappyAgentCredentialPath, s as readLocalHappyAgentCredentials, t as parseExactVersion, u as compareExactVersions, v as extractCompleteLines, x as decideBackfill, y as parseTerminalHookPayload, z as tmuxArgs, B as scrubTmuxClientEnv, D as decideMirrorBinding, E as MIRROR_BACKFILL_LINES_DEFAULT, G as mirrorLineKey, H as mirrorLocalId, J as detectCLIAvailability, K as detectResumeSupport, L as refreshAgentHomes, d as describeAgentHome, T as TurnActivityTracker, N as summarizeSpawnSessionForLog, O as locateClaudeConversation, P as agentHomeSpawnEnv, Q as shellescape, A as ApiClient, S as fetchCliUpdateState, U as resolveCliUpdateCheckInterval, V as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, W as listVhTerminals, X as readVhTerminal, Y as sendToVhTerminal, Z as TITLE_PROMPT_PREFIX, _ as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, $ as startOfflineReconnection, C as CodexAppServerClient } from './types-BEVzYSoV.mjs';
5
+ import { l as logger, a7 as notifyDaemonSessionEvent, x as BUILTIN_TODO_DISCOVERY, g as delay, a8 as STREAM_DELTA_MAX_CHARS, a9 as ensurePrivateDirectory, aa as hardenPrivateFile, c as configuration, ab as notifyDaemonClaudeAuthFailed, F as packageJson, r as readSettings, ac as readCredentials, Z as credentialRelayProblem, k as checkIfDaemonRunningAndCleanupStaleState, ad as readDaemonState, ae as AGENT_VERSION_SOURCES, w as encodeBase64, af as readSettingsOutcome, u as updateSettings, ag as encodeBase64Url, M as decodeBase64, ah as writeCredentialsLegacy, ai as writeCredentialsDataKey, d as readPersistedSessions, E as writePrivateFileSync, aj as decryptWithDataKey, ak as decryptLegacy, al as persistSession, z as ensurePrivateDirectorySync, am as getAgentCodeDefaults, an as normalizeAgentKey, ao as isClaudeOpus55Model, ap as supportsClaudeOpus55, aq as AUTOMATION_SUMMARY_MAX_CHARS, ar as AUTOMATION_CLAIM_MAX_RUNS, as as AUTOMATION_DEFAULT_LEASE_MS, f as isAutomationRunTerminal, at as isDaemonRunningCurrentlyInstalledHappyVersion, au as stopDaemon, av as acquireDaemonLock, aw as deletePersistedSessions, N as decrypt, ax as writeDaemonState, ay as writeSettings, az as cleanupDaemonState, aA as releaseDaemonLock, i as isValidSessionId, m as spawnDaemonSession, a1 as isValidTerminalId, aB as ContextUsageSchema, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, o as CLIPBOARD_MAX_BYTES, v as notifyDaemonSessionStarted, y as reportSessionEditToDaemon, aC as clearCredentials, aD as clearMachineId, h as readCredentialsForConfiguredRelay, aE as SandboxConfigSchema, e as createEnvelope, O as listDaemonSessions, n as stopDaemonSession, aF as getLatestDaemonLog } from './limits-DmMdchTz.mjs';
6
6
  import spawn$1, { spawn } from 'cross-spawn';
7
7
  import path, { join, resolve, basename, delimiter, isAbsolute, dirname, sep, normalize } from 'node:path';
8
8
  import { createInterface } from 'node:readline';
9
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 './terminalTitleSuggest-DKgX3osW.mjs';
11
- import { E as trimIdent, F as AGENT_GUIDANCE, G as formatEditConflictNotice, z as sendUserMessage, S as SESSION_PEER_SENT_FROM, x as readSessionMetadata, y as waitForSessionKey, k as archiveSession, H as createAutomationsClient, t as isAutomationsUnavailable, I as AutomationsRequestError, J as analyzeLatestTurn, K as readSessionLog$1, L as TURN_WINDOW, N as userPromptText, v as teamScopePath, O as TeamConnectionPreflightError, C as createTeamsClient, e as listSessions, h as readSessionTranscript, n as deliverToSession, o as sessionWebUrl, i as stopSession, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, Q as TEAM_TOOL_NAMES, U as AUTOMATION_TOOL_NAMES, V as SESSION_PEER_TOOL_NAMES, r as registerTeamsTools, c as registerAutomationTools, d as registerSessionPeerTools, W as createSessionPeerToolExecutor, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, X as REPORT_PROGRESS_TOOL_TITLE, Y as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-BWxFC6UL.mjs';
11
+ import { E as trimIdent, F as AGENT_GUIDANCE, G as formatEditConflictNotice, z as sendUserMessage, S as SESSION_PEER_SENT_FROM, x as readSessionMetadata, y as waitForSessionKey, k as archiveSession, H as createAutomationsClient, t as isAutomationsUnavailable, I as AutomationsRequestError, J as analyzeLatestTurn, K as readSessionLog$1, L as TURN_WINDOW, N as userPromptText, v as teamScopePath, O as TeamConnectionPreflightError, C as createTeamsClient, e as listSessions, h as readSessionTranscript, n as deliverToSession, o as sessionWebUrl, i as stopSession, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, Q as TEAM_TOOL_NAMES, U as AUTOMATION_TOOL_NAMES, V as SESSION_PEER_TOOL_NAMES, r as registerTeamsTools, c as registerAutomationTools, d as registerSessionPeerTools, W as createSessionPeerToolExecutor, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, X as REPORT_PROGRESS_TOOL_TITLE, Y as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-C604n0Zq.mjs';
12
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';
@@ -5380,8 +5380,8 @@ ${typeLabels[type] || type}:`));
5380
5380
  }
5381
5381
  console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
5382
5382
  try {
5383
- const { resolveAgentHomesForCli } = await import('./agentHome-ByLb2HFg.mjs');
5384
- const { describeAgentHome } = await import('./types-CkbfdRzq.mjs').then(function (n) { return n.a0; });
5383
+ const { resolveAgentHomesForCli } = await import('./agentHome-D2XGsoqs.mjs');
5384
+ const { describeAgentHome } = await import('./types-BEVzYSoV.mjs').then(function (n) { return n.a0; });
5385
5385
  const homes = await resolveAgentHomesForCli();
5386
5386
  console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
5387
5387
  console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
@@ -6220,7 +6220,8 @@ function startDaemonControlServer({
6220
6220
  onTerminalHook,
6221
6221
  setTerminalTitle,
6222
6222
  onSessionEdit,
6223
- listPeers
6223
+ listPeers,
6224
+ isTurnActive
6224
6225
  }) {
6225
6226
  return new Promise((resolve) => {
6226
6227
  const app = fastify({
@@ -6304,7 +6305,8 @@ function startDaemonControlServer({
6304
6305
  children: z.array(z.object({
6305
6306
  startedBy: z.string(),
6306
6307
  happySessionId: z.string(),
6307
- pid: z.number()
6308
+ pid: z.number(),
6309
+ turnActive: z.boolean()
6308
6310
  }))
6309
6311
  })
6310
6312
  }
@@ -6316,7 +6318,8 @@ function startDaemonControlServer({
6316
6318
  children: children.filter((child) => child.happySessionId !== void 0).map((child) => ({
6317
6319
  startedBy: child.startedBy,
6318
6320
  happySessionId: child.happySessionId,
6319
- pid: child.pid
6321
+ pid: child.pid,
6322
+ turnActive: isTurnActive ? isTurnActive(child.happySessionId) : false
6320
6323
  }))
6321
6324
  };
6322
6325
  });
@@ -9110,9 +9113,9 @@ function createMirrorManager(deps) {
9110
9113
  archiveReason: reason
9111
9114
  });
9112
9115
  try {
9113
- await deps.api.deactivateSession(binding.happySessionId);
9116
+ await deps.api.archiveSession(binding.happySessionId);
9114
9117
  } catch (error) {
9115
- logger.debug(`[MIRROR] deactivateSession failed for ${binding.happySessionId}:`, error);
9118
+ logger.debug(`[MIRROR] archiveSession failed for ${binding.happySessionId}:`, error);
9116
9119
  }
9117
9120
  deps.onBindingsChanged?.();
9118
9121
  };
@@ -10721,6 +10724,16 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
10721
10724
  function daemonExitCode(source) {
10722
10725
  return source === "exception" ? 1 : 0;
10723
10726
  }
10727
+ const HANDOVER_EXIT_CODE = 75;
10728
+
10729
+ function detectDaemonSupervisor(env, platform = process.platform) {
10730
+ const explicit = env.HAPPY_DAEMON_SUPERVISOR?.trim().toLowerCase();
10731
+ if (explicit === "systemd") return "systemd";
10732
+ if (explicit === "none") return "none";
10733
+ if (platform === "linux" && typeof env.INVOCATION_ID === "string" && env.INVOCATION_ID.trim() !== "") return "systemd";
10734
+ return "none";
10735
+ }
10736
+ const SYSTEMD_RESTART_HINT = "systemctl --user restart very-happy-daemon";
10724
10737
 
10725
10738
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10726
10739
  function sanitizeImportTitle(value) {
@@ -11826,6 +11839,9 @@ async function startDaemon() {
11826
11839
  peerCoordinator.onEdit(edit);
11827
11840
  },
11828
11841
  listPeers: () => peerCoordinator.list(),
11842
+ // B-505: `daemon list` shows which sessions have a turn in flight, so an
11843
+ // operator can check the WHOLE machine before a supervised restart.
11844
+ isTurnActive: (sessionId) => turnActivity.activeSessions().includes(sessionId),
11829
11845
  pushClipboard: (text, terminalId) => {
11830
11846
  if (!apiMachineRef) {
11831
11847
  return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
@@ -11845,6 +11861,8 @@ async function startDaemon() {
11845
11861
  }
11846
11862
  });
11847
11863
  const daemonClaudeCredentials = resolveClaudeCredentialReadiness();
11864
+ const daemonSupervisor = detectDaemonSupervisor(process.env);
11865
+ logger.debug(`[DAEMON RUN] Supervisor: ${daemonSupervisor}`);
11848
11866
  let fileState = {
11849
11867
  pid: process.pid,
11850
11868
  httpPort: controlPort,
@@ -11854,7 +11872,8 @@ async function startDaemon() {
11854
11872
  serverUrl: configuration.serverUrl,
11855
11873
  webappUrl: configuration.webappUrl,
11856
11874
  daemonLogPath: logger.logFilePath,
11857
- claudeCredentialSource: daemonClaudeCredentials.source
11875
+ claudeCredentialSource: daemonClaudeCredentials.source,
11876
+ supervisor: daemonSupervisor
11858
11877
  };
11859
11878
  writeDaemonState(fileState);
11860
11879
  logger.debug("[DAEMON RUN] Daemon state written");
@@ -12027,6 +12046,10 @@ async function startDaemon() {
12027
12046
  await cleanupDaemonState();
12028
12047
  await releaseDaemonLock(daemonLockHandle);
12029
12048
  await stopCaffeinate();
12049
+ if (daemonSupervisor === "systemd") {
12050
+ logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
12051
+ process.exit(HANDOVER_EXIT_CODE);
12052
+ }
12030
12053
  try {
12031
12054
  spawnHappyCLI(["daemon", "start"], {
12032
12055
  detached: true,
@@ -13006,6 +13029,40 @@ function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEve
13006
13029
  };
13007
13030
  }
13008
13031
 
13032
+ async function settleSessionOnExit(sessionId, intent, deps) {
13033
+ if (intent === "archive") {
13034
+ deps.updateMetadata((currentMetadata) => ({
13035
+ ...currentMetadata,
13036
+ lifecycleState: "archived",
13037
+ lifecycleStateSince: Date.now(),
13038
+ archivedBy: "cli",
13039
+ archiveReason: "User terminated"
13040
+ }));
13041
+ }
13042
+ deps.sendSessionDeath();
13043
+ try {
13044
+ if (intent === "archive") await deps.archiveSession(sessionId);
13045
+ else await deps.deactivateSession(sessionId);
13046
+ } catch (error) {
13047
+ deps.log?.(`[START] ${intent === "archive" ? "archiveSession" : "deactivateSession"} during cleanup failed:`, error);
13048
+ }
13049
+ }
13050
+ class SessionExitGate {
13051
+ decided = null;
13052
+ /** Returns the intent to act on, or null when cleanup already started. */
13053
+ claim(intent) {
13054
+ if (this.decided !== null) return null;
13055
+ this.decided = intent;
13056
+ return intent;
13057
+ }
13058
+ get current() {
13059
+ return this.decided;
13060
+ }
13061
+ }
13062
+ function exitIntentFromArchiveFlag(archive) {
13063
+ return archive ?? true ? "archive" : "offline";
13064
+ }
13065
+
13009
13066
  function isUnsupportedSideQuestionError(error) {
13010
13067
  const message = error instanceof Error ? error.message : String(error);
13011
13068
  return /unknown|unsupported|not supported|unrecognized|invalid.*subtype/i.test(message);
@@ -13182,7 +13239,7 @@ function registerSideQuestionHandler(rpc, deps) {
13182
13239
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
13183
13240
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
13184
13241
  const fork = deps.run ?? (async (input) => {
13185
- const { query } = await import('./index-DVVRqaVG.mjs');
13242
+ const { query } = await import('./index-PYO8Mdz4.mjs');
13186
13243
  return runSideQuestion(query, input);
13187
13244
  });
13188
13245
  const run = async (input, live) => {
@@ -14319,26 +14376,24 @@ async function runClaude(credentials, options = {}) {
14319
14376
  hasMetadata: Boolean(message.meta)
14320
14377
  });
14321
14378
  });
14379
+ const exitGate = new SessionExitGate();
14322
14380
  const cleanup = async (opts = { archive: true }) => {
14323
- logger.debug(`[START] Received termination signal, cleaning up (archive=${opts.archive ?? true})...`);
14381
+ const intent = exitGate.claim(exitIntentFromArchiveFlag(opts.archive));
14382
+ if (intent === null) {
14383
+ logger.debug(`[START] Termination (archive=${opts.archive ?? true}) ignored: cleanup already running as ${exitGate.current}`);
14384
+ return;
14385
+ }
14386
+ logger.debug(`[START] Received termination signal, cleaning up (archive=${intent === "archive"})...`);
14324
14387
  try {
14325
14388
  if (session) {
14326
- if (opts.archive ?? true) {
14327
- session.updateMetadata((currentMetadata) => ({
14328
- ...currentMetadata,
14329
- lifecycleState: "archived",
14330
- lifecycleStateSince: Date.now(),
14331
- archivedBy: "cli",
14332
- archiveReason: "User terminated"
14333
- }));
14334
- }
14335
14389
  currentSession?.cleanup();
14336
- session.sendSessionDeath();
14337
- try {
14338
- await api.deactivateSession(session.sessionId);
14339
- } catch (err) {
14340
- logger.debug("[START] deactivateSession during cleanup failed:", err);
14341
- }
14390
+ await settleSessionOnExit(session.sessionId, intent, {
14391
+ updateMetadata: (handler) => session.updateMetadata(handler),
14392
+ sendSessionDeath: () => session.sendSessionDeath(),
14393
+ archiveSession: (id) => api.archiveSession(id),
14394
+ deactivateSession: (id) => api.deactivateSession(id),
14395
+ log: (message, ...args) => logger.debug(message, ...args)
14396
+ });
14342
14397
  await session.flush();
14343
14398
  await session.close();
14344
14399
  }
@@ -18492,7 +18547,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18492
18547
  return;
18493
18548
  } else if (subcommand === "install-terminal-hooks") {
18494
18549
  try {
18495
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-tIg7mANc.mjs');
18550
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-CMpOV3RU.mjs');
18496
18551
  const command = parseTerminalHooksArgs(args.slice(1));
18497
18552
  if (command.action === "help") {
18498
18553
  console.log(TERMINAL_HOOKS_HELP);
@@ -18509,7 +18564,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18509
18564
  }
18510
18565
  } else if (subcommand === "agent-home") {
18511
18566
  try {
18512
- const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-ByLb2HFg.mjs');
18567
+ const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-D2XGsoqs.mjs');
18513
18568
  await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
18514
18569
  process.exit(0);
18515
18570
  } catch (error) {
@@ -18518,7 +18573,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18518
18573
  }
18519
18574
  } else if (subcommand === "spawn") {
18520
18575
  try {
18521
- const { handleSpawnCommand } = await import('./spawn-BaPnjClT.mjs');
18576
+ const { handleSpawnCommand } = await import('./spawn-BKNabutV.mjs');
18522
18577
  await handleSpawnCommand(args.slice(1));
18523
18578
  } catch (error) {
18524
18579
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18530,7 +18585,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18530
18585
  return;
18531
18586
  } else if (subcommand === "sessions") {
18532
18587
  try {
18533
- const { handleSessionsCommand } = await import('./sessions-JaVXlSqj.mjs');
18588
+ const { handleSessionsCommand } = await import('./sessions-Bn304xF1.mjs');
18534
18589
  await handleSessionsCommand(args.slice(1));
18535
18590
  } catch (error) {
18536
18591
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18542,7 +18597,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18542
18597
  return;
18543
18598
  } else if (subcommand === "send") {
18544
18599
  try {
18545
- const { handleSendCommand } = await import('./send-DJStmLBv.mjs');
18600
+ const { handleSendCommand } = await import('./send-BaOkDgH0.mjs');
18546
18601
  await handleSendCommand(args.slice(1));
18547
18602
  } catch (error) {
18548
18603
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18648,9 +18703,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18648
18703
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
18649
18704
  const projectId = args[3];
18650
18705
  try {
18651
- const { saveGoogleCloudProjectToConfig } = await import('./config-BPZ2Y6o8.mjs');
18652
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-C_DW0M0q.mjs').then(function (n) { return n.aG; });
18653
- const { ApiClient: ApiClient2 } = await import('./types-CkbfdRzq.mjs').then(function (n) { return n.a1; });
18706
+ const { saveGoogleCloudProjectToConfig } = await import('./config-BkTu25bH.mjs');
18707
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-DmMdchTz.mjs').then(function (n) { return n.aG; });
18708
+ const { ApiClient: ApiClient2 } = await import('./types-BEVzYSoV.mjs').then(function (n) { return n.a1; });
18654
18709
  let userEmail = void 0;
18655
18710
  try {
18656
18711
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -18681,7 +18736,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18681
18736
  }
18682
18737
  if (geminiSubcommand === "project" && args[2] === "get") {
18683
18738
  try {
18684
- const { readGeminiLocalConfig } = await import('./config-BPZ2Y6o8.mjs');
18739
+ const { readGeminiLocalConfig } = await import('./config-BkTu25bH.mjs');
18685
18740
  const config = readGeminiLocalConfig();
18686
18741
  if (config.googleCloudProject) {
18687
18742
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -18721,7 +18776,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18721
18776
  process.exit(0);
18722
18777
  }
18723
18778
  try {
18724
- const { runGemini } = await import('./runGemini-CDlcQ-OA.mjs');
18779
+ const { runGemini } = await import('./runGemini-CZ_lhMWY.mjs');
18725
18780
  let startedBy = void 0;
18726
18781
  for (let i = 1; i < args.length; i++) {
18727
18782
  if (args[i] === "--started-by") {
@@ -18744,7 +18799,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18744
18799
  } else if (subcommand === "pi") {
18745
18800
  if (args[1] === "--terminal") {
18746
18801
  try {
18747
- const { runPiTerminal } = await import('./piTerminal-Dpq6yqKU.mjs');
18802
+ const { runPiTerminal } = await import('./piTerminal-DanZeBrO.mjs');
18748
18803
  process.exitCode = await runPiTerminal(args.slice(2));
18749
18804
  } catch (error) {
18750
18805
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18761,7 +18816,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18761
18816
  }
18762
18817
  };
18763
18818
  try {
18764
- const { runAcp, resolveAcpAgentConfig } = await import('./index-BjzdXb9x.mjs');
18819
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-B5QbzEga.mjs');
18765
18820
  const parsed = parsePiRunnerArgs(args.slice(1));
18766
18821
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
18767
18822
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -18789,7 +18844,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18789
18844
  return;
18790
18845
  } else if (subcommand === "acp") {
18791
18846
  try {
18792
- const { runAcp, resolveAcpAgentConfig } = await import('./index-BjzdXb9x.mjs');
18847
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-B5QbzEga.mjs');
18793
18848
  let startedBy = void 0;
18794
18849
  let verbose = false;
18795
18850
  const acpArgs = [];
@@ -18829,7 +18884,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18829
18884
  return;
18830
18885
  } else if (subcommand === "openclaw") {
18831
18886
  try {
18832
- const { runOpenClaw } = await import('./runOpenClaw-YkhsgPs0.mjs');
18887
+ const { runOpenClaw } = await import('./runOpenClaw-CxgLpm38.mjs');
18833
18888
  let startedBy = void 0;
18834
18889
  let verbose = false;
18835
18890
  let gatewayUrl;
@@ -18880,7 +18935,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18880
18935
  return;
18881
18936
  } else if (subcommand === "todo") {
18882
18937
  try {
18883
- const { handleTodoCommand } = await import('./todo-2Z1REhfx.mjs');
18938
+ const { handleTodoCommand } = await import('./todo-BcPbNKt2.mjs');
18884
18939
  await handleTodoCommand(args.slice(1));
18885
18940
  } catch (error) {
18886
18941
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -18889,7 +18944,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18889
18944
  return;
18890
18945
  } else if (subcommand === "teams") {
18891
18946
  try {
18892
- const { handleTeamsCommand } = await import('./teams-DXV5Pwwt.mjs');
18947
+ const { handleTeamsCommand } = await import('./teams-CLbS2_4Q.mjs');
18893
18948
  await handleTeamsCommand(args.slice(1));
18894
18949
  } catch (error) {
18895
18950
  console.error(error instanceof Error ? error.message : "Teams command failed");
@@ -18898,7 +18953,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18898
18953
  return;
18899
18954
  } else if (subcommand === "auto") {
18900
18955
  try {
18901
- const { handleAutoCommand } = await import('./auto-BQnD6gVx.mjs');
18956
+ const { handleAutoCommand } = await import('./auto-B1I6G_Nf.mjs');
18902
18957
  await handleAutoCommand(args.slice(1));
18903
18958
  } catch (error) {
18904
18959
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
@@ -18910,7 +18965,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18910
18965
  return;
18911
18966
  } else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
18912
18967
  try {
18913
- const { handleMcpCommand } = await import('./mcp-o7QglAQM.mjs');
18968
+ const { handleMcpCommand } = await import('./mcp-C_uejotl.mjs');
18914
18969
  await handleMcpCommand(args[1] === "--terminal-tools");
18915
18970
  } catch (error) {
18916
18971
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -18939,6 +18994,8 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18939
18994
  } else {
18940
18995
  console.log("Active sessions:");
18941
18996
  console.log(JSON.stringify(sessions, null, 2));
18997
+ const busy = sessions.filter((s) => s.turnActive === true);
18998
+ console.log(busy.length === 0 ? "No turn in flight on this machine (wrappers older than 0.2.139 do not report turns)." : `${busy.length} session(s) with a turn in flight: ${busy.map((s) => s.happySessionId).join(", ")}`);
18942
18999
  }
18943
19000
  } catch (error) {
18944
19001
  console.log("No daemon running");
@@ -18967,13 +19024,19 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18967
19024
  configuration.webappUrl
18968
19025
  );
18969
19026
  if (running && !currentDaemonMatches) {
19027
+ if (currentState?.supervisor === "systemd") {
19028
+ console.error(`The running daemon is supervised by systemd; hand over through the unit instead:
19029
+ ${SYSTEMD_RESTART_HINT}`);
19030
+ process.exit(1);
19031
+ }
18970
19032
  console.log("Configured relay or CLI version changed; restarting the existing daemon");
18971
19033
  await stopDaemon();
18972
19034
  }
19035
+ const { HAPPY_DAEMON_SUPERVISOR: _supervisor, INVOCATION_ID: _invocation, ...detachedEnv } = process.env;
18973
19036
  const child = spawnHappyCLI(["daemon", "start-sync"], {
18974
19037
  detached: true,
18975
19038
  stdio: "ignore",
18976
- env: process.env
19039
+ env: detachedEnv
18977
19040
  });
18978
19041
  child.unref();
18979
19042
  let started = false;
@@ -1,11 +1,11 @@
1
- import { q as query } from './index-B4oXnbYw.mjs';
1
+ import { q as query } from './index-DAAreiRF.mjs';
2
2
  export { AbortError } from '@anthropic-ai/claude-agent-sdk';
3
3
  import 'chalk';
4
4
  import 'node:os';
5
5
  import 'node:crypto';
6
- import './types-CkbfdRzq.mjs';
6
+ import './types-BEVzYSoV.mjs';
7
7
  import 'axios';
8
- import './limits-C_DW0M0q.mjs';
8
+ import './limits-DmMdchTz.mjs';
9
9
  import 'zod';
10
10
  import '@paralleldrive/cuid2';
11
11
  import 'node:fs/promises';
@@ -30,7 +30,7 @@ import 'fs';
30
30
  import 'node:readline';
31
31
  import '@anthropic-ai/sandbox-runtime';
32
32
  import 'expo-server-sdk';
33
- import './agentGuidance-BWxFC6UL.mjs';
33
+ import './agentGuidance-C604n0Zq.mjs';
34
34
  import 'ink';
35
35
  import 'react';
36
36
  import 'ps-list';
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-D_fYUxVg.cjs');
5
- require('./limits-DkfHJ3nS.cjs');
4
+ require('./index-BbtR-Qto.cjs');
5
+ require('./limits-o29KXBgj.cjs');
6
6
  require('zod');
7
- require('./types-a2xsH2UI.cjs');
7
+ require('./types-CphWjqHc.cjs');
8
8
  require('node:child_process');
9
9
  require('node:os');
10
10
  require('node:crypto');
@@ -15,7 +15,7 @@ require('node:fs');
15
15
  require('./terminalTitleSuggest-Dji8I0Jy.cjs');
16
16
  require('path');
17
17
  require('url');
18
- require('./agentGuidance-un7YPryB.cjs');
18
+ require('./agentGuidance-C-CmV03f.cjs');
19
19
  require('node:fs/promises');
20
20
  require('axios');
21
21
  require('socket.io-client');
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import 'chalk';
2
- import './index-B4oXnbYw.mjs';
3
- import './limits-C_DW0M0q.mjs';
2
+ import './index-DAAreiRF.mjs';
3
+ import './limits-DmMdchTz.mjs';
4
4
  import 'zod';
5
- import './types-CkbfdRzq.mjs';
5
+ import './types-BEVzYSoV.mjs';
6
6
  import 'node:child_process';
7
7
  import 'node:os';
8
8
  import 'node:crypto';
@@ -13,7 +13,7 @@ import 'node:fs';
13
13
  import './terminalTitleSuggest-DKgX3osW.mjs';
14
14
  import 'path';
15
15
  import 'url';
16
- import './agentGuidance-BWxFC6UL.mjs';
16
+ import './agentGuidance-C604n0Zq.mjs';
17
17
  import 'node:fs/promises';
18
18
  import 'axios';
19
19
  import 'socket.io-client';
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { unlink, rmdir, mkdir, lstat, readFile, writeFile, rename } from 'node:fs/promises';
3
3
  import { join, resolve, dirname } from 'node:path';
4
- import { T as TEAM_SKILL } from './index-B4oXnbYw.mjs';
4
+ import { T as TEAM_SKILL } from './index-DAAreiRF.mjs';
5
5
 
6
6
  async function assertPrivateDestination(directory) {
7
7
  for (let path = directory; ; path = dirname(path)) {
@@ -3,7 +3,7 @@
3
3
  var node_crypto = require('node:crypto');
4
4
  var promises = require('node:fs/promises');
5
5
  var path = require('node:path');
6
- var index = require('./index-D_fYUxVg.cjs');
6
+ var index = require('./index-BbtR-Qto.cjs');
7
7
 
8
8
  async function assertPrivateDestination(directory) {
9
9
  for (let path$1 = directory; ; path$1 = path.dirname(path$1)) {
@@ -6,14 +6,14 @@ var os = require('node:os');
6
6
  var node_child_process = require('node:child_process');
7
7
  var chalk = require('chalk');
8
8
  var terminalTitleSuggest = require('./terminalTitleSuggest-Dji8I0Jy.cjs');
9
- var index = require('./index-D_fYUxVg.cjs');
9
+ var index = require('./index-BbtR-Qto.cjs');
10
10
  require('path');
11
11
  require('url');
12
12
  require('zod');
13
13
  require('node:crypto');
14
- require('./types-a2xsH2UI.cjs');
14
+ require('./types-CphWjqHc.cjs');
15
15
  require('axios');
16
- require('./limits-DkfHJ3nS.cjs');
16
+ require('./limits-o29KXBgj.cjs');
17
17
  require('@paralleldrive/cuid2');
18
18
  require('node:fs/promises');
19
19
  require('tweetnacl');
@@ -31,7 +31,7 @@ require('fs');
31
31
  require('node:readline');
32
32
  require('@anthropic-ai/sandbox-runtime');
33
33
  require('expo-server-sdk');
34
- require('./agentGuidance-un7YPryB.cjs');
34
+ require('./agentGuidance-C-CmV03f.cjs');
35
35
  require('ink');
36
36
  require('react');
37
37
  require('@anthropic-ai/claude-agent-sdk');
@@ -4,14 +4,14 @@ import { homedir } from 'node:os';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import chalk from 'chalk';
6
6
  import { p as projectPath } from './terminalTitleSuggest-DKgX3osW.mjs';
7
- import { b as tmuxSupportsSessionEnv } from './index-B4oXnbYw.mjs';
7
+ import { b as tmuxSupportsSessionEnv } from './index-DAAreiRF.mjs';
8
8
  import 'path';
9
9
  import 'url';
10
10
  import 'zod';
11
11
  import 'node:crypto';
12
- import './types-CkbfdRzq.mjs';
12
+ import './types-BEVzYSoV.mjs';
13
13
  import 'axios';
14
- import './limits-C_DW0M0q.mjs';
14
+ import './limits-DmMdchTz.mjs';
15
15
  import '@paralleldrive/cuid2';
16
16
  import 'node:fs/promises';
17
17
  import 'tweetnacl';
@@ -29,7 +29,7 @@ import 'fs';
29
29
  import 'node:readline';
30
30
  import '@anthropic-ai/sandbox-runtime';
31
31
  import 'expo-server-sdk';
32
- import './agentGuidance-BWxFC6UL.mjs';
32
+ import './agentGuidance-C604n0Zq.mjs';
33
33
  import 'ink';
34
34
  import 'react';
35
35
  import '@anthropic-ai/claude-agent-sdk';
package/dist/lib.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./types-a2xsH2UI.cjs');
4
- var persistence = require('./limits-DkfHJ3nS.cjs');
3
+ var index = require('./types-CphWjqHc.cjs');
4
+ var persistence = require('./limits-o29KXBgj.cjs');
5
5
  require('axios');
6
6
  require('node:events');
7
7
  require('socket.io-client');
package/dist/lib.d.cts CHANGED
@@ -1648,17 +1648,33 @@ declare class ApiClient {
1648
1648
  */
1649
1649
  getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini'): Promise<any | null>;
1650
1650
  /**
1651
- * Mark a session as inactive on the server (active=false). Does NOT
1652
- * change `lifecycleState`, so the session remains visible in the app
1653
- * and resumable — same effect as the in-app "Archive" button hitting
1654
- * the /archive endpoint, but without the extra metadata.
1651
+ * B-505: mark a session OFFLINE on the server (active=false, `archivedAt`
1652
+ * untouched) — the same row state a missed keepalive produces, so the web
1653
+ * shows it offline and resumable instead of archived.
1655
1654
  *
1656
- * Used during graceful shutdown (Ctrl-C / SIGTERM) as a synchronous
1657
- * fallback for the socket-based session-end signal: even if the
1658
- * socket emit doesn't drain before the process exits, the HTTP
1659
- * response confirms the deactivate landed.
1655
+ * Used when the wrapper exits for an infrastructure reason (SIGTERM /
1656
+ * SIGINT: daemon stop, supervisor restart, machine shutdown) as a
1657
+ * synchronous fallback for the socket `session-end` signal: even if the
1658
+ * socket emit doesn't drain before the process exits, the HTTP response
1659
+ * confirms the row went inactive.
1660
+ *
1661
+ * This MUST NOT hit `/archive`: since B-265 that route writes the
1662
+ * server-owned `archivedAt` tombstone (the only thing that distinguishes
1663
+ * "archived" from "offline"), and the server answers it with a
1664
+ * `session-archive` command that makes the wrapper archive itself. That is
1665
+ * exactly how every dev-sg upgrade archived the sessions mid-turn on
1666
+ * 2026-09-25. A server without `/deactivate` returns 404, which is a
1667
+ * compatible no-op: `session-end` already sets active=false there.
1660
1668
  */
1661
1669
  deactivateSession(sessionId: string): Promise<boolean>;
1670
+ /**
1671
+ * Archive a session on the server: writes the `archivedAt` tombstone and
1672
+ * makes the server tell every wrapper of this session to exit. Only for an
1673
+ * intentional end of life — the user's Archive / kill, a crash the wrapper
1674
+ * cannot recover from, a terminal mirror whose pane went back to a shell.
1675
+ * Never call this from a signal handler; see `deactivateSession`.
1676
+ */
1677
+ archiveSession(sessionId: string): Promise<boolean>;
1662
1678
  /**
1663
1679
  * B-265: one session by id — the message cursor (`seq`), metadata and
1664
1680
  * agent state with their versions, decrypted with the session key the
package/dist/lib.d.mts CHANGED
@@ -1648,17 +1648,33 @@ declare class ApiClient {
1648
1648
  */
1649
1649
  getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini'): Promise<any | null>;
1650
1650
  /**
1651
- * Mark a session as inactive on the server (active=false). Does NOT
1652
- * change `lifecycleState`, so the session remains visible in the app
1653
- * and resumable — same effect as the in-app "Archive" button hitting
1654
- * the /archive endpoint, but without the extra metadata.
1651
+ * B-505: mark a session OFFLINE on the server (active=false, `archivedAt`
1652
+ * untouched) — the same row state a missed keepalive produces, so the web
1653
+ * shows it offline and resumable instead of archived.
1655
1654
  *
1656
- * Used during graceful shutdown (Ctrl-C / SIGTERM) as a synchronous
1657
- * fallback for the socket-based session-end signal: even if the
1658
- * socket emit doesn't drain before the process exits, the HTTP
1659
- * response confirms the deactivate landed.
1655
+ * Used when the wrapper exits for an infrastructure reason (SIGTERM /
1656
+ * SIGINT: daemon stop, supervisor restart, machine shutdown) as a
1657
+ * synchronous fallback for the socket `session-end` signal: even if the
1658
+ * socket emit doesn't drain before the process exits, the HTTP response
1659
+ * confirms the row went inactive.
1660
+ *
1661
+ * This MUST NOT hit `/archive`: since B-265 that route writes the
1662
+ * server-owned `archivedAt` tombstone (the only thing that distinguishes
1663
+ * "archived" from "offline"), and the server answers it with a
1664
+ * `session-archive` command that makes the wrapper archive itself. That is
1665
+ * exactly how every dev-sg upgrade archived the sessions mid-turn on
1666
+ * 2026-09-25. A server without `/deactivate` returns 404, which is a
1667
+ * compatible no-op: `session-end` already sets active=false there.
1660
1668
  */
1661
1669
  deactivateSession(sessionId: string): Promise<boolean>;
1670
+ /**
1671
+ * Archive a session on the server: writes the `archivedAt` tombstone and
1672
+ * makes the server tell every wrapper of this session to exit. Only for an
1673
+ * intentional end of life — the user's Archive / kill, a crash the wrapper
1674
+ * cannot recover from, a terminal mirror whose pane went back to a shell.
1675
+ * Never call this from a signal handler; see `deactivateSession`.
1676
+ */
1677
+ archiveSession(sessionId: string): Promise<boolean>;
1662
1678
  /**
1663
1679
  * B-265: one session by id — the message cursor (`seq`), metadata and
1664
1680
  * agent state with their versions, decrypted with the session key the