very-happy-cli 0.2.93 → 0.2.95

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 (31) hide show
  1. package/dist/{AcpBackend-COv_3umE.cjs → AcpBackend-BXsbwTbl.cjs} +1 -1
  2. package/dist/{AcpBackend-C5507ePk.mjs → AcpBackend-Cuiff4Nu.mjs} +1 -1
  3. package/dist/{AcpSessionManager-Bd1trXWx.mjs → AcpSessionManager-BU69q_vR.mjs} +1 -1
  4. package/dist/{AcpSessionManager-vm4rIcH0.cjs → AcpSessionManager-BXGmMoe8.cjs} +1 -1
  5. package/dist/{config-BJjvra5x.mjs → config-2GD21yg_.mjs} +2 -2
  6. package/dist/{config-SZEQWS8k.cjs → config-BVOE0pni.cjs} +2 -2
  7. package/dist/{index-DaVhaBF2.cjs → index-BzbaMrIa.cjs} +333 -49
  8. package/dist/{index-17NbIAkU.cjs → index-CbPc1iVx.cjs} +4 -4
  9. package/dist/{index-C00jxV83.mjs → index-DVEdpWq0.mjs} +331 -47
  10. package/dist/{index-D-U-7DAy.mjs → index-DZ0uw6ef.mjs} +5 -5
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/{installTerminalHooks-CRjlRj9K.cjs → installTerminalHooks-BrrsyTMd.cjs} +2 -2
  14. package/dist/{installTerminalHooks-DsECLFy3.mjs → installTerminalHooks-FiujjEvb.mjs} +2 -2
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +9 -0
  17. package/dist/lib.d.mts +9 -0
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-Bs6R9P7q.mjs → mcp-0mZcHPtz.mjs} +2 -2
  20. package/dist/{mcp-C-dDCrXm.cjs → mcp-ziyMk3eS.cjs} +2 -2
  21. package/dist/{runGemini-DapsCPLi.cjs → runGemini-C91O1oxi.cjs} +4 -4
  22. package/dist/{runGemini-CrSCxKqI.mjs → runGemini-D_InkOhk.mjs} +4 -4
  23. package/dist/{runOpenClaw-C9dhyV4x.cjs → runOpenClaw-B1RRfA1V.cjs} +3 -3
  24. package/dist/{runOpenClaw-Cnagj-hv.mjs → runOpenClaw-BjebGmV6.mjs} +3 -3
  25. package/dist/{send-DlHES1EO.mjs → send-CxLL0z9J.mjs} +2 -2
  26. package/dist/{send-PmtzSBVM.cjs → send-DgK6MtgT.cjs} +2 -2
  27. package/dist/{spawn-OE9kENQi.cjs → spawn-Dian7x_g.cjs} +2 -2
  28. package/dist/{spawn-DgZUVvT2.mjs → spawn-uTPD3qOt.mjs} +2 -2
  29. package/dist/{types-Cx5wn5Hf.mjs → types-BLE8Hfmh.mjs} +34 -14
  30. package/dist/{types-BHVxYajT.cjs → types-HRuZ13LS.cjs} +34 -14
  31. package/package.json +7 -7
@@ -3,7 +3,7 @@
3
3
  var node_child_process = require('node:child_process');
4
4
  var sdk = require('@agentclientprotocol/sdk');
5
5
  var node_crypto = require('node:crypto');
6
- var persistence = require('./types-BHVxYajT.cjs');
6
+ var persistence = require('./types-HRuZ13LS.cjs');
7
7
 
