very-happy-cli 0.2.91 → 0.2.93

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-B5RRlzLU.mjs → AcpBackend-C5507ePk.mjs} +1 -1
  2. package/dist/{AcpBackend-GqveWg8u.cjs → AcpBackend-COv_3umE.cjs} +1 -1
  3. package/dist/{AcpSessionManager-Ij1pI5Ex.mjs → AcpSessionManager-Bd1trXWx.mjs} +1 -1
  4. package/dist/{AcpSessionManager-YcrSZwvq.cjs → AcpSessionManager-vm4rIcH0.cjs} +1 -1
  5. package/dist/{config-ra8iKxdU.mjs → config-BJjvra5x.mjs} +2 -2
  6. package/dist/{config-C4NCw7Dz.cjs → config-SZEQWS8k.cjs} +2 -2
  7. package/dist/{index-C1HIm7NY.cjs → index-17NbIAkU.cjs} +4 -4
  8. package/dist/{index-UCYD5Wk3.mjs → index-C00jxV83.mjs} +337 -35
  9. package/dist/{index-CB5H7YI6.mjs → index-D-U-7DAy.mjs} +5 -5
  10. package/dist/{index-FkXJNwiy.cjs → index-DaVhaBF2.cjs} +353 -51
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/{installTerminalHooks-CoK5wNcA.cjs → installTerminalHooks-CRjlRj9K.cjs} +2 -2
  14. package/dist/{installTerminalHooks-TkkGzzbI.mjs → installTerminalHooks-DsECLFy3.mjs} +2 -2
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +69 -3
  17. package/dist/lib.d.mts +69 -3
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-C3MiqObi.mjs → mcp-Bs6R9P7q.mjs} +2 -2
  20. package/dist/{mcp-DcRvswVI.cjs → mcp-C-dDCrXm.cjs} +2 -2
  21. package/dist/{runGemini-BbC0uwUj.mjs → runGemini-CrSCxKqI.mjs} +4 -4
  22. package/dist/{runGemini-Dnvd2gWq.cjs → runGemini-DapsCPLi.cjs} +4 -4
  23. package/dist/{runOpenClaw-CnuTBNQW.cjs → runOpenClaw-C9dhyV4x.cjs} +3 -3
  24. package/dist/{runOpenClaw--nbqTW3K.mjs → runOpenClaw-Cnagj-hv.mjs} +3 -3
  25. package/dist/{send-9nJxn09Y.mjs → send-DlHES1EO.mjs} +2 -2
  26. package/dist/{send-DkKOPatx.cjs → send-PmtzSBVM.cjs} +2 -2
  27. package/dist/{spawn-CRW4ioms.mjs → spawn-DgZUVvT2.mjs} +2 -2
  28. package/dist/{spawn-DE7WhS8Y.cjs → spawn-OE9kENQi.cjs} +2 -2
  29. package/dist/{types-ClDADuxP.cjs → types-BHVxYajT.cjs} +332 -88
  30. package/dist/{types-BKyjG23_.mjs → types-Cx5wn5Hf.mjs} +331 -88
  31. package/package.json +7 -7
@@ -1,10 +1,10 @@
1
- import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-B5RRlzLU.mjs';
2
- export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-B5RRlzLU.mjs';
3
- import { h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, p as projectPath } from './types-BKyjG23_.mjs';
4
- import { A as AcpSessionManager } from './AcpSessionManager-Ij1pI5Ex.mjs';
1
+ import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-C5507ePk.mjs';
2
+ export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-C5507ePk.mjs';
3
+ import { h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, p as projectPath } from './types-Cx5wn5Hf.mjs';
4
+ import { A as AcpSessionManager } from './AcpSessionManager-Bd1trXWx.mjs';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { join } from 'node:path';
7
- import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, f as startHappyServer, r as registerKillSessionHandler, B as BasePermissionHandler } from './index-UCYD5Wk3.mjs';
7
+ import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, f as startHappyServer, r as registerKillSessionHandler, B as BasePermissionHandler } from './index-C00jxV83.mjs';
8
8
  import 'node:child_process';
9
9
  import '@agentclientprotocol/sdk';
10
10
  import 'axios';
@@ -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-ClDADuxP.cjs');
6
+ var persistence = require('./types-BHVxYajT.cjs');
7
7
  var spawn = require('cross-spawn');
8
8
  var path = require('node:path');
9
9
  var node_readline = require('node:readline');
@@ -46,24 +46,74 @@ var promises$1 = require('node:readline/promises');
46
46
 
