happy-imou-cloud 2.1.7 → 2.1.9

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 (27) hide show
  1. package/dist/{BaseReasoningProcessor-C0A6Jr8V.mjs → BaseReasoningProcessor-CgEO6Vh-.mjs} +4 -3
  2. package/dist/{BaseReasoningProcessor-iSuaFeZH.cjs → BaseReasoningProcessor-DV6TAtd7.cjs} +4 -3
  3. package/dist/{ProviderSelectionHandler-DI9QK1iY.cjs → ProviderSelectionHandler-C6ILAmE3.cjs} +2 -2
  4. package/dist/{ProviderSelectionHandler-WT-tvePy.mjs → ProviderSelectionHandler-dczuX21U.mjs} +2 -2
  5. package/dist/{api-BYKV7vX6.mjs → api-CfHYTLZX.mjs} +141 -35
  6. package/dist/{api-CdGT5hsH.cjs → api-CfmTDha2.cjs} +141 -35
  7. package/dist/{command-C1cKqmsa.mjs → command-B0HfYWYW.mjs} +3 -3
  8. package/dist/{command-uyA8cYmL.cjs → command-Bw2XKjA9.cjs} +3 -3
  9. package/dist/{index-BAhlFq45.mjs → index-BKjWLXkN.mjs} +146 -30
  10. package/dist/{index-D_S_7bqK.cjs → index-C3bSe5_d.cjs} +149 -33
  11. package/dist/index.cjs +3 -3
  12. package/dist/index.mjs +3 -3
  13. package/dist/lib.cjs +1 -1
  14. package/dist/lib.d.cts +8 -4
  15. package/dist/lib.d.mts +8 -4
  16. package/dist/lib.mjs +1 -1
  17. package/dist/{persistence-CMvrZ4SA.cjs → persistence-Db4p7gsX.cjs} +1 -1
  18. package/dist/{persistence-B8Wgn6aN.mjs → persistence-Nun5c360.mjs} +1 -1
  19. package/dist/{registerKillSessionHandler-5DVlZaJx.mjs → registerKillSessionHandler-C1S9ytML.mjs} +3 -3
  20. package/dist/{registerKillSessionHandler-DjxtkGMv.cjs → registerKillSessionHandler-FIQEW2Rx.cjs} +3 -3
  21. package/dist/{runClaude-WB884iOO.mjs → runClaude-ClXU2LZA.mjs} +9 -5
  22. package/dist/{runClaude-DNZFsQY0.cjs → runClaude-Dxye_oZf.cjs} +9 -5
  23. package/dist/{runCodex-MJar-238.mjs → runCodex-CXu7_Wgk.mjs} +96 -21
  24. package/dist/{runCodex-WZ91ZLK7.cjs → runCodex-IaL6Vqwc.cjs} +96 -21
  25. package/dist/{runGemini-B_wCHgPU.cjs → runGemini-BgcZcZsv.cjs} +11 -5
  26. package/dist/{runGemini-BPGpwljp.mjs → runGemini-BlJjRWN1.mjs} +11 -5
  27. package/package.json +1 -1
@@ -18,7 +18,7 @@ var node_child_process = require('node:child_process');
18
18
  var expoServerSdk = require('expo-server-sdk');
19
19
 
20
20
  var name = "happy-imou-cloud";
21
- var version = "2.1.7";
21
+ var version = "2.1.9";
22
22
  var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
23
23
  var author = "long.zhu";
24
24
  var license = "MIT";
@@ -433,7 +433,7 @@ async function listDaemonLogFiles(limit = 50) {
433
433
  return { file, path: fullPath, modified: stats.mtime };
434
434
  }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