8
8
  const DEFAULT_TIMEOUTS = {
9
9
  /** Default initialization timeout: 60 seconds */
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { l as logger, d as contentLogMetadata, e as errorLogMetadata, f as packageJson, g as delay } from './types-Cx5wn5Hf.mjs';
4
+ import { l as logger, d as contentLogMetadata, e as errorLogMetadata, f as packageJson, g as delay } from './types-BLE8Hfmh.mjs';
5
5
 
6
6
  const DEFAULT_TIMEOUTS = {
7
7
  /** Default initialization timeout: 60 seconds */
@@ -1,5 +1,5 @@
1
1
  import { createId } from '@paralleldrive/cuid2';
2
- import { b as createEnvelope } from './types-Cx5wn5Hf.mjs';
2
+ import { b as createEnvelope } from './types-BLE8Hfmh.mjs';
3
3
 
4
4
  function turnOptions(turnId, time) {
5
5
  return turnId ? { turn: turnId, time } : { time };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var cuid2 = require('@paralleldrive/cuid2');
4
- var persistence = require('./types-BHVxYajT.cjs');
4
+ var persistence = require('./types-HRuZ13LS.cjs');
5
5
 
6
6
  function turnOptions(turnId, time) {
7
7
  return turnId ? { turn: turnId, time } : { time };
@@ -2,8 +2,8 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { execSync } from 'child_process';
5
- import { l as logger } from './types-Cx5wn5Hf.mjs';
6
- import { D as DEFAULT_GEMINI_MODEL, G as GEMINI_MODEL_ENV } from './index-C00jxV83.mjs';
5
+ import { l as logger } from './types-BLE8Hfmh.mjs';
6
+ import { D as DEFAULT_GEMINI_MODEL, G as GEMINI_MODEL_ENV } from './index-DVEdpWq0.mjs';
7
7
  import 'axios';
8
8
  import 'chalk';
9
9
  import 'node:util';
@@ -4,8 +4,8 @@ var fs = require('fs');
4
4
  var path = require('path');
5
5
  var os = require('os');
6
6
  var child_process = require('child_process');
7
- var persistence = require('./types-BHVxYajT.cjs');
8
- var index = require('./index-DaVhaBF2.cjs');
7
+ var persistence = require('./types-HRuZ13LS.cjs');
8
+ var index = require('./index-BzbaMrIa.cjs');
9
9
  require('axios');
10
10
  require('chalk');
11
11
  require('node:util');
@@ -3,7 +3,7 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var persistence = require('./types-BHVxYajT.cjs');
6
+ var persistence = require('./types-HRuZ13LS.cjs');
7
7
  var spawn = require('cross-spawn');
8
8
  var path = require('node:path');
9
9
  var node_readline = require('node:readline');
@@ -6788,10 +6788,20 @@ function createMirrorScanner(opts) {
6788
6788
  }
6789
6789
 
6790
6790
  const TERMINAL_MIRROR_FLAVOR = "terminal-mirror";
6791
+ const RECONCILE_HYSTERESIS_MS = 2e4;
6791
6792
  function createMirrorManager(deps) {
6792
6793
  const backfillLines = deps.backfillLines ?? persistence.MIRROR_BACKFILL_LINES_DEFAULT;
6793
6794
  const bindings = /* @__PURE__ */ new Map();
6795
+ const lastEndedAt = /* @__PURE__ */ new Map();
6794
6796
  let chain = Promise.resolve();
6797
+ const reactivate = async (binding) => {
6798
+ try {
6799
+ const ok = await deps.api.reactivateSession(binding.happySessionId);
6800
+ binding.needsReactivate = !ok;
6801
+ } catch {
6802
+ binding.needsReactivate = true;
6803
+ }
6804
+ };
6795
6805
  const isTerminalAlive = deps.isTerminalAlive ?? ((terminalId) => {
6796
6806
  try {
6797
6807
  return node_child_process.spawnSync("tmux", persistence.tmuxArgs(["has-session", "-t", `=vh-${terminalId}:`]), { stdio: "ignore", timeout: 3e3, env: persistence.scrubTmuxClientEnv({ ...process.env }) }).status === 0;
@@ -6843,6 +6853,8 @@ function createMirrorManager(deps) {
6843
6853
  const endBinding = async (binding, reason) => {
6844
6854
  if (binding.status === "ended") return;
6845
6855
  binding.status = "ended";
6856
+ lastEndedAt.set(binding.terminalId, Date.now());
6857
+ binding.needsReactivate = false;
6846
6858
  persistence.logger.debug(`[MIRROR] ending binding for terminal ${binding.terminalId} (${reason})`);
6847
6859
  await binding.scanner?.cleanup();
6848
6860
  binding.scanner = null;
@@ -6870,6 +6882,76 @@ function createMirrorManager(deps) {
6870
6882
  }
6871
6883
  await binding.client.close();
6872
6884
  };
6885
+ const adoptPersisted = async (terminalId, persisted) => {
6886
+ if (bindings.has(terminalId)) return;
6887
+ let best = null;
6888
+ for (const [sessionId2, entry2] of Object.entries(persisted)) {
6889
+ const meta2 = entry2.metadata;
6890
+ if (meta2?.flavor !== TERMINAL_MIRROR_FLAVOR) continue;
6891
+ if (meta2.machineId !== deps.machineId) continue;
6892
+ if (meta2.terminalId !== terminalId) continue;
6893
+ if (!meta2.claudeSessionId) continue;
6894
+ if (!best || entry2.savedAt > best.entry.savedAt) best = { sessionId: sessionId2, entry: entry2 };
6895
+ }
6896
+ if (!best) return;
6897
+ const { sessionId, entry } = best;
6898
+ const meta = entry.metadata;
6899
+ const session = {
6900
+ id: sessionId,
6901
+ seq: entry.seq,
6902
+ metadata: meta,
6903
+ metadataVersion: entry.metadataVersion,
6904
+ agentState: null,
6905
+ agentStateVersion: entry.agentStateVersion,
6906
+ encryptionKey: persistence.decodeBase64(entry.encryptionKey),
6907
+ encryptionVariant: entry.encryptionVariant
6908
+ };
6909
+ const client = deps.api.sessionSyncClient(session);
6910
+ client.skipExistingMessages();
6911
+ const binding = {
6912
+ terminalId,
6913
+ happySessionId: sessionId,
6914
+ claudeSessionId: meta.claudeSessionId,
6915
+ status: "active",
6916
+ client,
6917
+ scanner: null,
6918
+ persist: {
6919
+ encryptionKey: entry.encryptionKey,
6920
+ encryptionVariant: entry.encryptionVariant,
6921
+ seq: entry.seq,
6922
+ metadataVersion: entry.metadataVersion,
6923
+ agentStateVersion: entry.agentStateVersion
6924
+ },
6925
+ metadata: meta
6926
+ };
6927
+ bindings.set(terminalId, binding);
6928
+ await reactivate(binding);
6929
+ updateBindingMetadata(binding, {
6930
+ lifecycleState: "running",
6931
+ lifecycleStateSince: Date.now(),
6932
+ archivedBy: void 0,
6933
+ archiveReason: void 0
6934
+ });
6935
+ const scanner = attachScanner(binding, { withTruncationNotice: false });
6936
+ scanner.addFile(path.join(persistence.getProjectPath(meta.path), `${meta.claudeSessionId}.jsonl`), "backfill-tail");
6937
+ persistence.logger.debug(`[MIRROR] reconcile adopted terminal ${terminalId} \u2192 ${sessionId}`);
6938
+ deps.onBindingsChanged?.();
6939
+ };
6940
+ const reactivateInPlace = async (binding) => {
6941
+ if (binding.status !== "ended") return;
6942
+ binding.status = "active";
6943
+ await reactivate(binding);
6944
+ updateBindingMetadata(binding, {
6945
+ lifecycleState: "running",
6946
+ lifecycleStateSince: Date.now(),
6947
+ archivedBy: void 0,
6948
+ archiveReason: void 0
6949
+ });
6950
+ const scanner = binding.scanner ?? attachScanner(binding, { withTruncationNotice: false });
6951
+ scanner.addFile(path.join(persistence.getProjectPath(binding.metadata.path), `${binding.claudeSessionId}.jsonl`), "backfill-tail");
6952
+ persistence.logger.debug(`[MIRROR] reconcile reactivated (in place) terminal ${binding.terminalId}`);
6953
+ deps.onBindingsChanged?.();
6954
+ };
6873
6955
  const createBinding = async (event, replaces) => {
6874
6956
  if (replaces) {
6875
6957
  await teardownBinding(replaces, "superseded by a fresh conversation");
@@ -6930,6 +7012,7 @@ function createMirrorManager(deps) {
6930
7012
  binding.claudeSessionId = event.claudeSessionId;
6931
7013
  if (binding.status === "ended") {
6932
7014
  binding.status = "active";
7015
+ await reactivate(binding);
6933
7016
  updateBindingMetadata(binding, {
6934
7017
  lifecycleState: "running",
6935
7018
  lifecycleStateSince: Date.now(),
@@ -6999,6 +7082,43 @@ function createMirrorManager(deps) {
6999
7082
  }
7000
7083
  }
7001
7084
  },
7085
+ reconcile(list) {
7086
+ let persisted = null;
7087
+ const now = Date.now();
7088
+ for (const item of list) {
7089
+ const binding = bindings.get(item.id) ?? null;
7090
+ if (item.agentState === "shell") {
7091
+ if (binding && binding.status === "active") {
7092
+ chain = chain.then(() => {
7093
+ const b = bindings.get(item.id);
7094
+ return b && b.status === "active" ? endBinding(b, "claude exited (pane observation)") : void 0;
7095
+ }).catch((e) => persistence.logger.debug(`[MIRROR] reconcile end failed for ${item.id}:`, e));
7096
+ }
7097
+ continue;
7098
+ }
7099
+ if (binding && binding.status === "active" && binding.needsReactivate) {
7100
+ chain = chain.then(() => {
7101
+ const b = bindings.get(item.id);
7102
+ return b && b.status === "active" && b.needsReactivate ? reactivate(b) : void 0;
7103
+ }).catch((e) => persistence.logger.debug(`[MIRROR] reconcile reactivate-retry failed for ${item.id}:`, e));
7104
+ }
7105
+ if (!item.claudeConfident) continue;
7106
+ const endedAt = lastEndedAt.get(item.id);
7107
+ if (endedAt !== void 0 && now - endedAt < RECONCILE_HYSTERESIS_MS) continue;
7108
+ if (binding && binding.status === "ended") {
7109
+ chain = chain.then(() => {
7110
+ const b = bindings.get(item.id);
7111
+ return b && b.status === "ended" ? reactivateInPlace(b) : void 0;
7112
+ }).catch((e) => persistence.logger.debug(`[MIRROR] reconcile in-place reactivate failed for ${item.id}:`, e));
7113
+ } else if (!binding) {
7114
+ persisted ??= persistence.readPersistedSessions();
7115
+ const p = persisted;
7116
+ chain = chain.then(() => {
7117
+ return bindings.has(item.id) ? void 0 : adoptPersisted(item.id, p);
7118
+ }).catch((e) => persistence.logger.debug(`[MIRROR] reconcile adopt failed for ${item.id}:`, e));
7119
+ }
7120
+ }
7121
+ },
7002
7122
  resolveMirrorSessionId(terminalId) {
7003
7123
  return bindings.get(terminalId)?.happySessionId;
7004
7124
  },
@@ -7113,6 +7233,144 @@ function recoverableSessionPid(metadata, liveHappyPids) {
7113
7233
  const pid = metadata?.hostPid;
7114
7234
  return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && liveHappyPids.has(pid) ? pid : null;
7115
7235
  }
7236
+ function escapeRegExp(value) {
7237
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7238
+ }
7239
+ function sessionAgentConversationId(metadata) {
7240
+ if (!metadata) return null;
7241
+ if (metadata.flavor === "codex") return metadata.codexThreadId ?? null;
7242
+ return metadata.claudeSessionId ?? metadata.codexThreadId ?? null;
7243
+ }
7244
+ function isDaemonWrapperForConversation(command, conversationId) {
7245
+ if (!command || !conversationId) return false;
7246
+ const startedByDaemon = /(?:^|\s)--started-by\s+daemon(?:\s|$)/.test(command);
7247
+ if (!startedByDaemon) return false;
7248
+ const resume = new RegExp(`(?:^|\\s)--resume(?:\\s+|=)${escapeRegExp(conversationId)}(?:\\s|$)`);
7249
+ return resume.test(command);
7250
+ }
7251
+ function findSessionWrapperPids(metadata, liveHappyProcesses, options = {}) {
7252
+ const out = [];
7253
+ const livePids = new Set(liveHappyProcesses.map((p) => p.pid));
7254
+ const lockPid = options.lockPid ?? null;
7255
+ if (lockPid !== null && lockPid !== options.excludePid && livePids.has(lockPid)) out.push(lockPid);
7256
+ const persisted = recoverableSessionPid(metadata, livePids);
7257
+ if (persisted !== null && persisted !== options.excludePid && !out.includes(persisted)) out.push(persisted);
7258
+ const conversationId = sessionAgentConversationId(metadata);
7259
+ if (conversationId) {
7260
+ for (const proc of liveHappyProcesses) {
7261
+ if (proc.pid === options.excludePid || out.includes(proc.pid)) continue;
7262
+ if (isDaemonWrapperForConversation(proc.command, conversationId)) out.push(proc.pid);
7263
+ }
7264
+ }
7265
+ return out;
7266
+ }
7267
+ function mergeRestoreMetadata(stale, reported) {
7268
+ if (!reported) return stale;
7269
+ return { ...stale, ...processIdentityFields(reported) };
7270
+ }
7271
+
7272
+ const SESSION_LOCK_DIR = "session-locks";
7273
+ const defaultRuntime = () => ({
7274
+ dir: path.join(persistence.configuration.happyHomeDir, SESSION_LOCK_DIR),
7275
+ selfPid: process.pid,
7276
+ now: () => Date.now(),
7277
+ signal: (pid, signal) => process.kill(pid, signal),
7278
+ isAlive: (pid) => {
7279
+ try {
7280
+ process.kill(pid, 0);
7281
+ return true;
7282
+ } catch {
7283
+ return false;
7284
+ }
7285
+ },
7286
+ schedule: (callback, delayMs) => {
7287
+ setTimeout(callback, delayMs);
7288
+ }
7289
+ });
7290
+ function sessionLockPath(happySessionId, dir = path.join(persistence.configuration.happyHomeDir, SESSION_LOCK_DIR)) {
7291
+ return path.join(dir, `${happySessionId}.json`);
7292
+ }
7293
+ function readSessionLock(happySessionId, dir) {
7294
+ try {
7295
+ const path = sessionLockPath(happySessionId, dir);
7296
+ if (!node_fs.existsSync(path)) return null;
7297
+ const parsed = JSON.parse(node_fs.readFileSync(path, "utf-8"));
7298
+ if (typeof parsed?.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
7299
+ return {
7300
+ pid: parsed.pid,
7301
+ startedAt: typeof parsed.startedAt === "number" ? parsed.startedAt : 0,
7302
+ version: typeof parsed.version === "string" ? parsed.version : "",
7303
+ ...typeof parsed.flavor === "string" ? { flavor: parsed.flavor } : {}
7304
+ };
7305
+ } catch {
7306
+ return null;
7307
+ }
7308
+ }
7309
+ function liveSessionLockHolder(happySessionId, runtime = defaultRuntime()) {
7310
+ const record = readSessionLock(happySessionId, runtime.dir);
7311
+ if (!record || record.pid === runtime.selfPid) return null;
7312
+ return runtime.isAlive(record.pid) ? record : null;
7313
+ }
7314
+ function writeSessionLock(happySessionId, record, dir) {
7315
+ node_fs.mkdirSync(dir, { recursive: true, mode: 448 });
7316
+ const path = sessionLockPath(happySessionId, dir);
7317
+ const tmp = `${path}.${record.pid}.tmp`;
7318
+ persistence.writePrivateFileSync(tmp, JSON.stringify(record));
7319
+ node_fs.renameSync(tmp, path);
7320
+ }
7321
+ function acquireSessionLock(happySessionId, options, runtime = defaultRuntime()) {
7322
+ const record = () => ({
7323
+ pid: runtime.selfPid,
7324
+ startedAt: runtime.now(),
7325
+ version: options.version,
7326
+ ...options.flavor ? { flavor: options.flavor } : {}
7327
+ });
7328
+ const holder = liveSessionLockHolder(happySessionId, runtime);
7329
+ if (!holder) {
7330
+ writeSessionLock(happySessionId, record(), runtime.dir);
7331
+ return Promise.resolve({ ok: true, replaced: null });
7332
+ }
7333
+ if (!options.takeover) {
7334
+ return Promise.resolve({ ok: false, holder });
7335
+ }
7336
+ return new Promise((resolve) => {
7337
+ const requested = terminateProcess(holder.pid, (stopped) => {
7338
+ if (!stopped) {
7339
+ resolve({ ok: false, holder });
7340
+ return;
7341
+ }
7342
+ writeSessionLock(happySessionId, record(), runtime.dir);
7343
+ resolve({ ok: true, replaced: holder });
7344
+ }, runtime, options.graceMs ?? 2e3);
7345
+ if (!requested) resolve({ ok: false, holder });
7346
+ });
7347
+ }
7348
+ function releaseSessionLock(happySessionId, runtime = defaultRuntime()) {
7349
+ try {
7350
+ const record = readSessionLock(happySessionId, runtime.dir);
7351
+ if (record && record.pid === runtime.selfPid) {
7352
+ node_fs.unlinkSync(sessionLockPath(happySessionId, runtime.dir));
7353
+ }
7354
+ } catch {
7355
+ }
7356
+ }
7357
+ async function claimSessionOrExit(happySessionId, options) {
7358
+ const result = await acquireSessionLock(happySessionId, {
7359
+ takeover: options.takeover,
7360
+ version: persistence.configuration.currentCliVersion,
7361
+ flavor: options.flavor
7362
+ });
7363
+ if (!result.ok) {
7364
+ const message = `Session ${happySessionId} is already run by very-happy pid ${result.holder.pid} (v${result.holder.version || "?"}); this process yields.`;
7365
+ persistence.logger.debug(`[SESSION LOCK] ${message}`);
7366
+ console.error(message);
7367
+ process.exit(0);
7368
+ }
7369
+ if (result.replaced) {
7370
+ persistence.logger.debug(`[SESSION LOCK] Took over session ${happySessionId} from pid ${result.replaced.pid} (v${result.replaced.version || "?"})`);
7371
+ }
7372
+ process.on("exit", () => releaseSessionLock(happySessionId));
7373
+ }
7116
7374
 
7117
7375
  function shellescape(s) {
7118
7376
  return "'" + s.replace(/'/g, "'\\''") + "'";
@@ -7204,7 +7462,7 @@ async function startDaemon() {
7204
7462
  const pidToTrackedSession = /* @__PURE__ */ new Map();
7205
7463
  const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
7206
7464
  const persisted = persistence.readPersistedSessions();
7207
- const liveHappyPids = new Set((await findAllHappyProcesses()).map((entry) => entry.pid));
7465
+ const liveHappyProcesses = await findAllHappyProcesses();
7208
7466
  for (const [id, s] of Object.entries(persisted)) {
7209
7467
  const tracked = {
7210
7468
  startedBy: "persisted",
@@ -7219,12 +7477,20 @@ async function startDaemon() {
7219
7477
  },
7220
7478
  pid: 0
7221
7479
  };
7222
- const livePid = recoverableSessionPid(s.metadata, liveHappyPids);
7223
- if (livePid !== null) {
7480
+ const livePids = findSessionWrapperPids(s.metadata, liveHappyProcesses, { excludePid: process.pid, lockPid: readSessionLock(id)?.pid });
7481
+ if (livePids.length > 0) {
7482
+ const [livePid, ...duplicates] = livePids;
7224
7483
  tracked.pid = livePid;
7225
7484
  tracked.startedBy = "recovered after daemon restart";
7226
7485
  pidToTrackedSession.set(livePid, tracked);
7227
- persistence.persistSession(id, { ...s, savedAt: Date.now() });
7486
+ if (livePid !== s.metadata?.hostPid) {
7487
+ persistence.logger.debug(`[DAEMON RUN] Session ${id}: persisted hostPid ${s.metadata?.hostPid} is stale; adopted live wrapper ${livePid} by its --resume command line`);
7488
+ }
7489
+ for (const dup of duplicates) {
7490
+ persistence.logger.warn(`[DAEMON RUN] Session ${id} has a duplicate live wrapper ${dup} (kept ${livePid}); restart the session to collapse them`);
7491
+ pidToTrackedSession.set(dup, { ...tracked, pid: dup, startedBy: "recovered after daemon restart (duplicate wrapper)" });
7492
+ }
7493
+ persistence.persistSession(id, { ...s, metadata: { ...s.metadata, hostPid: livePid }, savedAt: Date.now() });
7228
7494
  } else {
7229
7495
  sessionIdToFinishedSession.set(id, tracked);
7230
7496
  }
@@ -7254,6 +7520,11 @@ async function startDaemon() {
7254
7520
  savedAt: Date.now()
7255
7521
  });
7256
7522
  }
7523
+ for (const [otherPid, other] of pidToTrackedSession.entries()) {
7524
+ if (other.happySessionId === sessionId && otherPid !== pid && isPidAlive(otherPid)) {
7525
+ persistence.logger.warn(`[DAEMON RUN] Session ${sessionId} now has two live wrappers (${otherPid} and ${pid}); restart the session to collapse them`);
7526
+ }
7527
+ }
7257
7528
  const existingSession = pidToTrackedSession.get(pid);
7258
7529
  if (existingSession && existingSession.startedBy === "daemon") {
7259
7530
  existingSession.happySessionId = sessionId;
@@ -7681,6 +7952,21 @@ async function startDaemon() {
7681
7952
  }
7682
7953
  return sessionIdToFinishedSession.get(happySessionId);
7683
7954
  };
7955
+ const persistRestoreRecord = (happySessionId, fallback, metadata) => {
7956
+ const candidates = [...pidToTrackedSession.values()].filter((s) => s.happySessionId === happySessionId && isPidAlive(s.pid));
7957
+ const live = candidates.find((s) => s.childProcess) ?? candidates[0];
7958
+ const encryption = live?.encryption ?? fallback.encryption;
7959
+ if (!encryption) return;
7960
+ persistence.persistSession(happySessionId, {
7961
+ encryptionKey: persistence.encodeBase64(encryption.encryptionKey),
7962
+ encryptionVariant: encryption.encryptionVariant,
7963
+ seq: encryption.seq,
7964
+ metadataVersion: encryption.metadataVersion,
7965
+ agentStateVersion: encryption.agentStateVersion,
7966
+ metadata: mergeRestoreMetadata(metadata, live?.happySessionMetadataFromLocalWebhook),
7967
+ savedAt: Date.now()
7968
+ });
7969
+ };
7684
7970
  const fetchServerSessionMetadata = async (sessionId, encryptionKey, encryptionVariant) => {
7685
7971
  try {
7686
7972
  const byId = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
@@ -7737,6 +8023,14 @@ async function startDaemon() {
7737
8023
  if (!tracked) {
7738
8024
  return { type: "error", errorMessage: `resume-precheck:not-tracked: Session ${happySessionId} is not tracked by this daemon. It may have been started before the daemon, more than 14 days ago, or on another machine.` };
7739
8025
  }
8026
+ const orphans = findSessionWrapperPids(tracked.happySessionMetadataFromLocalWebhook, await findAllHappyProcesses(), { excludePid: process.pid, lockPid: readSessionLock(happySessionId)?.pid }).filter((pid) => isPidAlive(pid) && !pidToTrackedSession.has(pid));
8027
+ if (orphans.length > 0) {
8028
+ for (const pid of orphans) {
8029
+ pidToTrackedSession.set(pid, { ...tracked, pid, startedBy: "adopted untracked wrapper" });
8030
+ }
8031
+ persistence.logger.debug(`[DAEMON RUN] resume ${happySessionId}: adopted untracked live wrapper(s) ${orphans.join(", ")} \u2014 idempotent success`);
8032
+ return { type: "success", sessionId: happySessionId };
8033
+ }
7740
8034
  if (!tracked.happySessionMetadataFromLocalWebhook) {
7741
8035
  return { type: "error", errorMessage: `resume-precheck:no-metadata: Session ${happySessionId} has no metadata. Cannot resume.` };
7742
8036
  }
@@ -7784,15 +8078,7 @@ async function startDaemon() {
7784
8078
  }
7785
8079
  });
7786
8080
  if (result.type === "success") {
7787
- persistence.persistSession(happySessionId, {
7788
- encryptionKey: persistence.encodeBase64(tracked.encryption.encryptionKey),
7789
- encryptionVariant: tracked.encryption.encryptionVariant,
7790
- seq: tracked.encryption.seq,
7791
- metadataVersion: tracked.encryption.metadataVersion,
7792
- agentStateVersion: tracked.encryption.agentStateVersion,
7793
- metadata,
7794
- savedAt: Date.now()
7795
- });
8081
+ persistRestoreRecord(happySessionId, tracked, metadata);
7796
8082
  }
7797
8083
  return result;
7798
8084
  } catch (error) {
@@ -7805,17 +8091,18 @@ async function startDaemon() {
7805
8091
  }
7806
8092
  };
7807
8093
  const restartCounts = /* @__PURE__ */ new Map();
7808
- const stopSessionAndWait = (happySessionId, timeoutMs = 8e3) => {
7809
- let targetPid = null;
7810
- for (const [pid2, s] of pidToTrackedSession.entries()) {
7811
- if (s.happySessionId === happySessionId && isPidAlive(pid2)) {
7812
- targetPid = pid2;
7813
- break;
7814
- }
7815
- }
7816
- if (targetPid === null) return Promise.resolve();
7817
- const pid = targetPid;
7818
- return new Promise((resolve) => {
8094
+ const stopSessionAndWait = async (happySessionId, timeoutMs = 8e3) => {
8095
+ const targets = /* @__PURE__ */ new Set();
8096
+ for (const [pid, s] of pidToTrackedSession.entries()) {
8097
+ if (s.happySessionId === happySessionId && isPidAlive(pid)) targets.add(pid);
8098
+ }
8099
+ const known = findTrackedSessionById(happySessionId)?.happySessionMetadataFromLocalWebhook;
8100
+ for (const pid of findSessionWrapperPids(known, await findAllHappyProcesses(), { excludePid: process.pid, lockPid: readSessionLock(happySessionId)?.pid })) {
8101
+ if (isPidAlive(pid)) targets.add(pid);
8102
+ }
8103
+ if (targets.size === 0) return;
8104
+ persistence.logger.debug(`[DAEMON RUN] restart ${happySessionId}: stopping live wrapper(s) ${[...targets].join(", ")}`);
8105
+ await Promise.all([...targets].map((pid) => new Promise((resolve) => {
7819
8106
  let settled = false;
7820
8107
  const done = () => {
7821
8108
  if (!settled) {
@@ -7835,7 +8122,7 @@ async function startDaemon() {
7835
8122
  });
7836
8123
  if (!requested) done();
7837
8124
  setTimeout(done, timeoutMs).unref?.();
7838
- });
8125
+ })));
7839
8126
  };
7840
8127
  const restartSession = async (happySessionId, options) => {
7841
8128
  let gate = resumeGates.get(happySessionId);
@@ -7917,15 +8204,7 @@ async function startDaemon() {
7917
8204
  }
7918
8205
  });
7919
8206
  if (result.type === "success") {
7920
- persistence.persistSession(happySessionId, {
7921
- encryptionKey: persistence.encodeBase64(tracked.encryption.encryptionKey),
7922
- encryptionVariant: tracked.encryption.encryptionVariant,
7923
- seq: tracked.encryption.seq,
7924
- metadataVersion: tracked.encryption.metadataVersion,
7925
- agentStateVersion: tracked.encryption.agentStateVersion,
7926
- metadata,
7927
- savedAt: Date.now()
7928
- });
8207
+ persistRestoreRecord(happySessionId, tracked, metadata);
7929
8208
  }
7930
8209
  return result;
7931
8210
  } catch (error) {
@@ -8096,6 +8375,7 @@ async function startDaemon() {
8096
8375
  resolveMirrorSessionId: (terminalId) => mirrorManager.resolveMirrorSessionId(terminalId),
8097
8376
  onTerminalClosed: (terminalId) => mirrorManager.onTerminalClosed(terminalId),
8098
8377
  onTerminalList: (terminals) => mirrorManager.observeTerminalList(terminals),
8378
+ onTerminalListTick: (terminals) => mirrorManager.reconcile(terminals),
8099
8379
  isMirrorInputAllowed: (terminalId) => mirrorManager.isMirrorInputAllowed(terminalId)
8100
8380
  });
8101
8381
  mirrorManager.restore().catch((error) => {
@@ -9156,7 +9436,7 @@ const MAX_TITLE_CHARS = 60;
9156
9436
  const GENERATION_TIMEOUT_MS = 3e4;
9157
9437
  function resolveClaudeBinary() {
9158
9438
  try {
9159
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DaVhaBF2.cjs', document.baseURI).href)));
9439
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BzbaMrIa.cjs', document.baseURI).href)));
9160
9440
  const utilsPath = path.resolve(path.join(persistence.projectPath(), "scripts", "claude_version_utils.cjs"));
9161
9441
  const { getClaudeCliPath } = require$1(utilsPath);
9162
9442
  const path$1 = getClaudeCliPath();
@@ -9693,6 +9973,7 @@ async function runClaude(credentials, options = {}) {
9693
9973
  process.exit(0);
9694
9974
  }
9695
9975
  persistence.logger.debug(`Session created: ${response.id}`);
9976
+ await claimSessionOrExit(response.id, { takeover: !!reconnectSessionId, flavor: "claude" });
9696
9977
  if (reconnectSessionId && !await api.reactivateSession(response.id)) {
9697
9978
  throw new Error(`Failed to reactivate archived session ${response.id}`);
9698
9979
  }
@@ -11350,9 +11631,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
11350
11631
  };
11351
11632
  }
11352
11633
 
11353
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DaVhaBF2.cjs', document.baseURI).href)));
11634
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BzbaMrIa.cjs', document.baseURI).href)));
11354
11635
  const __dirname$1 = path.dirname(__filename$1);
11355
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DaVhaBF2.cjs', document.baseURI).href)));
11636
+ const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BzbaMrIa.cjs', document.baseURI).href)));
11356
11637
  const PRISMA_QUERY_ENGINE_FILES = {
11357
11638
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
11358
11639
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -13207,6 +13488,9 @@ async function runCodex(opts) {
13207
13488
  } else {
13208
13489
  response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
13209
13490
  }
13491
+ if (response) {
13492
+ await claimSessionOrExit(response.id, { takeover: !!reconnectSessionId, flavor: "codex" });
13493
+ }
13210
13494
  if (reconnectSessionId && response && !await api.reactivateSession(response.id)) {
13211
13495
  throw new Error(`Failed to reactivate archived session ${response.id}`);
13212
13496
  }
@@ -13973,7 +14257,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13973
14257
  process.exit(0);
13974
14258
  } else if (subcommand === "install-terminal-hooks") {
13975
14259
  try {
13976
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-CRjlRj9K.cjs'); });
14260
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-BrrsyTMd.cjs'); });
13977
14261
  const command = parseTerminalHooksArgs(args.slice(1));
13978
14262
  if (command.action === "help") {
13979
14263
  console.log(TERMINAL_HOOKS_HELP);
@@ -13990,7 +14274,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13990
14274
  }
13991
14275
  } else if (subcommand === "spawn") {
13992
14276
  try {
13993
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-OE9kENQi.cjs'); });
14277
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-Dian7x_g.cjs'); });
13994
14278
  await handleSpawnCommand(args.slice(1));
13995
14279
  } catch (error) {
13996
14280
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -14002,7 +14286,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14002
14286
  return;
14003
14287
  } else if (subcommand === "send") {
14004
14288
  try {
14005
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-PmtzSBVM.cjs'); });
14289
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-DgK6MtgT.cjs'); });
14006
14290
  await handleSendCommand(args.slice(1));
14007
14291
  } catch (error) {
14008
14292
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -14108,9 +14392,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14108
14392
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
14109
14393
  const projectId = args[3];
14110
14394
  try {
14111
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-SZEQWS8k.cjs'); });
14112
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-BHVxYajT.cjs'); }).then(function (n) { return n.persistence; });
14113
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-BHVxYajT.cjs'); }).then(function (n) { return n.api; });
14395
+ const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-BVOE0pni.cjs'); });
14396
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-HRuZ13LS.cjs'); }).then(function (n) { return n.persistence; });
14397
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-HRuZ13LS.cjs'); }).then(function (n) { return n.api; });
14114
14398
  let userEmail = void 0;
14115
14399
  try {
14116
14400
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -14141,7 +14425,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14141
14425
  }
14142
14426
  if (geminiSubcommand === "project" && args[2] === "get") {
14143
14427
  try {
14144
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-SZEQWS8k.cjs'); });
14428
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-BVOE0pni.cjs'); });
14145
14429
  const config = readGeminiLocalConfig();
14146
14430
  if (config.googleCloudProject) {
14147
14431
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -14181,7 +14465,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14181
14465
  process.exit(0);
14182
14466
  }
14183
14467
  try {
14184
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DapsCPLi.cjs'); });
14468
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-C91O1oxi.cjs'); });
14185
14469
  let startedBy = void 0;
14186
14470
  for (let i = 1; i < args.length; i++) {
14187
14471
  if (args[i] === "--started-by") {
@@ -14203,7 +14487,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14203
14487
  return;
14204
14488
  } else if (subcommand === "acp") {
14205
14489
  try {
14206
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-17NbIAkU.cjs'); });
14490
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-CbPc1iVx.cjs'); });
14207
14491
  let startedBy = void 0;
14208
14492
  let verbose = false;
14209
14493
  const acpArgs = [];
@@ -14243,7 +14527,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14243
14527
  return;
14244
14528
  } else if (subcommand === "openclaw") {
14245
14529
  try {
14246
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-C9dhyV4x.cjs'); });
14530
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-B1RRfA1V.cjs'); });
14247
14531
  let startedBy = void 0;
14248
14532
  let verbose = false;
14249
14533
  let gatewayUrl;
@@ -14294,7 +14578,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14294
14578
  return;
14295
14579
  } else if (subcommand === "mcp" && args.length === 1) {
14296
14580
  try {
14297
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-C-dDCrXm.cjs'); });
14581
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-ziyMk3eS.cjs'); });
14298
14582
  await handleMcpCommand();
14299
14583
  } catch (error) {
14300
14584
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}