47
47
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
48
48
  function _interopNamespaceDefault(e) {
49
- var n = Object.create(null);
50
- if (e) {
51
- Object.keys(e).forEach(function (k) {
52
- if (k !== 'default') {
53
- var d = Object.getOwnPropertyDescriptor(e, k);
54
- Object.defineProperty(n, k, d.get ? d : {
55
- enumerable: true,
56
- get: function () { return e[k]; }
49
+ var n = Object.create(null);
50
+ if (e) {
51
+ Object.keys(e).forEach(function (k) {
52
+ if (k !== 'default') {
53
+ var d = Object.getOwnPropertyDescriptor(e, k);
54
+ Object.defineProperty(n, k, d.get ? d : {
55
+ enumerable: true,
56
+ get: function () { return e[k]; }
57
+ });
58
+ }
57
59
  });
58
- }
59
- });
60
- }
61
- n.default = e;
62
- return Object.freeze(n);
60
+ }
61
+ n.default = e;
62
+ return Object.freeze(n);
63
63
  }
64
64
 
65
65
  var tmp__namespace = /*#__PURE__*/_interopNamespaceDefault(tmp);
66
66
 
67
+ const PROCESS_IDENTITY_FIELDS = [
68
+ "hostPid",
69
+ "version",
70
+ "startedBy",
71
+ "startedFromDaemon",
72
+ "happyHomeDir",
73
+ "happyLibDir",
74
+ "happyToolsDir",
75
+ "capabilities",
76
+ "attachmentKinds",
77
+ "queueCancellation",
78
+ "sandbox",
79
+ "dangerouslySkipPermissions",
80
+ "os",
81
+ "host",
82
+ "homeDir",
83
+ "permissionMode"
84
+ ];
85
+ function processIdentityFields(local) {
86
+ const out = {};
87
+ for (const key of PROCESS_IDENTITY_FIELDS) {
88
+ const value = local[key];
89
+ if (value !== void 0) out[key] = value;
90
+ }
91
+ return out;
92
+ }
93
+ function mergeReconnectMetadata(server, local, now) {
94
+ return {
95
+ ...server,
96
+ ...processIdentityFields(local),
97
+ lifecycleState: "running",
98
+ lifecycleStateSince: now,
99
+ archivedBy: void 0,
100
+ archiveReason: void 0
101
+ };
102
+ }
103
+ function applyServerSnapshot(response, snapshot) {
104
+ return {
105
+ ...response,
106
+ seq: snapshot.seq,
107
+ metadata: snapshot.metadata,
108
+ metadataVersion: snapshot.metadataVersion,
109
+ agentState: snapshot.agentState,
110
+ agentStateVersion: snapshot.agentStateVersion
111
+ };
112
+ }
113
+ function withoutServerSnapshot(response) {
114
+ return { ...response, metadataVersion: 0, agentStateVersion: 0 };
115
+ }
116
+
67
117
  function daemonControlHeaders(controlToken) {
68
118
  return {
69
119
  "Content-Type": "application/json",
@@ -1570,6 +1620,9 @@ function query(params) {
1570
1620
  if (opts?.mcpServers && Object.keys(opts.mcpServers).length > 0) {
1571
1621
  ensureLocalProxyBypass(env);
1572
1622
  }
1623
+ if (sdkOptions.allowDangerouslySkipPermissions && typeof process.getuid === "function" && process.getuid() === 0 && env.IS_SANDBOX === void 0) {
1624
+ env.IS_SANDBOX = "1";
1625
+ }
1573
1626
  sdkOptions.env = env;
1574
1627
  if (opts?.canCallTool) {
1575
1628
  const callback = opts.canCallTool;
@@ -5315,6 +5368,45 @@ function sanitizeSpawnPermissionMode(value) {
5315
5368
  return ALLOWED_SPAWN_PERMISSION_MODES.includes(value) ? value : null;
5316
5369
  }
5317
5370
 
5371
+ function resumePrecheck(metadata, fs) {
5372
+ const flavor = metadata.flavor ?? "claude";
5373
+ if (flavor !== "claude" && flavor !== "codex") {
5374
+ return { ok: false, reason: "unsupported-flavor", detail: `flavor "${flavor}" cannot be resumed` };
5375
+ }
5376
+ if (!metadata.path || !fs.cwdExists(metadata.path)) {
5377
+ return { ok: false, reason: "missing-cwd", detail: `working directory ${metadata.path ?? "(none)"} does not exist` };
5378
+ }
5379
+ if (flavor === "codex") {
5380
+ if (!metadata.codexThreadId) return { ok: false, reason: "no-backend-id", detail: "session has no Codex thread id" };
5381
+ return { ok: true };
5382
+ }
5383
+ if (!persistence.isClaudeSessionId(metadata.claudeSessionId)) {
5384
+ return { ok: false, reason: "no-backend-id", detail: "session has no Claude session id" };
5385
+ }
5386
+ if (!fs.conversationExists(metadata.path, metadata.claudeSessionId)) {
5387
+ return { ok: false, reason: "conversation-missing", detail: `conversation ${metadata.claudeSessionId} is not on disk` };
5388
+ }
5389
+ return { ok: true };
5390
+ }
5391
+ function sanitizeResumeModel(value) {
5392
+ if (typeof value !== "string") return null;
5393
+ return /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(value) ? value : null;
5394
+ }
5395
+
5396
+ const DEFAULT_MAX_RESTARTS = 3;
5397
+ function decideRestart(priorAttempts, max = DEFAULT_MAX_RESTARTS) {
5398
+ if (priorAttempts >= max) {
5399
+ return {
5400
+ allowed: false,
5401
+ reason: `restart-limit: this session has already been restarted ${priorAttempts} time(s) this daemon session (max ${max}). Start a new session instead.`
5402
+ };
5403
+ }
5404
+ return { allowed: true, attempt: priorAttempts + 1 };
5405
+ }
5406
+ function recordRestartAttempt(priorAttempts) {
5407
+ return priorAttempts + 1;
5408
+ }
5409
+
5318
5410
  const CONTROL_TOKEN_BYTES = 32;
5319
5411
  function createDaemonControlToken() {
5320
5412
  return node_crypto.randomBytes(CONTROL_TOKEN_BYTES).toString("base64url");
@@ -7132,6 +7224,7 @@ async function startDaemon() {
7132
7224
  tracked.pid = livePid;
7133
7225
  tracked.startedBy = "recovered after daemon restart";
7134
7226
  pidToTrackedSession.set(livePid, tracked);
7227
+ persistence.persistSession(id, { ...s, savedAt: Date.now() });
7135
7228
  } else {
7136
7229
  sessionIdToFinishedSession.set(id, tracked);
7137
7230
  }
@@ -7560,6 +7653,13 @@ async function startDaemon() {
7560
7653
  const timeout = setTimeout(() => {
7561
7654
  pidToAwaiter.delete(happyProcess.pid);
7562
7655
  persistence.logger.debug(`[DAEMON RUN] Session webhook timeout for PID ${happyProcess.pid}`);
7656
+ if (happyProcess.pid) {
7657
+ try {
7658
+ terminateProcess(happyProcess.pid, () => {
7659
+ });
7660
+ } catch {
7661
+ }
7662
+ }
7563
7663
  resolve({
7564
7664
  type: "error",
7565
7665
  errorMessage: `Session webhook timeout for PID ${happyProcess.pid}`
@@ -7582,6 +7682,19 @@ async function startDaemon() {
7582
7682
  return sessionIdToFinishedSession.get(happySessionId);
7583
7683
  };
7584
7684
  const fetchServerSessionMetadata = async (sessionId, encryptionKey, encryptionVariant) => {
7685
+ try {
7686
+ const byId = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
7687
+ headers: { Authorization: `Bearer ${credentials.token}` },
7688
+ timeout: 1e4,
7689
+ validateStatus: (status) => status >= 200 && status < 300 || status === 404
7690
+ });
7691
+ if (byId.status !== 404) {
7692
+ const row = byId.data.session;
7693
+ if (row?.metadata) return persistence.decrypt(encryptionKey, encryptionVariant, persistence.decodeBase64(row.metadata));
7694
+ }
7695
+ } catch (error) {
7696
+ persistence.logger.debug(`[DAEMON RUN] by-id session metadata fetch failed, falling back to list: ${error instanceof Error ? error.message : error}`);
7697
+ }
7585
7698
  try {
7586
7699
  const response = await axios.get(`${persistence.configuration.serverUrl}/v1/sessions`, {
7587
7700
  headers: { Authorization: `Bearer ${credentials.token}` },
@@ -7597,17 +7710,38 @@ async function startDaemon() {
7597
7710
  return null;
7598
7711
  }
7599
7712
  };
7713
+ const resumeGates = /* @__PURE__ */ new Map();
7600
7714
  const resumeSession = async (happySessionId, options) => {
7715
+ let gate = resumeGates.get(happySessionId);
7716
+ if (!gate) {
7717
+ gate = createSpawnGate();
7718
+ resumeGates.set(happySessionId, gate);
7719
+ }
7720
+ try {
7721
+ return await gate.join(() => resumeSessionImpl(happySessionId, options));
7722
+ } finally {
7723
+ if (!gate.inFlight()) resumeGates.delete(happySessionId);
7724
+ }
7725
+ };
7726
+ const resumeSessionImpl = async (happySessionId, options) => {
7601
7727
  try {
7728
+ for (const [pid, live] of pidToTrackedSession.entries()) {
7729
+ if (live.happySessionId !== happySessionId) continue;
7730
+ if (isPidAlive(pid)) {
7731
+ persistence.logger.debug(`[DAEMON RUN] resume ${happySessionId}: process ${pid} already alive \u2014 idempotent success`);
7732
+ return { type: "success", sessionId: happySessionId };
7733
+ }
7734
+ onChildExited(pid);
7735
+ }
7602
7736
  const tracked = findTrackedSessionById(happySessionId);
7603
7737
  if (!tracked) {
7604
- return { type: "error", errorMessage: `Session ${happySessionId} is not tracked by this daemon. It may have been started before the daemon or on another machine.` };
7738
+ 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.` };
7605
7739
  }
7606
7740
  if (!tracked.happySessionMetadataFromLocalWebhook) {
7607
- return { type: "error", errorMessage: `Session ${happySessionId} has no metadata. Cannot resume.` };
7741
+ return { type: "error", errorMessage: `resume-precheck:no-metadata: Session ${happySessionId} has no metadata. Cannot resume.` };
7608
7742
  }
7609
7743
  if (!tracked.encryption) {
7610
- return { type: "error", errorMessage: `Session ${happySessionId} has no stored encryption data. It was likely started before this feature was available. Restart the daemon and start a new session to enable resume.` };
7744
+ return { type: "error", errorMessage: `resume-precheck:no-encryption: Session ${happySessionId} has no stored encryption data. It was likely started before this feature was available. Restart the daemon and start a new session to enable resume.` };
7611
7745
  }
7612
7746
  let metadata = tracked.happySessionMetadataFromLocalWebhook;
7613
7747
  const needsFetch = !metadata.claudeSessionId && (!metadata.flavor || metadata.flavor === "claude") || !metadata.codexThreadId && metadata.flavor === "codex";
@@ -7619,18 +7753,24 @@ async function startDaemon() {
7619
7753
  tracked.happySessionMetadataFromLocalWebhook = serverMetadata;
7620
7754
  }
7621
7755
  }
7756
+ const precheck = resumePrecheck(metadata, {
7757
+ cwdExists: (p) => fs$1.existsSync(p),
7758
+ conversationExists: (cwd, claudeSessionId) => fs$1.existsSync(path$1.join(persistence.getProjectPath(cwd), `${claudeSessionId}.jsonl`))
7759
+ });
7760
+ if (!precheck.ok) {
7761
+ return { type: "error", errorMessage: `resume-precheck:${precheck.reason}: ${precheck.detail}` };
7762
+ }
7622
7763
  const launch = buildResumeLaunch(
7623
7764
  { id: happySessionId, active: true, metadata },
7624
7765
  { startedBy: "daemon", claudeStartingMode: "remote" }
7625
7766
  );
7626
- if (options?.model) {
7627
- launch.args.push("--model", options.model);
7628
- }
7629
- if (options?.permissionMode) {
7630
- launch.args.push("--permission-mode", options.permissionMode);
7631
- }
7632
- await fs.access(launch.cwd);
7633
- return spawnTrackedHappyProcess({
7767
+ const model = sanitizeResumeModel(options?.model);
7768
+ if (model) launch.args.push("--model", model);
7769
+ else if (options?.model !== void 0) persistence.logger.debug(`[DAEMON RUN] resume: ignoring invalid model ${JSON.stringify(options.model)}`);
7770
+ const permissionMode = sanitizeSpawnPermissionMode(options?.permissionMode);
7771
+ if (permissionMode) launch.args.push("--permission-mode", permissionMode);
7772
+ else if (options?.permissionMode !== void 0) persistence.logger.debug(`[DAEMON RUN] resume: ignoring invalid permission mode ${JSON.stringify(options.permissionMode)}`);
7773
+ const result = await spawnTrackedHappyProcess({
7634
7774
  args: launch.args,
7635
7775
  cwd: launch.cwd,
7636
7776
  env: {
@@ -7643,6 +7783,18 @@ async function startDaemon() {
7643
7783
  HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
7644
7784
  }
7645
7785
  });
7786
+ 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
+ });
7796
+ }
7797
+ return result;
7646
7798
  } catch (error) {
7647
7799
  const errorMessage = error instanceof Error ? error.message : error && typeof error === "object" ? JSON.stringify(error) : String(error);
7648
7800
  persistence.logger.debug(`[DAEMON RUN] Failed to resume session: ${errorMessage}`, error instanceof Error ? error.stack : void 0);
@@ -7652,6 +7804,136 @@ async function startDaemon() {
7652
7804
  };
7653
7805
  }
7654
7806
  };
7807
+ 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) => {
7819
+ let settled = false;
7820
+ const done = () => {
7821
+ if (!settled) {
7822
+ settled = true;
7823
+ resolve();
7824
+ }
7825
+ };
7826
+ const requested = terminateProcess(pid, () => {
7827
+ const tracked = pidToTrackedSession.get(pid);
7828
+ if (tracked) {
7829
+ if (tracked.happySessionId && tracked.encryption) {
7830
+ sessionIdToFinishedSession.set(tracked.happySessionId, tracked);
7831
+ }
7832
+ pidToTrackedSession.delete(pid);
7833
+ }
7834
+ done();
7835
+ });
7836
+ if (!requested) done();
7837
+ setTimeout(done, timeoutMs).unref?.();
7838
+ });
7839
+ };
7840
+ const restartSession = async (happySessionId, options) => {
7841
+ let gate = resumeGates.get(happySessionId);
7842
+ if (!gate) {
7843
+ gate = createSpawnGate();
7844
+ resumeGates.set(happySessionId, gate);
7845
+ }
7846
+ try {
7847
+ return await gate.replace(() => restartSessionImpl(happySessionId, options));
7848
+ } finally {
7849
+ if (!gate.inFlight()) resumeGates.delete(happySessionId);
7850
+ }
7851
+ };
7852
+ const restartSessionImpl = async (happySessionId, options) => {
7853
+ try {
7854
+ const prior = restartCounts.get(happySessionId) ?? 0;
7855
+ const decision = decideRestart(prior, DEFAULT_MAX_RESTARTS);
7856
+ if (!decision.allowed) {
7857
+ return { type: "error", errorMessage: `restart:${decision.reason}` };
7858
+ }
7859
+ await stopSessionAndWait(happySessionId);
7860
+ const tracked = findTrackedSessionById(happySessionId);
7861
+ if (!tracked) {
7862
+ return { type: "error", errorMessage: `restart-precheck:not-tracked: Session ${happySessionId} is not tracked by this daemon.` };
7863
+ }
7864
+ if (!tracked.happySessionMetadataFromLocalWebhook) {
7865
+ return { type: "error", errorMessage: `restart-precheck:no-metadata: Session ${happySessionId} has no metadata. Cannot restart.` };
7866
+ }
7867
+ if (!tracked.encryption) {
7868
+ return { type: "error", errorMessage: `restart-precheck:no-encryption: Session ${happySessionId} has no stored encryption data. Restart the daemon and start a new session.` };
7869
+ }
7870
+ let metadata = tracked.happySessionMetadataFromLocalWebhook;
7871
+ const needsFetch = !metadata.claudeSessionId && (!metadata.flavor || metadata.flavor === "claude") || !metadata.codexThreadId && metadata.flavor === "codex";
7872
+ if (needsFetch) {
7873
+ const serverMetadata = await fetchServerSessionMetadata(happySessionId, tracked.encryption.encryptionKey, tracked.encryption.encryptionVariant);
7874
+ if (serverMetadata) {
7875
+ metadata = serverMetadata;
7876
+ tracked.happySessionMetadataFromLocalWebhook = serverMetadata;
7877
+ }
7878
+ }
7879
+ const precheck = resumePrecheck(metadata, {
7880
+ cwdExists: (p) => fs$1.existsSync(p),
7881
+ conversationExists: (cwd2, claudeSessionId) => fs$1.existsSync(path$1.join(persistence.getProjectPath(cwd2), `${claudeSessionId}.jsonl`))
7882
+ });
7883
+ let args;
7884
+ let cwd;
7885
+ if (precheck.ok) {
7886
+ const launch = buildResumeLaunch(
7887
+ { id: happySessionId, active: true, metadata },
7888
+ { startedBy: "daemon", claudeStartingMode: "remote" }
7889
+ );
7890
+ args = launch.args;
7891
+ cwd = launch.cwd;
7892
+ } else if ((metadata.flavor ?? "claude") === "claude" && typeof metadata.path === "string" && metadata.path.length > 0) {
7893
+ cwd = metadata.path;
7894
+ if (!fs$1.existsSync(cwd)) {
7895
+ return { type: "error", errorMessage: `restart-precheck:cwd-missing: ${cwd}` };
7896
+ }
7897
+ args = ["claude", "--happy-starting-mode", "remote", "--started-by", "daemon"];
7898
+ } else {
7899
+ return { type: "error", errorMessage: `restart-precheck:${precheck.ok ? "unknown" : precheck.reason}: ${precheck.ok ? "" : precheck.detail}` };
7900
+ }
7901
+ const model = sanitizeResumeModel(options?.model);
7902
+ if (model) args.push("--model", model);
7903
+ const permissionMode = sanitizeSpawnPermissionMode(options?.permissionMode);
7904
+ if (permissionMode) args.push("--permission-mode", permissionMode);
7905
+ restartCounts.set(happySessionId, recordRestartAttempt(prior));
7906
+ const result = await spawnTrackedHappyProcess({
7907
+ args,
7908
+ cwd,
7909
+ env: {
7910
+ ...process.env,
7911
+ HAPPY_RECONNECT_SESSION_ID: happySessionId,
7912
+ HAPPY_RECONNECT_ENCRYPTION_KEY: persistence.encodeBase64(tracked.encryption.encryptionKey),
7913
+ HAPPY_RECONNECT_ENCRYPTION_VARIANT: tracked.encryption.encryptionVariant,
7914
+ HAPPY_RECONNECT_SEQ: String(tracked.encryption.seq),
7915
+ HAPPY_RECONNECT_METADATA_VERSION: String(tracked.encryption.metadataVersion),
7916
+ HAPPY_RECONNECT_AGENT_STATE_VERSION: String(tracked.encryption.agentStateVersion)
7917
+ }
7918
+ });
7919
+ 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
+ });
7929
+ }
7930
+ return result;
7931
+ } catch (error) {
7932
+ const errorMessage = error instanceof Error ? error.message : String(error);
7933
+ persistence.logger.debug(`[DAEMON RUN] Failed to restart session: ${errorMessage}`, error instanceof Error ? error.stack : void 0);
7934
+ return { type: "error", errorMessage: `Failed to restart session: ${errorMessage}` };
7935
+ }
7936
+ };
7655
7937
  const stopSession = (sessionId) => {
7656
7938
  persistence.logger.debug(`[DAEMON RUN] Attempting to stop session ${sessionId}`);
7657
7939
  for (const [pid, session] of pidToTrackedSession.entries()) {
@@ -7822,6 +8104,7 @@ async function startDaemon() {
7822
8104
  apiMachine.setRPCHandlers({
7823
8105
  spawnSession,
7824
8106
  resumeSession,
8107
+ restartSession,
7825
8108
  stopSession,
7826
8109
  listTrackedSessionIds: () => [...pidToTrackedSession.values()].map((session) => session.happySessionId).filter((sessionId) => typeof sessionId === "string"),
7827
8110
  requestShutdown: () => requestShutdown("happy-app")
@@ -8873,7 +9156,7 @@ const MAX_TITLE_CHARS = 60;
8873
9156
  const GENERATION_TIMEOUT_MS = 3e4;
8874
9157
  function resolveClaudeBinary() {
8875
9158
  try {
8876
- 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-FkXJNwiy.cjs', document.baseURI).href)));
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)));
8877
9160
  const utilsPath = path.resolve(path.join(persistence.projectPath(), "scripts", "claude_version_utils.cjs"));
8878
9161
  const { getClaudeCliPath } = require$1(utilsPath);
8879
9162
  const path$1 = getClaudeCliPath();
@@ -9413,6 +9696,18 @@ async function runClaude(credentials, options = {}) {
9413
9696
  if (reconnectSessionId && !await api.reactivateSession(response.id)) {
9414
9697
  throw new Error(`Failed to reactivate archived session ${response.id}`);
9415
9698
  }
9699
+ let reconnectSeeded = false;
9700
+ if (reconnectSessionId) {
9701
+ const snapshot = await api.getSession(response.id, response.encryptionKey, response.encryptionVariant);
9702
+ if (snapshot.ok) {
9703
+ response = applyServerSnapshot(response, snapshot.session);
9704
+ reconnectSeeded = true;
9705
+ persistence.logger.debug(`[START] Reconnect seeded from server: seq=${snapshot.session.seq} metadataVersion=${snapshot.session.metadataVersion}`);
9706
+ } else {
9707
+ response = withoutServerSnapshot(response);
9708
+ persistence.logger.debug(`[START] Reconnect without server snapshot (${snapshot.reason}); skipping existing messages`);
9709
+ }
9710
+ }
9416
9711
  try {
9417
9712
  persistence.logger.debug(`[START] Reporting session ${response.id} to daemon`);
9418
9713
  const result = await notifyDaemonSessionStarted(response.id, metadata, {
@@ -9430,14 +9725,12 @@ async function runClaude(credentials, options = {}) {
9430
9725
  } catch (error) {
9431
9726
  persistence.logger.debug("[START] Failed to report to daemon (may not be running):", error);
9432
9727
  }
9433
- const session = api.sessionSyncClient(response);
9728
+ const session = api.sessionSyncClient(response, reconnectSeeded ? { initialSeq: response.seq } : void 0);
9434
9729
  if (reconnectSessionId) {
9435
9730
  session.suppressNextArchiveSignal();
9436
- session.skipExistingMessages();
9731
+ if (!reconnectSeeded) session.skipExistingMessages();
9437
9732
  session.updateMetadata((meta) => ({
9438
- ...meta,
9439
- lifecycleState: "running",
9440
- archivedBy: void 0,
9733
+ ...mergeReconnectMetadata(meta, metadata, Date.now()),
9441
9734
  queueCancellation: true,
9442
9735
  // B-262 batch 2 (B5): a reconnected process re-publishes the mode
9443
9736
  // IT enforces; the server copy may still carry the previous
@@ -11057,9 +11350,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
11057
11350
  };
11058
11351
  }
11059
11352
 
11060
- 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-FkXJNwiy.cjs', document.baseURI).href)));
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)));
11061
11354
  const __dirname$1 = path.dirname(__filename$1);
11062
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-FkXJNwiy.cjs', document.baseURI).href)));
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)));
11063
11356
  const PRISMA_QUERY_ENGINE_FILES = {
11064
11357
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
11065
11358
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -12148,7 +12441,7 @@ function setupOfflineReconnection(opts) {
12148
12441
  });
12149
12442
  return { session, reconnectionHandle, isOffline: true };
12150
12443
  } else {
12151
- session = api.sessionSyncClient(response);
12444
+ session = api.sessionSyncClient(response, opts.sessionClientOptions);
12152
12445
  return { session, reconnectionHandle: null, isOffline: false };
12153
12446
  }
12154
12447
  }
@@ -12917,6 +13210,18 @@ async function runCodex(opts) {
12917
13210
  if (reconnectSessionId && response && !await api.reactivateSession(response.id)) {
12918
13211
  throw new Error(`Failed to reactivate archived session ${response.id}`);
12919
13212
  }
13213
+ let reconnectSeeded = false;
13214
+ if (reconnectSessionId && response) {
13215
+ const snapshot = await api.getSession(response.id, response.encryptionKey, response.encryptionVariant);
13216
+ if (snapshot.ok) {
13217
+ response = applyServerSnapshot(response, snapshot.session);
13218
+ reconnectSeeded = true;
13219
+ persistence.logger.debug(`[START] Reconnect seeded from server: seq=${snapshot.session.seq} metadataVersion=${snapshot.session.metadataVersion}`);
13220
+ } else {
13221
+ response = withoutServerSnapshot(response);
13222
+ persistence.logger.debug(`[START] Reconnect without server snapshot (${snapshot.reason}); skipping existing messages`);
13223
+ }
13224
+ }
12920
13225
  let session;
12921
13226
  let permissionHandler;
12922
13227
  let client;
@@ -12928,6 +13233,7 @@ async function runCodex(opts) {
12928
13233
  metadata,
12929
13234
  state,
12930
13235
  response,
13236
+ sessionClientOptions: reconnectSeeded && response ? { initialSeq: response.seq } : void 0,
12931
13237
  onSessionSwap: (newSession) => {
12932
13238
  session = newSession;
12933
13239
  if (permissionHandler) {
@@ -12938,12 +13244,8 @@ async function runCodex(opts) {
12938
13244
  session = initialSession;
12939
13245
  if (reconnectSessionId) {
12940
13246
  session.suppressNextArchiveSignal();
12941
- session.skipExistingMessages();
12942
- session.updateMetadata((meta) => ({
12943
- ...meta,
12944
- lifecycleState: "running",
12945
- archivedBy: void 0
12946
- }));
13247
+ if (!reconnectSeeded) session.skipExistingMessages();
13248
+ session.updateMetadata((meta) => mergeReconnectMetadata(meta, metadata, Date.now()));
12947
13249
  }
12948
13250
  if (response) {
12949
13251
  try {
@@ -13671,7 +13973,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13671
13973
  process.exit(0);
13672
13974
  } else if (subcommand === "install-terminal-hooks") {
13673
13975
  try {
13674
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-CoK5wNcA.cjs'); });
13976
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-CRjlRj9K.cjs'); });
13675
13977
  const command = parseTerminalHooksArgs(args.slice(1));
13676
13978
  if (command.action === "help") {
13677
13979
  console.log(TERMINAL_HOOKS_HELP);
@@ -13688,7 +13990,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13688
13990
  }
13689
13991
  } else if (subcommand === "spawn") {
13690
13992
  try {
13691
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-DE7WhS8Y.cjs'); });
13993
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-OE9kENQi.cjs'); });
13692
13994
  await handleSpawnCommand(args.slice(1));
13693
13995
  } catch (error) {
13694
13996
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -13700,7 +14002,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13700
14002
  return;
13701
14003
  } else if (subcommand === "send") {
13702
14004
  try {
13703
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-DkKOPatx.cjs'); });
14005
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-PmtzSBVM.cjs'); });
13704
14006
  await handleSendCommand(args.slice(1));
13705
14007
  } catch (error) {
13706
14008
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -13806,9 +14108,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13806
14108
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
13807
14109
  const projectId = args[3];
13808
14110
  try {
13809
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-C4NCw7Dz.cjs'); });
13810
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-ClDADuxP.cjs'); }).then(function (n) { return n.persistence; });
13811
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-ClDADuxP.cjs'); }).then(function (n) { return n.api; });
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; });
13812
14114
  let userEmail = void 0;
13813
14115
  try {
13814
14116
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -13839,7 +14141,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13839
14141
  }
13840
14142
  if (geminiSubcommand === "project" && args[2] === "get") {
13841
14143
  try {
13842
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-C4NCw7Dz.cjs'); });
14144
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-SZEQWS8k.cjs'); });
13843
14145
  const config = readGeminiLocalConfig();
13844
14146
  if (config.googleCloudProject) {
13845
14147
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -13879,7 +14181,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13879
14181
  process.exit(0);
13880
14182
  }
13881
14183
  try {
13882
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-Dnvd2gWq.cjs'); });
14184
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DapsCPLi.cjs'); });
13883
14185
  let startedBy = void 0;
13884
14186
  for (let i = 1; i < args.length; i++) {
13885
14187
  if (args[i] === "--started-by") {
@@ -13901,7 +14203,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13901
14203
  return;
13902
14204
  } else if (subcommand === "acp") {
13903
14205
  try {
13904
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-C1HIm7NY.cjs'); });
14206
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-17NbIAkU.cjs'); });
13905
14207
  let startedBy = void 0;
13906
14208
  let verbose = false;
13907
14209
  const acpArgs = [];
@@ -13941,7 +14243,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13941
14243
  return;
13942
14244
  } else if (subcommand === "openclaw") {
13943
14245
  try {
13944
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-CnuTBNQW.cjs'); });
14246
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-C9dhyV4x.cjs'); });
13945
14247
  let startedBy = void 0;
13946
14248
  let verbose = false;
13947
14249
  let gatewayUrl;
@@ -13992,7 +14294,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13992
14294
  return;
13993
14295
  } else if (subcommand === "mcp" && args.length === 1) {
13994
14296
  try {
13995
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-DcRvswVI.cjs'); });
14297
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-C-dDCrXm.cjs'); });
13996
14298
  await handleMcpCommand();
13997
14299
  } catch (error) {
13998
14300
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-FkXJNwiy.cjs');
5
- require('./types-ClDADuxP.cjs');
4
+ require('./index-DaVhaBF2.cjs');
5
+ require('./types-BHVxYajT.cjs');
6
6
  require('zod');
7
7
  require('node:child_process');
8
8
  require('node:os');
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import 'chalk';
2
- import './index-UCYD5Wk3.mjs';
3
- import './types-BKyjG23_.mjs';
2
+ import './index-C00jxV83.mjs';
3
+ import './types-Cx5wn5Hf.mjs';
4
4
  import 'zod';
5
5
  import 'node:child_process';
6
6
  import 'node:os';