435
435
  try {
436
- const { readDaemonState } = await Promise.resolve().then(function () { return require('./persistence-CMvrZ4SA.cjs'); });
436
+ const { readDaemonState } = await Promise.resolve().then(function () { return require('./persistence-Db4p7gsX.cjs'); });
437
437
  const state = await readDaemonState();
438
438
  if (!state) {
439
439
  return logs;
@@ -1734,8 +1734,8 @@ function buildSocketAuth(opts) {
1734
1734
  return auth;
1735
1735
  }
1736
1736
 
1737
- const MAX_PENDING_RELIABLE_CODEX_MESSAGES = 200;
1738
- const MAX_PENDING_RELIABLE_CODEX_MESSAGE_BYTES = 512 * 1024;
1737
+ const MAX_PENDING_RELIABLE_SESSION_MESSAGES = 200;
1738
+ const MAX_PENDING_RELIABLE_SESSION_MESSAGE_BYTES = 512 * 1024;
1739
1739
  const PROTOCOL_V3_INITIAL_SNAPSHOT_LIMIT = 150;
1740
1740
  const PROTOCOL_V3_CHANGES_PAGE_LIMIT = 200;
1741
1741
  const PROTOCOL_V3_CAPABILITIES_WAIT_MS = 250;
@@ -1754,8 +1754,8 @@ class ApiSessionClient extends node_events.EventEmitter {
1754
1754
  metadataLock = new AsyncLock();
1755
1755
  encryptionKey;
1756
1756
  encryptionVariant;
1757
- pendingReliableCodexMessages = [];
1758
- pendingReliableCodexMessageBytes = 0;
1757
+ pendingReliableSessionMessages = [];
1758
+ pendingReliableSessionMessageBytes = 0;
1759
1759
  reconnectAfterServerDisconnectTimer = null;
1760
1760
  lastSocketServerError = null;
1761
1761
  protocolV3SessionSync = null;
@@ -1784,7 +1784,14 @@ class ApiSessionClient extends node_events.EventEmitter {
1784
1784
  encryptionVariant: this.encryptionVariant,
1785
1785
  logger: (msg, data) => logger.debug(msg, data)
1786
1786
  });
1787
- registerCommonHandlers(this.rpcHandlerManager, this.metadata.path);
1787
+ const workingDirectory = this.metadata?.path ?? process.cwd();
1788
+ if (!this.metadata?.path) {
1789
+ logger.debug("[API] Session metadata missing path during client initialization, falling back to process.cwd()", {
1790
+ sessionId: this.sessionId,
1791
+ workingDirectory
1792
+ });
1793
+ }
1794
+ registerCommonHandlers(this.rpcHandlerManager, workingDirectory);
1788
1795
  this.socket = socket_ioClient.io(configuration.serverUrl, {
1789
1796
  auth: (cb) => cb(buildSocketAuth({
1790
1797
  credentials: this.credentials,
@@ -1805,7 +1812,7 @@ class ApiSessionClient extends node_events.EventEmitter {
1805
1812
  this.clearReconnectAfterServerDisconnectTimer();
1806
1813
  this.lastSocketServerError = null;
1807
1814
  this.rpcHandlerManager.onSocketConnect(this.socket);
1808
- this.flushReliableCodexMessages();
1815
+ this.flushReliableSessionMessages();
1809
1816
  this.protocolV3Descriptor = null;
1810
1817
  this.protocolV3SocketCapabilities = null;
1811
1818
  this.scheduleProtocolV3SessionSync();
@@ -1988,9 +1995,7 @@ class ApiSessionClient extends node_events.EventEmitter {
1988
1995
  if (!this.socket.connected) {
1989
1996
  if (this.shouldBufferReliableCodexMessage(body)) {
1990
1997
  logger.debug("[API] Socket not connected, buffering reliable Codex message:", { type: eventType });
1991
- this.pendingReliableCodexMessages.push({ encrypted, type: eventType });
1992
- this.pendingReliableCodexMessageBytes += encrypted.length;
1993
- this.trimPendingReliableCodexMessages();
1998
+ this.bufferReliableSessionMessage({ encrypted, type: `codex:${eventType}` });
1994
1999
  } else {
1995
2000
  logger.debug("[API] Socket not connected, dropping non-critical Codex message:", { type: eventType });
1996
2001
  }
@@ -2019,10 +2024,23 @@ class ApiSessionClient extends node_events.EventEmitter {
2019
2024
  };
2020
2025
  logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
2021
2026
  const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
2022
- this.socket.emit("message", {
2023
- sid: this.sessionId,
2024
- message: encrypted
2025
- });
2027
+ const eventType = typeof body?.type === "string" ? body.type : "unknown";
2028
+ if (!this.socket.connected) {
2029
+ if (this.shouldBufferReliableAcpMessage(body)) {
2030
+ logger.debug("[API] Socket not connected, buffering reliable ACP message:", {
2031
+ provider,
2032
+ type: eventType
2033
+ });
2034
+ this.bufferReliableSessionMessage({ encrypted, type: `acp:${provider}:${eventType}` });
2035
+ } else {
2036
+ logger.debug("[API] Socket not connected, dropping non-critical ACP message:", {
2037
+ provider,
2038
+ type: eventType
2039
+ });
2040
+ }
2041
+ return;
2042
+ }
2043
+ this.emitEncryptedSessionMessage(encrypted);
2026
2044
  }
2027
2045
  sendSessionEvent(event, id) {
2028
2046
  let content = {
@@ -2034,10 +2052,16 @@ class ApiSessionClient extends node_events.EventEmitter {
2034
2052
  }
2035
2053
  };
2036
2054
  const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
2037
- this.socket.emit("message", {
2038
- sid: this.sessionId,
2039
- message: encrypted
2040
- });
2055
+ if (!this.socket.connected) {
2056
+ if (this.shouldBufferReliableSessionEvent(event)) {
2057
+ logger.debug("[API] Socket not connected, buffering session event:", { type: event.type });
2058
+ this.bufferReliableSessionMessage({ encrypted, type: `event:${event.type}` });
2059
+ } else {
2060
+ logger.debug("[API] Socket not connected, dropping session event:", { type: event.type });
2061
+ }
2062
+ return;
2063
+ }
2064
+ this.emitEncryptedSessionMessage(encrypted);
2041
2065
  }
2042
2066
  /**
2043
2067
  * Send a ping message to keep the connection alive
@@ -2091,7 +2115,7 @@ class ApiSessionClient extends node_events.EventEmitter {
2091
2115
  * @param handler - Handler function that returns the updated metadata
2092
2116
  */
2093
2117
  updateMetadata(handler) {
2094
- this.metadataLock.inLock(async () => {
2118
+ void this.metadataLock.inLock(async () => {
2095
2119
  await backoff(async () => {
2096
2120
  let updated = handler(this.metadata);
2097
2121
  const sessionIndex = buildSessionRuntimeIndex(updated);
@@ -2114,6 +2138,8 @@ class ApiSessionClient extends node_events.EventEmitter {
2114
2138
  throw new Error("Metadata version mismatch");
2115
2139
  } else if (answer.result === "error") ;
2116
2140
  });
2141
+ }).catch((error) => {
2142
+ logger.debug("[API] Metadata update failed unexpectedly", error);
2117
2143
  });
2118
2144
  }
2119
2145
  /**
@@ -2122,7 +2148,7 @@ class ApiSessionClient extends node_events.EventEmitter {
2122
2148
  */
2123
2149
  updateAgentState(handler) {
2124
2150
  logger.debugLargeJson("Updating agent state", this.agentState);
2125
- this.agentStateLock.inLock(async () => {
2151
+ void this.agentStateLock.inLock(async () => {
2126
2152
  await backoff(async () => {
2127
2153
  let updated = handler(this.agentState || {});
2128
2154
  const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
@@ -2140,6 +2166,8 @@ class ApiSessionClient extends node_events.EventEmitter {
2140
2166
  throw new Error("Agent state version mismatch");
2141
2167
  } else if (answer.result === "error") ;
2142
2168
  });
2169
+ }).catch((error) => {
2170
+ logger.debug("[API] Agent state update failed unexpectedly", error);
2143
2171
  });
2144
2172
  }
2145
2173
  /**
@@ -2441,13 +2469,18 @@ class ApiSessionClient extends node_events.EventEmitter {
2441
2469
  message: encrypted
2442
2470
  });
2443
2471
  }
2444
- flushReliableCodexMessages() {
2445
- if (!this.socket.connected || this.pendingReliableCodexMessages.length === 0) {
2472
+ bufferReliableSessionMessage(message) {
2473
+ this.pendingReliableSessionMessages.push(message);
2474
+ this.pendingReliableSessionMessageBytes += message.encrypted.length;
2475
+ this.trimPendingReliableSessionMessages();
2476
+ }
2477
+ flushReliableSessionMessages() {
2478
+ if (!this.socket.connected || this.pendingReliableSessionMessages.length === 0) {
2446
2479
  return;
2447
2480
  }
2448
- const buffered = this.pendingReliableCodexMessages.splice(0, this.pendingReliableCodexMessages.length);
2449
- this.pendingReliableCodexMessageBytes = 0;
2450
- logger.debug("[API] Flushing buffered Codex messages after reconnect", {
2481
+ const buffered = this.pendingReliableSessionMessages.splice(0, this.pendingReliableSessionMessages.length);
2482
+ this.pendingReliableSessionMessageBytes = 0;
2483
+ logger.debug("[API] Flushing buffered session messages after reconnect", {
2451
2484
  count: buffered.length,
2452
2485
  types: buffered.map((message) => message.type)
2453
2486
  });
@@ -2471,21 +2504,49 @@ class ApiSessionClient extends node_events.EventEmitter {
2471
2504
  return false;
2472
2505
  }
2473
2506
  }
2474
- trimPendingReliableCodexMessages() {
2507
+ shouldBufferReliableAcpMessage(body) {
2508
+ switch (body?.type) {
2509
+ case "message":
2510
+ case "tool-call":
2511
+ case "tool-result":
2512
+ case "file-edit":
2513
+ case "terminal-output":
2514
+ case "task_started":
2515
+ case "task_complete":
2516
+ case "turn_aborted":
2517
+ case "turn-report":
2518
+ case "permission-request":
2519
+ return true;
2520
+ default:
2521
+ return false;
2522
+ }
2523
+ }
2524
+ shouldBufferReliableSessionEvent(event) {
2525
+ switch (event.type) {
2526
+ case "switch":
2527
+ case "message":
2528
+ case "permission-mode-changed":
2529
+ case "ready":
2530
+ return true;
2531
+ default:
2532
+ return false;
2533
+ }
2534
+ }
2535
+ trimPendingReliableSessionMessages() {
2475
2536
  let dropped = 0;
2476
- while (this.pendingReliableCodexMessages.length > MAX_PENDING_RELIABLE_CODEX_MESSAGES || this.pendingReliableCodexMessageBytes > MAX_PENDING_RELIABLE_CODEX_MESSAGE_BYTES) {
2477
- const removed = this.pendingReliableCodexMessages.shift();
2537
+ while (this.pendingReliableSessionMessages.length > MAX_PENDING_RELIABLE_SESSION_MESSAGES || this.pendingReliableSessionMessageBytes > MAX_PENDING_RELIABLE_SESSION_MESSAGE_BYTES) {
2538
+ const removed = this.pendingReliableSessionMessages.shift();
2478
2539
  if (!removed) {
2479
2540
  break;
2480
2541
  }
2481
- this.pendingReliableCodexMessageBytes -= removed.encrypted.length;
2542
+ this.pendingReliableSessionMessageBytes -= removed.encrypted.length;
2482
2543
  dropped += 1;
2483
2544
  }
2484
2545
  if (dropped > 0) {
2485
- logger.debug("[API] Dropped oldest buffered Codex messages to cap reconnect memory usage", {
2546
+ logger.debug("[API] Dropped oldest buffered session messages to cap reconnect memory usage", {
2486
2547
  dropped,
2487
- remaining: this.pendingReliableCodexMessages.length,
2488
- bytes: this.pendingReliableCodexMessageBytes
2548
+ remaining: this.pendingReliableSessionMessages.length,
2549
+ bytes: this.pendingReliableSessionMessageBytes
2489
2550
  });
2490
2551
  }
2491
2552
  }
@@ -3205,6 +3266,39 @@ class OfflineState {
3205
3266
  }
3206
3267
  const connectionState = new OfflineState();
3207
3268
 
3269
+ async function hmac_sha512(key, data) {
3270
+ const hmac = node_crypto.createHmac("sha512", key);
3271
+ hmac.update(data);
3272
+ return new Uint8Array(hmac.digest());
3273
+ }
3274
+
3275
+ async function deriveSecretKeyTreeRoot(seed, usage) {
3276
+ const I = await hmac_sha512(new TextEncoder().encode(usage + " Master Seed"), seed);
3277
+ return {
3278
+ key: I.slice(0, 32),
3279
+ chainCode: I.slice(32)
3280
+ };
3281
+ }
3282
+ async function deriveSecretKeyTreeChild(chainCode, index) {
3283
+ const data = new Uint8Array([0, ...new TextEncoder().encode(index)]);
3284
+ const I = await hmac_sha512(chainCode, data);
3285
+ return {
3286
+ key: I.subarray(0, 32),
3287
+ chainCode: I.subarray(32)
3288
+ };
3289
+ }
3290
+ async function deriveKey(master, usage, path) {
3291
+ let state = await deriveSecretKeyTreeRoot(master, usage);
3292
+ let remaining = [...path];
3293
+ while (remaining.length > 0) {
3294
+ let index = remaining[0];
3295
+ remaining = remaining.slice(1);
3296
+ state = await deriveSecretKeyTreeChild(state.chainCode, index);
3297
+ }
3298
+ return state.key;
3299
+ }
3300
+
3301
+ const SESSION_DATA_KEY_USAGE = "Happy Session Data Key";
3208
3302
  class ApiClient {
3209
3303
  static async create(credential) {
3210
3304
  return new ApiClient(credential);
@@ -3346,7 +3440,15 @@ class ApiClient {
3346
3440
  let encryptionKey;
3347
3441
  let encryptionVariant;
3348
3442
  if (this.credential.encryption.type === "dataKey") {
3349
- encryptionKey = getRandomBytes(32);
3443
+ if (opts.stableDataKeyPath && opts.stableDataKeyPath.length > 0) {
3444
+ encryptionKey = await deriveKey(
3445
+ this.credential.encryption.machineKey,
3446
+ SESSION_DATA_KEY_USAGE,
3447
+ opts.stableDataKeyPath
3448
+ );
3449
+ } else {
3450
+ encryptionKey = getRandomBytes(32);
3451
+ }
3350
3452
  encryptionVariant = "dataKey";
3351
3453
  let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
3352
3454
  dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
@@ -3372,10 +3474,14 @@ class ApiClient {
3372
3474
  });
3373
3475
  logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
3374
3476
  let raw = response.data.session;
3477
+ const metadata = decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata));
3478
+ if (metadata === null) {
3479
+ throw new Error(`Failed to decrypt session metadata for tag ${opts.tag}`);
3480
+ }
3375
3481
  let session = {
3376
3482
  id: raw.id,
3377
3483
  seq: raw.seq,
3378
- metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
3484
+ metadata,
3379
3485
  metadataVersion: raw.metadataVersion,
3380
3486
  agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
3381
3487
  agentStateVersion: raw.agentStateVersion,
@@ -1,6 +1,6 @@
1
- import { c as createDefaultRuntimeShell } from './index-BAhlFq45.mjs';
1
+ import { c as createDefaultRuntimeShell } from './index-BKjWLXkN.mjs';
2
2
  import 'chalk';
3
- import './api-BYKV7vX6.mjs';
3
+ import './api-CfHYTLZX.mjs';
4
4
  import 'axios';
5
5
  import 'fs';
6
6
  import 'node:fs';
@@ -16,7 +16,7 @@ import 'crypto';
16
16
  import 'path';
17
17
  import 'node:child_process';
18
18
  import 'expo-server-sdk';
19
- import './persistence-B8Wgn6aN.mjs';
19
+ import './persistence-Nun5c360.mjs';
20
20
  import 'node:fs/promises';
21
21
  import 'os';
22
22
  import 'tmp';
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-D_S_7bqK.cjs');
3
+ var index = require('./index-C3bSe5_d.cjs');
4
4
  require('chalk');
5
- require('./api-CdGT5hsH.cjs');
5
+ require('./api-CfmTDha2.cjs');
6
6
  require('axios');
7
7
  require('fs');
8
8
  require('node:fs');
@@ -18,7 +18,7 @@ require('crypto');
18
18
  require('path');
19
19
  require('node:child_process');
20
20
  require('expo-server-sdk');
21
- require('./persistence-CMvrZ4SA.cjs');
21
+ require('./persistence-Db4p7gsX.cjs');
22
22
  require('node:fs/promises');
23
23
  require('os');
24
24
  require('tmp');
@@ -1,6 +1,6 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
2
- import { l as logger, e as encodeBase64, c as configuration, k as buildAuthenticatedHeaders, S as SigningBootstrapRequiredError, m as SIGNING_BOOTSTRAP_REQUIRED_MESSAGE, n as encodeBase64Url, h as delay, o as buildClientHeaders, p as decodeBase64, q as HAPPY_CLOUD_DAEMON_PORT, r as packageJson, A as ApiClient, t as HeadTailPreviewBuffer, u as getLatestDaemonLog } from './api-BYKV7vX6.mjs';
3
- import { writeCredentialsLegacy, writeCredentialsDataKey, readCredentials, readSettings, updateSettings, readDaemonState, clearDaemonState, acquireDaemonLock, writeDaemonState, releaseDaemonLock, validateProfileForAgent, getProfileEnvironmentVariables, clearCredentials, clearMachineId } from './persistence-B8Wgn6aN.mjs';
2
+ import { l as logger, e as encodeBase64, c as configuration, k as buildAuthenticatedHeaders, S as SigningBootstrapRequiredError, m as SIGNING_BOOTSTRAP_REQUIRED_MESSAGE, n as encodeBase64Url, h as delay, o as buildClientHeaders, p as decodeBase64, q as HAPPY_CLOUD_DAEMON_PORT, r as packageJson, A as ApiClient, t as HeadTailPreviewBuffer, u as getLatestDaemonLog } from './api-CfHYTLZX.mjs';
3
+ import { writeCredentialsLegacy, writeCredentialsDataKey, readCredentials, readSettings, updateSettings, readDaemonState, clearDaemonState, acquireDaemonLock, writeDaemonState, releaseDaemonLock, validateProfileForAgent, getProfileEnvironmentVariables, clearCredentials, clearMachineId } from './persistence-Nun5c360.mjs';
4
4
  import { z } from 'zod';
5
5
  import fs, { writeFile as writeFile$1, rename, unlink as unlink$1 } from 'fs/promises';
6
6
  import os$1, { homedir } from 'os';
@@ -2447,6 +2447,20 @@ function buildDaemonChildEnv(baseEnv, extraEnv) {
2447
2447
  }
2448
2448
 
2449
2449
  const DIFFERENT_DAEMON_RUNNING_MESSAGE = "A different daemon was started without killing us. We should kill ourselves.";
2450
+ function pruneStaleTrackedSessions({
2451
+ trackedSessionPids,
2452
+ removeTrackedSession,
2453
+ kill = process.kill
2454
+ }) {
2455
+ for (const pid of trackedSessionPids) {
2456
+ try {
2457
+ kill(pid, 0);
2458
+ } catch {
2459
+ logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
2460
+ removeTrackedSession(pid);
2461
+ }
2462
+ }
2463
+ }
2450
2464
  function readProjectCliVersionFromDisk() {
2451
2465
  const packageJsonPath = join(projectPath(), "package.json");
2452
2466
  try {
@@ -2477,14 +2491,10 @@ async function runDaemonHealthCheck({
2477
2491
  requestShutdown,
2478
2492
  writeHeartbeat
2479
2493
  }) {
2480
- for (const pid of trackedSessionPids) {
2481
- try {
2482
- process.kill(pid, 0);
2483
- } catch {
2484
- logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
2485
- removeTrackedSession(pid);
2486
- }
2487
- }
2494
+ pruneStaleTrackedSessions({
2495
+ trackedSessionPids,
2496
+ removeTrackedSession
2497
+ });
2488
2498
  const projectVersion = readProjectCliVersion();
2489
2499
  if (projectVersion && projectVersion !== currentCliVersion) {
2490
2500
  await onDaemonOutdated();
@@ -2594,7 +2604,8 @@ async function precreateDaemonManagedSession(opts) {
2594
2604
  return await opts.api.getOrCreateSession({
2595
2605
  tag: opts.sessionTag,
2596
2606
  metadata,
2597
- state
2607
+ state,
2608
+ stableDataKeyPath: ["managed-session-tag", opts.sessionTag]
2598
2609
  });
2599
2610
  } catch (error) {
2600
2611
  logger.debug(`[DAEMON RUN] Failed to precreate ${opts.flavor} session for tag ${opts.sessionTag}`, error);
@@ -2956,7 +2967,42 @@ function resolveManagedSessionTag(env = process.env) {
2956
2967
  return readManagedSessionTag(env) ?? randomUUID();
2957
2968
  }
2958
2969
 
2970
+ const DEFAULT_MAX_LINES = 40;
2971
+ const DEFAULT_MAX_LINE_LENGTH = 300;
2972
+ function truncateLine(line, maxLineLength) {
2973
+ if (line.length <= maxLineLength) {
2974
+ return line;
2975
+ }
2976
+ return `${line.slice(0, Math.max(0, maxLineLength - 3))}...`;
2977
+ }
2978
+ function createChildProcessOutputBuffer(options) {
2979
+ const maxLines = Math.max(1, DEFAULT_MAX_LINES);
2980
+ const maxLineLength = Math.max(4, DEFAULT_MAX_LINE_LENGTH);
2981
+ const lines = [];
2982
+ return {
2983
+ push(text) {
2984
+ for (const rawLine of text.split(/\r?\n/)) {
2985
+ const line = rawLine.trim();
2986
+ if (!line) {
2987
+ continue;
2988
+ }
2989
+ lines.push(truncateLine(line, maxLineLength));
2990
+ while (lines.length > maxLines) {
2991
+ lines.shift();
2992
+ }
2993
+ }
2994
+ },
2995
+ snapshot() {
2996
+ return lines.join("\n");
2997
+ },
2998
+ hasContent() {
2999
+ return lines.length > 0;
3000
+ }
3001
+ };
3002
+ }
3003
+
2959
3004
  const DEFAULT_SESSION_WEBHOOK_TIMEOUT_MS = 45e3;
3005
+ const DEFAULT_TRACKED_SESSION_SWEEP_INTERVAL_MS = 5e3;
2960
3006
  function resolveSessionWebhookTimeoutMs() {
2961
3007
  const parsed = Number.parseInt(process.env.HAPPY_DAEMON_SESSION_WEBHOOK_TIMEOUT || "", 10);
2962
3008
  if (Number.isFinite(parsed) && parsed > 0) {
@@ -2964,6 +3010,13 @@ function resolveSessionWebhookTimeoutMs() {
2964
3010
  }
2965
3011
  return DEFAULT_SESSION_WEBHOOK_TIMEOUT_MS;
2966
3012
  }
3013
+ function resolveTrackedSessionSweepIntervalMs() {
3014
+ const parsed = Number.parseInt(process.env.HAPPY_DAEMON_TRACKED_SESSION_SWEEP_INTERVAL || "", 10);
3015
+ if (Number.isFinite(parsed) && parsed > 0) {
3016
+ return parsed;
3017
+ }
3018
+ return DEFAULT_TRACKED_SESSION_SWEEP_INTERVAL_MS;
3019
+ }
2967
3020
  const initialMachineMetadata = {
2968
3021
  host: os$1.hostname(),
2969
3022
  platform: os$1.platform(),
@@ -2994,6 +3047,7 @@ async function getProfileEnvironmentVariablesForAgent(profileId, agentType) {
2994
3047
  }
2995
3048
  async function startDaemon() {
2996
3049
  const sessionWebhookTimeoutMs = resolveSessionWebhookTimeoutMs();
3050
+ const trackedSessionSweepIntervalMs = resolveTrackedSessionSweepIntervalMs();
2997
3051
  const shutdownCoordinator = createShutdownCoordinator(async () => {
2998
3052
  logger.debug("[DAEMON RUN] Startup malfunctioned, forcing exit with code 1");
2999
3053
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -3328,14 +3382,39 @@ async function startDaemon() {
3328
3382
  // Capture stdout/stderr for debugging
3329
3383
  env: buildDaemonChildEnv(process.env, extraEnv)
3330
3384
  });
3331
- if (process.env.DEBUG) {
3332
- happyProcess.stdout?.on("data", (data) => {
3333
- logger.debug(`[DAEMON RUN] Child stdout: ${data.toString()}`);
3334
- });
3335
- happyProcess.stderr?.on("data", (data) => {
3336
- logger.debug(`[DAEMON RUN] Child stderr: ${data.toString()}`);
3337
- });
3338
- }
3385
+ const stdoutBuffer = createChildProcessOutputBuffer();
3386
+ const stderrBuffer = createChildProcessOutputBuffer();
3387
+ let childOutputSnapshotLogged = false;
3388
+ const logChildOutputSnapshot = (reason) => {
3389
+ if (childOutputSnapshotLogged) {
3390
+ return;
3391
+ }
3392
+ childOutputSnapshotLogged = true;
3393
+ const stdoutSnapshot = stdoutBuffer.snapshot();
3394
+ const stderrSnapshot = stderrBuffer.snapshot();
3395
+ if (stdoutSnapshot) {
3396
+ logger.debug(`[DAEMON RUN] Child PID ${happyProcess.pid} recent stdout before ${reason}:
3397
+ ${stdoutSnapshot}`);
3398
+ }
3399
+ if (stderrSnapshot) {
3400
+ logger.debug(`[DAEMON RUN] Child PID ${happyProcess.pid} recent stderr before ${reason}:
3401
+ ${stderrSnapshot}`);
3402
+ }
3403
+ };
3404
+ happyProcess.stdout?.on("data", (data) => {
3405
+ const text = data.toString();
3406
+ stdoutBuffer.push(text);
3407
+ if (process.env.DEBUG) {
3408
+ logger.debug(`[DAEMON RUN] Child stdout: ${text}`);
3409
+ }
3410
+ });
3411
+ happyProcess.stderr?.on("data", (data) => {
3412
+ const text = data.toString();
3413
+ stderrBuffer.push(text);
3414
+ if (process.env.DEBUG) {
3415
+ logger.debug(`[DAEMON RUN] Child stderr: ${text}`);
3416
+ }
3417
+ });
3339
3418
  if (!happyProcess.pid) {
3340
3419
  logger.debug("[DAEMON RUN] Failed to spawn process - no PID returned");
3341
3420
  if (precreatedCodexSession && api) {
@@ -3366,12 +3445,14 @@ async function startDaemon() {
3366
3445
  };
3367
3446
  pidToTrackedSession.set(happyProcess.pid, trackedSession);
3368
3447
  happyProcess.on("exit", (code, signal) => {
3448
+ logChildOutputSnapshot(`exit (code=${code}, signal=${signal})`);
3369
3449
  logger.debug(`[DAEMON RUN] Child PID ${happyProcess.pid} exited with code ${code}, signal ${signal}`);
3370
3450
  if (happyProcess.pid) {
3371
3451
  onChildExited(happyProcess.pid);
3372
3452
  }
3373
3453
  });
3374
3454
  happyProcess.on("error", (error) => {
3455
+ logChildOutputSnapshot("process error");
3375
3456
  logger.debug(`[DAEMON RUN] Child process error:`, error);
3376
3457
  if (happyProcess.pid) {
3377
3458
  onChildExited(happyProcess.pid);
@@ -3388,6 +3469,7 @@ async function startDaemon() {
3388
3469
  return new Promise((resolve) => {
3389
3470
  const timeout = setTimeout(() => {
3390
3471
  pidToAwaiter.delete(happyProcess.pid);
3472
+ logChildOutputSnapshot("session webhook timeout");
3391
3473
  logger.debug(`[DAEMON RUN] Session webhook timeout for PID ${happyProcess.pid}`);
3392
3474
  const spawnError = createSpawnSessionError(
3393
3475
  SPAWN_SESSION_ERROR_CODES.SESSION_WEBHOOK_TIMEOUT,
@@ -3572,6 +3654,25 @@ async function startDaemon() {
3572
3654
  logger.debug("[DAEMON RUN] Remote session index startup recovery failed", error);
3573
3655
  });
3574
3656
  const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
3657
+ let trackedSessionSweepRunning = false;
3658
+ const trackedSessionSweepInterval = setInterval(() => {
3659
+ if (trackedSessionSweepRunning) {
3660
+ return;
3661
+ }
3662
+ trackedSessionSweepRunning = true;
3663
+ try {
3664
+ pruneStaleTrackedSessions({
3665
+ trackedSessionPids: pidToTrackedSession.keys(),
3666
+ removeTrackedSession: (pid) => {
3667
+ removeTrackedSession(pid, `fast stale-session sweep removed tracked PID ${pid}`);
3668
+ }
3669
+ });
3670
+ } catch (error) {
3671
+ logger.debug("[DAEMON RUN] Fast tracked-session sweep failed", error);
3672
+ } finally {
3673
+ trackedSessionSweepRunning = false;
3674
+ }
3675
+ }, trackedSessionSweepIntervalMs);
3575
3676
  let heartbeatRunning = false;
3576
3677
  const restartOnStaleVersionAndHeartbeat = setInterval(async () => {
3577
3678
  if (heartbeatRunning) {
@@ -3643,6 +3744,7 @@ async function startDaemon() {
3643
3744
  clearInterval(restartOnStaleVersionAndHeartbeat);
3644
3745
  logger.debug("[DAEMON RUN] Health check interval cleared");
3645
3746
  }
3747
+ clearInterval(trackedSessionSweepInterval);
3646
3748
  if (remoteSessionIndexRefreshTimer) {
3647
3749
  clearTimeout(remoteSessionIndexRefreshTimer);
3648
3750
  remoteSessionIndexRefreshTimer = null;
@@ -7567,7 +7669,14 @@ ${recentStderrExcerpt}`);
7567
7669
  prompt: [contentBlock]
7568
7670
  };
7569
7671
  logger.debug(`[AcpBackend] Prompt request:`, JSON.stringify(promptRequest, null, 2));
7570
- await this.connection.prompt(promptRequest);
7672
+ await raceWithProcessExit(
7673
+ this.process,
7674
+ this.connection.prompt(promptRequest),
7675
+ {
7676
+ agentName: this.transport.agentName,
7677
+ operationName: "prompt"
7678
+ }
7679
+ );
7571
7680
  logger.debug("[AcpBackend] Prompt request sent to ACP connection");
7572
7681
  if (this.waitingForResponse && this.activeToolCalls.size === 0 && this.sawSessionUpdateSincePrompt === false) {
7573
7682
  const noUpdatesTimeoutMs = this.getPostPromptNoUpdatesTimeoutMs();
@@ -9730,11 +9839,11 @@ var launch = /*#__PURE__*/Object.freeze({
9730
9839
 
9731
9840
  const unifiedProviderExecutors = {
9732
9841
  claude: async (opts) => {
9733
- const { runClaude } = await import('./runClaude-WB884iOO.mjs');
9842
+ const { runClaude } = await import('./runClaude-ClXU2LZA.mjs');
9734
9843
  await runClaude(opts.credentials, opts.claudeOptions ?? {});
9735
9844
  },
9736
9845
  codex: async (opts) => {
9737
- const { runCodex } = await import('./runCodex-MJar-238.mjs');
9846
+ const { runCodex } = await import('./runCodex-CXu7_Wgk.mjs');
9738
9847
  await runCodex({
9739
9848
  credentials: opts.credentials,
9740
9849
  startedBy: opts.startedBy,
@@ -9743,7 +9852,7 @@ const unifiedProviderExecutors = {
9743
9852
  });
9744
9853
  },
9745
9854
  gemini: async (opts) => {
9746
- const { runGemini } = await import('./runGemini-BPGpwljp.mjs');
9855
+ const { runGemini } = await import('./runGemini-BlJjRWN1.mjs');
9747
9856
  await runGemini({
9748
9857
  credentials: opts.credentials,
9749
9858
  startedBy: opts.startedBy
@@ -9756,13 +9865,17 @@ const unifiedProviderExecutors = {
9756
9865
  });
9757
9866
  }
9758
9867
  };
9759
- async function ensureUnifiedRuntimePrerequisites(credentials) {
9868
+ async function ensureUnifiedRuntimePrerequisites(credentials, opts) {
9760
9869
  if (credentials) {
9761
- await ensureUnifiedDaemonStarted();
9870
+ if (!opts?.skipDaemonBootstrap) {
9871
+ await ensureUnifiedDaemonStarted();
9872
+ }
9762
9873
  return credentials;
9763
9874
  }
9764
9875
  const auth = await authAndSetupMachineIfNeeded();
9765
- await ensureUnifiedDaemonStarted();
9876
+ if (!opts?.skipDaemonBootstrap) {
9877
+ await ensureUnifiedDaemonStarted();
9878
+ }
9766
9879
  return auth.credentials;
9767
9880
  }
9768
9881
  async function ensureUnifiedDaemonStarted() {
@@ -9783,7 +9896,10 @@ async function ensureUnifiedDaemonStarted() {
9783
9896
  throw new Error("Failed to start Happy background service.");
9784
9897
  }
9785
9898
  async function executeUnifiedProvider(opts) {
9786
- const credentials = await ensureUnifiedRuntimePrerequisites(opts.credentials);
9899
+ const skipDaemonBootstrap = "startedBy" in opts && opts.startedBy === "daemon" || opts.provider === "claude" && opts.claudeOptions?.startedBy === "daemon";
9900
+ const credentials = await ensureUnifiedRuntimePrerequisites(opts.credentials, {
9901
+ skipDaemonBootstrap
9902
+ });
9787
9903
  await unifiedProviderExecutors[opts.provider]({
9788
9904
  ...opts,
9789
9905
  credentials
@@ -9819,7 +9935,7 @@ function shouldRunMainClaudeFlow(opts) {
9819
9935
  return;
9820
9936
  } else if (subcommand === "runtime") {
9821
9937
  if (args[1] === "providers") {
9822
- const { renderRuntimeProviders } = await import('./command-C1cKqmsa.mjs');
9938
+ const { renderRuntimeProviders } = await import('./command-B0HfYWYW.mjs');
9823
9939
  console.log(renderRuntimeProviders());
9824
9940
  return;
9825
9941
  }
@@ -9997,8 +10113,8 @@ function shouldRunMainClaudeFlow(opts) {
9997
10113
  const projectId = args[3];
9998
10114
  try {
9999
10115
  const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
10000
- const { readCredentials: readCredentials2 } = await import('./persistence-B8Wgn6aN.mjs');
10001
- const { ApiClient: ApiClient2 } = await import('./api-BYKV7vX6.mjs').then(function (n) { return n.v; });
10116
+ const { readCredentials: readCredentials2 } = await import('./persistence-Nun5c360.mjs');
10117
+ const { ApiClient: ApiClient2 } = await import('./api-CfHYTLZX.mjs').then(function (n) { return n.v; });
10002
10118
  let userEmail = void 0;
10003
10119
  try {
10004
10120
  const credentials = await readCredentials2();