very-happy-cli 0.2.121 → 0.2.123

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 (35) hide show
  1. package/dist/{AcpBackend-Cdj56ZfU.cjs → AcpBackend-DzIvpTxZ.cjs} +1 -1
  2. package/dist/{AcpBackend-BxvQ6O4k.mjs → AcpBackend-hM-qHLN3.mjs} +1 -1
  3. package/dist/{AcpSessionManager-DlWlQVhw.mjs → AcpSessionManager-CRPZPHPa.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DahCU78z.cjs → AcpSessionManager-DyU3Ikgj.cjs} +1 -1
  5. package/dist/{config-DxOhdFEx.cjs → config-4RaHrVSV.cjs} +2 -2
  6. package/dist/{config-osO37dR1.mjs → config-alG86PDN.mjs} +2 -2
  7. package/dist/{index-Iz-M3qbz.mjs → index-36AkXNF2.mjs} +388 -188
  8. package/dist/{index-DObePs_K.cjs → index-5b9YhV-1.cjs} +390 -190
  9. package/dist/{index-Bv53Zygv.cjs → index-Beh0UZf8.cjs} +4 -4
  10. package/dist/{index-BcwAnM9k.mjs → index-Cg3Vfc00.mjs} +5 -5
  11. package/dist/{index-Vjo1jIJH.cjs → index-DWBKJ5zy.cjs} +2 -2
  12. package/dist/{index-DtZFY8aS.mjs → index-vyYHKKk6.mjs} +2 -2
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-DehsXLmV.cjs → installTerminalHooks-DWCa5j7u.cjs} +2 -2
  16. package/dist/{installTerminalHooks-Bq11vOqF.mjs → installTerminalHooks-DqUPkh0l.mjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +7 -0
  19. package/dist/lib.d.mts +7 -0
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-Cpx9Qlch.cjs → mcp-BBya5dsZ.cjs} +2 -2
  22. package/dist/{mcp-Bx1B3uny.mjs → mcp-DZIdcsJp.mjs} +2 -2
  23. package/dist/{runGemini-CMgudbJs.mjs → runGemini-BQa2eF3i.mjs} +4 -4
  24. package/dist/{runGemini-DI8DipUx.cjs → runGemini-DmWxHIwS.cjs} +4 -4
  25. package/dist/{runOpenClaw-DR3fLtVf.mjs → runOpenClaw-362eg04u.mjs} +3 -3
  26. package/dist/{runOpenClaw-CetdjTl-.cjs → runOpenClaw-boyJtPBq.cjs} +3 -3
  27. package/dist/{send-Cq0NZCn8.cjs → send-BINA__Ke.cjs} +2 -2
  28. package/dist/{send-M4b_hD7t.mjs → send-CT22-8Tp.mjs} +2 -2
  29. package/dist/{sessions-DW4lrLOp.cjs → sessions-DXYxV6Np.cjs} +2 -2
  30. package/dist/{sessions-CelkWhqX.mjs → sessions-DemgtCkr.mjs} +2 -2
  31. package/dist/{spawn-D3cwBlWI.cjs → spawn-BtV9dU1i.cjs} +2 -2
  32. package/dist/{spawn-C4fF0q3Z.mjs → spawn-D2egvPgO.mjs} +2 -2
  33. package/dist/{types-P2cJWD0E.mjs → types-BqW7nEN_.mjs} +88 -39
  34. package/dist/{types-COOYpI8_.cjs → types-Cd_tUN3U.cjs} +89 -39
  35. package/package.json +7 -7
@@ -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-COOYpI8_.cjs');
6
+ var persistence = require('./types-Cd_tUN3U.cjs');
7
7
  var spawn = require('cross-spawn');
8
8
  var path = require('node:path');
9
9
  var node_readline = require('node:readline');
@@ -7320,24 +7320,297 @@ function decideHandover(run) {
7320
7320
  return { action: "handover" };
7321
7321
  }
7322
7322
 
7323
- function decideAutoUpdate(context) {
7324
- if (!context.enabled) return { action: "skip", reason: "auto-update disabled" };
7325
- const target = context.autoUpdateVersion;
7326
- if (!target) return { action: "skip", reason: "no version approved for automatic install" };
7327
- const ordering = persistence.compareExactVersions(context.currentVersion, target);
7328
- if (ordering === null) return { action: "skip", reason: "version numbers not comparable" };
7329
- if (ordering === 0) return { action: "skip", reason: "already current" };
7330
- if (ordering > 0) return { action: "skip", reason: `already ahead of the approved ${target}` };
7331
- if (context.failedVersion === target) {
7332
- return { action: "skip", reason: `already failed to install ${target} once` };
7333
- }
7334
- if (!context.idle) return { action: "skip", reason: "machine is busy" };
7335
- return { action: "install", version: target };
7323
+ function serialTask(task) {
7324
+ let running = false;
7325
+ return async () => {
7326
+ if (running) return;
7327
+ running = true;
7328
+ try {
7329
+ await task();
7330
+ } finally {
7331
+ running = false;
7332
+ }
7333
+ };
7336
7334
  }
7335
+
7337
7336
  function autoUpdateInstallArgs(version) {
7338
7337
  return ["i", "-g", "--allow-scripts=very-happy-cli,node-pty", `very-happy-cli@${version}`];
7339
7338
  }
7340
7339
 
7340
+ const processDefaults = {
7341
+ spawn: node_child_process.spawn,
7342
+ killGroup: (pid) => {
7343
+ process.kill(-pid, "SIGKILL");
7344
+ },
7345
+ platform: process.platform,
7346
+ timeoutMs: 10 * 6e4,
7347
+ terminationGraceMs: 5e3
7348
+ };
7349
+ const runNpmProcess = (args, deps = processDefaults) => new Promise((resolveResult) => {
7350
+ let stdout = "";
7351
+ let stderr = "";
7352
+ let timedOut = false;
7353
+ let settled = false;
7354
+ let grace;
7355
+ let timer;
7356
+ let treeKilled = false;
7357
+ const done = (result) => {
7358
+ if (settled) return;
7359
+ settled = true;
7360
+ clearTimeout(timer);
7361
+ clearTimeout(grace);
7362
+ resolveResult(result);
7363
+ };
7364
+ if (args.some((arg) => !/^[A-Za-z0-9@=.,+/_-]+$/.test(arg))) {
7365
+ done({ code: null, stdout, stderr: "Invalid npm argument" });
7366
+ return;
7367
+ }
7368
+ const child = deps.platform === "win32" ? deps.spawn("cmd.exe", ["/d", "/s", "/c", `npm ${args.join(" ")}`], { stdio: ["ignore", "pipe", "pipe"] }) : deps.spawn("npm", args, { stdio: ["ignore", "pipe", "pipe"], detached: true });
7369
+ timer = setTimeout(() => {
7370
+ timedOut = true;
7371
+ grace = setTimeout(() => done({ code: null, stdout, stderr, terminationConfirmed: false }), deps.terminationGraceMs);
7372
+ if (!child.pid) return;
7373
+ if (deps.platform === "win32") {
7374
+ const killer = deps.spawn("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
7375
+ killer.on("error", () => void 0);
7376
+ killer.on("close", (code) => {
7377
+ treeKilled = code === 0;
7378
+ });
7379
+ } else {
7380
+ try {
7381
+ deps.killGroup(child.pid);
7382
+ treeKilled = true;
7383
+ } catch {
7384
+ }
7385
+ }
7386
+ }, deps.timeoutMs);
7387
+ child.stdout?.on("data", (data) => {
7388
+ stdout = (stdout + String(data)).slice(-8192);
7389
+ });
7390
+ child.stderr?.on("data", (data) => {
7391
+ stderr = (stderr + String(data)).slice(-8192);
7392
+ });
7393
+ child.on("error", () => done({ code: null, stdout, stderr, terminationConfirmed: !timedOut }));
7394
+ child.on("close", (code) => done({ code: timedOut ? null : code, stdout, stderr, terminationConfirmed: !timedOut || treeKilled }));
7395
+ });
7396
+ const runNpm = (args) => runNpmProcess(args);
7397
+ const defaults = { npm: runNpm, fs: { lstat: promises.lstat, readlink: promises.readlink, unlink: promises.unlink, rm: promises.rm, readFile: promises.readFile, symlink: promises.symlink }, platform: process.platform };
7398
+ function ownedCliLink(link, target, packageDir) {
7399
+ const resolved = path.resolve(path.dirname(link), target);
7400
+ return resolved.startsWith(path.join(packageDir, "bin") + path.sep);
7401
+ }
7402
+ async function installCliSafely(version, deps = defaults) {
7403
+ const parsed = persistence.parseExactVersion(version);
7404
+ if (!parsed) throw new Error("Invalid exact CLI version");
7405
+ const args = autoUpdateInstallArgs(parsed.exact);
7406
+ if (deps.platform === "win32") {
7407
+ const result2 = await deps.npm(args);
7408
+ return result2.terminationConfirmed === false ? "blocked" : result2.code;
7409
+ }
7410
+ const rootResult = await deps.npm(["root", "-g"]);
7411
+ if (rootResult.terminationConfirmed === false) return "blocked";
7412
+ const prefixResult = await deps.npm(["prefix", "-g"]);
7413
+ if (prefixResult.terminationConfirmed === false) return "blocked";
7414
+ const root = rootResult.stdout.trim();
7415
+ const prefix = prefixResult.stdout.trim();
7416
+ if (rootResult.code !== 0 || prefixResult.code !== 0 || !path.isAbsolute(root) || !path.isAbsolute(prefix) || path.basename(root) !== "node_modules" || path.resolve(prefix) === "/" || root.includes("\n") || prefix.includes("\n")) {
7417
+ throw new Error("Cannot verify global npm paths; use manual install");
7418
+ }
7419
+ if (path.resolve(root) !== path.resolve(prefix, "lib/node_modules")) throw new Error("Unexpected npm root/prefix relationship");
7420
+ const packageDir = path.join(root, "very-happy-cli");
7421
+ const packageStat = await deps.fs.lstat(packageDir).catch((error) => {
7422
+ if (error.code === "ENOENT") return null;
7423
+ throw error;
7424
+ });
7425
+ if (packageStat && (!packageStat.isDirectory() || packageStat.isSymbolicLink())) {
7426
+ throw new Error("Refusing to modify a linked or non-directory CLI package");
7427
+ }
7428
+ if (packageStat) {
7429
+ const manifest = JSON.parse(await deps.fs.readFile(path.join(packageDir, "package.json"), "utf8"));
7430
+ if (manifest.name !== "very-happy-cli") throw new Error("Cannot verify installed CLI package identity");
7431
+ }
7432
+ const ownLinks = [];
7433
+ for (const name of ["very-happy", "very-happy-mcp"]) {
7434
+ const link = path.join(prefix, "bin", name);
7435
+ const stat = await deps.fs.lstat(link).catch((error) => {
7436
+ if (error.code === "ENOENT") return null;
7437
+ throw error;
7438
+ });
7439
+ if (!stat) continue;
7440
+ if (!stat.isSymbolicLink() || !ownedCliLink(link, await deps.fs.readlink(link), packageDir)) {
7441
+ throw new Error("CLI bin is owned by another installation; use manual recovery");
7442
+ }
7443
+ ownLinks.push({ link, target: await deps.fs.readlink(link) });
7444
+ }
7445
+ let result = null;
7446
+ let safeToRestore = true;
7447
+ try {
7448
+ for (const { link } of ownLinks) await deps.fs.unlink(link);
7449
+ const first = await deps.npm(args);
7450
+ if (first.terminationConfirmed === false) {
7451
+ safeToRestore = false;
7452
+ return "blocked";
7453
+ }
7454
+ result = first.code;
7455
+ if (first.code === 0 || first.code === null) return first.code;
7456
+ if (!/\b(?:EEXIST|ENOTEMPTY)\b/.test(first.stderr ?? "")) return first.code;
7457
+ const failedStat = await deps.fs.lstat(packageDir).catch((error) => {
7458
+ if (error.code === "ENOENT") return null;
7459
+ throw error;
7460
+ });
7461
+ if (failedStat && (!packageStat || !failedStat.isDirectory() || failedStat.isSymbolicLink())) {
7462
+ throw new Error("Refusing to remove an unverified CLI package");
7463
+ }
7464
+ if (failedStat) await deps.fs.rm(packageDir, { recursive: true, force: true });
7465
+ const second = await deps.npm(args);
7466
+ result = second.code;
7467
+ if (second.terminationConfirmed === false) {
7468
+ safeToRestore = false;
7469
+ return "blocked";
7470
+ }
7471
+ return result;
7472
+ } finally {
7473
+ if (result !== 0 && safeToRestore) {
7474
+ for (const { link, target } of ownLinks) {
7475
+ const entry = await deps.fs.lstat(path.resolve(path.dirname(link), target)).catch(() => null);
7476
+ const current = await deps.fs.lstat(link).catch(() => null);
7477
+ if (entry?.isFile() && !current) await deps.fs.symlink(target, link).catch(() => void 0);
7478
+ }
7479
+ }
7480
+ }
7481
+ }
7482
+
7483
+ function createUpdateController(deps) {
7484
+ let policy = null;
7485
+ let outcome = null;
7486
+ let running = false;
7487
+ let operation = null;
7488
+ let blocked = false;
7489
+ let handoverHold;
7490
+ let lastPublished = "";
7491
+ const now = deps.now ?? Date.now;
7492
+ function publish(state, version, detail) {
7493
+ if (!policy) return;
7494
+ const fingerprint = JSON.stringify([policy.checkedAt, state, version, detail]);
7495
+ if (fingerprint === lastPublished) return;
7496
+ lastPublished = fingerprint;
7497
+ outcome = { state, version, detail, at: now() };
7498
+ deps.publish({ ...policy, retrySupported: !blocked, handoverHold, autoUpdate: outcome });
7499
+ }
7500
+ async function evaluate() {
7501
+ if (!policy || running) return;
7502
+ const target = policy.autoUpdateVersion ?? null;
7503
+ if (outcome?.version === target && ["failed", "installed"].includes(outcome.state)) {
7504
+ const fingerprint = JSON.stringify([policy.checkedAt, outcome.state, outcome.version, outcome.detail]);
7505
+ if (fingerprint !== lastPublished) {
7506
+ lastPublished = fingerprint;
7507
+ deps.publish({ ...policy, retrySupported: !blocked, handoverHold, autoUpdate: outcome });
7508
+ }
7509
+ return;
7510
+ }
7511
+ if (now() - policy.checkedAt > 65 * 6e4) {
7512
+ publish("policy_stale", target);
7513
+ return;
7514
+ }
7515
+ if (!await deps.enabled()) {
7516
+ publish("disabled", target);
7517
+ return;
7518
+ }
7519
+ if (running) return;
7520
+ if (now() - policy.checkedAt > 65 * 6e4) {
7521
+ publish("policy_stale", target);
7522
+ return;
7523
+ }
7524
+ if (!target) {
7525
+ publish("unapproved", null);
7526
+ return;
7527
+ }
7528
+ const order = persistence.compareExactVersions(policy.currentVersion, target);
7529
+ if (order !== -1) {
7530
+ publish(order === null ? "policy_stale" : "current", target);
7531
+ return;
7532
+ }
7533
+ if (!deps.idle()) {
7534
+ publish("waiting_idle", target);
7535
+ return;
7536
+ }
7537
+ running = true;
7538
+ publish("installing", target);
7539
+ try {
7540
+ const code = await deps.install(target);
7541
+ blocked = code === "blocked";
7542
+ publish(
7543
+ blocked ? "manual_required" : code === 0 ? "installed" : "failed",
7544
+ target,
7545
+ blocked ? "installer_termination_unconfirmed" : code === 0 ? void 0 : "npm_install_failed"
7546
+ );
7547
+ } catch {
7548
+ publish("failed", target, "npm_install_failed");
7549
+ } finally {
7550
+ running = false;
7551
+ }
7552
+ }
7553
+ async function tick() {
7554
+ if (operation || blocked) return;
7555
+ operation = "tick";
7556
+ try {
7557
+ await evaluate();
7558
+ } finally {
7559
+ operation = null;
7560
+ }
7561
+ }
7562
+ async function refresh() {
7563
+ if (operation || blocked) return;
7564
+ operation = "refresh";
7565
+ try {
7566
+ const next = await deps.policy();
7567
+ if (!next) return;
7568
+ policy = next;
7569
+ await evaluate();
7570
+ } finally {
7571
+ operation = null;
7572
+ }
7573
+ }
7574
+ async function retry(version) {
7575
+ if (operation || blocked) return { error: blocked ? "manual_recovery_required" : "update_in_progress" };
7576
+ if (typeof version !== "string" || outcome?.state !== "failed" || outcome.version !== version) {
7577
+ return { error: "no_matching_failed_update" };
7578
+ }
7579
+ operation = "retry";
7580
+ try {
7581
+ const next = await deps.policy();
7582
+ if (!next || next.autoUpdateVersion !== version || now() - next.checkedAt > 65 * 6e4) {
7583
+ return { error: "update_policy_changed_or_unavailable" };
7584
+ }
7585
+ if (!await deps.enabled()) return { error: "auto_update_disabled" };
7586
+ policy = next;
7587
+ outcome = null;
7588
+ handoverHold = null;
7589
+ publish("waiting_idle", version);
7590
+ setTimeout(() => {
7591
+ void tick().catch(() => publish("policy_stale", version));
7592
+ }, 0);
7593
+ return { accepted: true };
7594
+ } finally {
7595
+ operation = null;
7596
+ }
7597
+ }
7598
+ async function withHandover(task) {
7599
+ if (operation || blocked) return;
7600
+ operation = "handover";
7601
+ try {
7602
+ await task();
7603
+ } finally {
7604
+ operation = null;
7605
+ }
7606
+ }
7607
+ function holdHandover(reason) {
7608
+ handoverHold = { reason, at: now() };
7609
+ publish("failed", policy?.autoUpdateVersion ?? null, "handover_preflight_failed");
7610
+ }
7611
+ return { refresh, retry, tick, withHandover, holdHandover, isRunning: () => running };
7612
+ }
7613
+
7341
7614
  const CLAUDE_KEYCHAIN_SERVICE_PREFIX = "Claude Code";
7342
7615
  function keychainIdentity(env = process.env, deps = {}) {
7343
7616
  const home = deps.home ?? os.homedir();
@@ -7401,7 +7674,7 @@ function parseJsonObject(text) {
7401
7674
  return null;
7402
7675
  }
7403
7676
  }
7404
- function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DObePs_K.cjs', document.baseURI).href))).resolve(s)) {
7677
+ function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-5b9YhV-1.cjs', document.baseURI).href))).resolve(s)) {
7405
7678
  const platformArch = `${platform}-${arch}`;
7406
7679
  const candidates = [
7407
7680
  `@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
@@ -8614,45 +8887,7 @@ async function preflightNewBundle(bundlePath) {
8614
8887
  }
8615
8888
  }
8616
8889
  }
8617
- function withHandoverHold(state, reason) {
8618
- if (!state) return state;
8619
- return reason ? { ...state, handoverHold: { reason, at: Date.now() } } : { ...state, handoverHold: null };
8620
- }
8621
8890
  const HANDOVER_PREFLIGHT_TIMEOUT_MS = 3e4;
8622
- function runNpmInstall(args) {
8623
- return new Promise((resolve) => {
8624
- let settled = false;
8625
- const done = (code) => {
8626
- if (!settled) {
8627
- settled = true;
8628
- resolve(code);
8629
- }
8630
- };
8631
- let child;
8632
- try {
8633
- child = node_child_process.spawn("npm", args, { stdio: "ignore", env: process.env });
8634
- } catch {
8635
- done(null);
8636
- return;
8637
- }
8638
- const timer = setTimeout(() => {
8639
- try {
8640
- child.kill("SIGKILL");
8641
- } catch {
8642
- }
8643
- done(null);
8644
- }, AUTO_UPDATE_INSTALL_TIMEOUT_MS);
8645
- child.on("error", () => {
8646
- clearTimeout(timer);
8647
- done(null);
8648
- });
8649
- child.on("close", (code) => {
8650
- clearTimeout(timer);
8651
- done(code);
8652
- });
8653
- });
8654
- }
8655
- const AUTO_UPDATE_INSTALL_TIMEOUT_MS = 10 * 6e4;
8656
8891
  async function startDaemon() {
8657
8892
  let claudeAuthServiceRef = null;
8658
8893
  let cliUpdateStateRef = null;
@@ -9740,67 +9975,30 @@ async function startDaemon() {
9740
9975
  host: initialMachineMetadata.host
9741
9976
  })).then(() => persistence.logger.debug(`[DAEMON RUN] claimed the machine record for host ${initialMachineMetadata.host}`)).catch((error) => persistence.logger.debug("[DAEMON RUN] could not refresh the recorded host:", error));
9742
9977
  }
9743
- let autoUpdateFailedVersion = null;
9744
- let autoUpdateRunning = false;
9745
- const maybeAutoUpdate = async (cliUpdate) => {
9746
- if (autoUpdateRunning) return;
9747
- const settings = await persistence.readSettings();
9748
- const decision = decideAutoUpdate({
9749
- enabled: (settings.cliAutoUpdate ?? "idle") !== "off",
9750
- currentVersion: cliUpdate.currentVersion,
9751
- autoUpdateVersion: cliUpdate.autoUpdateVersion ?? null,
9752
- idle: pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals(),
9753
- failedVersion: autoUpdateFailedVersion
9754
- });
9755
- if (decision.action === "skip") {
9756
- persistence.logger.debug(`[DAEMON RUN] auto-update skipped: ${decision.reason}`);
9757
- return;
9758
- }
9759
- autoUpdateRunning = true;
9760
- const target = decision.version;
9761
- const report = (state, detail) => {
9762
- cliUpdateStateRef = { ...cliUpdate, autoUpdate: { state, version: target, detail, at: Date.now() } };
9763
- void apiMachine.setCliUpdateState(cliUpdateStateRef);
9764
- };
9765
- persistence.logger.warn(`[DAEMON RUN] auto-updating CLI to ${target} (machine idle)`);
9766
- report("installing");
9767
- try {
9768
- const code = await runNpmInstall(autoUpdateInstallArgs(target));
9769
- if (code === 0) {
9770
- persistence.logger.warn(`[DAEMON RUN] auto-update installed ${target}; handover follows once it verifies`);
9771
- report("installed");
9978
+ let lastUpdateLog = "";
9979
+ const updateController = createUpdateController({
9980
+ policy: () => persistence.fetchCliUpdateState(persistence.configuration.serverUrl, persistence.packageJson.version),
9981
+ enabled: async () => ((await persistence.readSettings()).cliAutoUpdate ?? "idle") !== "off",
9982
+ idle: () => pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals(),
9983
+ install: installCliSafely,
9984
+ publish: (state) => {
9985
+ cliUpdateStateRef = state;
9986
+ fileState = { ...fileState, cliUpdate: state };
9987
+ persistence.writeDaemonState(fileState);
9988
+ apiMachine.setCliUpdateState(state);
9989
+ const logKey = JSON.stringify([state.autoUpdate?.state, state.autoUpdate?.version, state.autoUpdate?.detail]);
9990
+ if (logKey !== lastUpdateLog) {
9991
+ lastUpdateLog = logKey;
9992
+ persistence.logger.warn(`[DAEMON RUN] CLI update: ${state.autoUpdate?.state} target=${state.autoUpdate?.version ?? "none"} detail=${state.autoUpdate?.detail ?? "none"}`);
9772
9993
  } else {
9773
- autoUpdateFailedVersion = target;
9774
- persistence.logger.warn(`[DAEMON RUN] auto-update of ${target} failed with exit ${code}`);
9775
- report("failed", `npm exited ${code}`);
9994
+ persistence.logger.debug("[DAEMON RUN] CLI update policy refreshed");
9776
9995
  }
9777
- } catch (error) {
9778
- autoUpdateFailedVersion = target;
9779
- persistence.logger.warn(`[DAEMON RUN] auto-update of ${target} could not run:`, error);
9780
- report("failed", String(error).slice(0, 200));
9781
- } finally {
9782
- autoUpdateRunning = false;
9783
- }
9784
- };
9785
- let cliUpdateCheckRunning = false;
9786
- const refreshCliUpdate = async () => {
9787
- if (cliUpdateCheckRunning) return;
9788
- cliUpdateCheckRunning = true;
9789
- try {
9790
- const cliUpdate = await persistence.fetchCliUpdateState(persistence.configuration.serverUrl, persistence.packageJson.version);
9791
- if (!cliUpdate) return;
9792
- fileState = { ...fileState, cliUpdate };
9793
- persistence.writeDaemonState(fileState);
9794
- cliUpdateStateRef = cliUpdate;
9795
- apiMachine.setCliUpdateState(cliUpdate);
9796
- persistence.logger.debug(`[DAEMON RUN] CLI update policy checked: ${cliUpdate.status} (recommended=${cliUpdate.recommendedVersion ?? "none"}, minimum=${cliUpdate.minimumVersion ?? "none"})`);
9797
- await maybeAutoUpdate(cliUpdate);
9798
- } catch (error) {
9799
- persistence.logger.debug("[DAEMON RUN] CLI update policy check failed:", error);
9800
- } finally {
9801
- cliUpdateCheckRunning = false;
9802
9996
  }
9803
- };
9997
+ });
9998
+ apiMachine.setCliUpdateRetryHandler(updateController.retry);
9999
+ const refreshCliUpdate = () => updateController.refresh().catch((error) => {
10000
+ persistence.logger.debug("[DAEMON RUN] CLI update policy check failed:", error);
10001
+ });
9804
10002
  void refreshCliUpdate();
9805
10003
  const cliUpdateIntervalMs = persistence.resolveCliUpdateCheckInterval(process.env.HAPPY_CLI_UPDATE_CHECK_INTERVAL);
9806
10004
  if (process.env.HAPPY_CLI_UPDATE_CHECK_INTERVAL && cliUpdateIntervalMs === persistence.DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS && process.env.HAPPY_CLI_UPDATE_CHECK_INTERVAL.trim() !== String(persistence.DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS)) {
@@ -9808,77 +10006,79 @@ async function startDaemon() {
9808
10006
  }
9809
10007
  const cliUpdateInterval = setInterval(() => void refreshCliUpdate(), cliUpdateIntervalMs);
9810
10008
  const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
9811
- let heartbeatRunning = false;
9812
- const restartOnStaleVersionAndHeartbeat = setInterval(async () => {
9813
- if (heartbeatRunning) {
9814
- return;
9815
- }
9816
- heartbeatRunning = true;
9817
- if (process.env.DEBUG) {
9818
- persistence.logger.debug(`[DAEMON RUN] Health check started at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
9819
- }
9820
- for (const [pid, _] of pidToTrackedSession.entries()) {
9821
- try {
9822
- process.kill(pid, 0);
9823
- } catch (error) {
9824
- persistence.logger.debug(`[DAEMON RUN] Finalizing stale session with PID ${pid} (process no longer exists)`);
9825
- onChildExited(pid);
10009
+ const restartOnStaleVersionAndHeartbeat = setInterval(serialTask(async () => {
10010
+ try {
10011
+ if (process.env.DEBUG) {
10012
+ persistence.logger.debug(`[DAEMON RUN] Health check started at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
9826
10013
  }
9827
- }
9828
- let bundleReplaced = false;
9829
- if (initialBundleMtimeMs > 0) {
9830
- try {
9831
- const currentMtimeMs = fs$1.statSync(bundlePath).mtimeMs;
9832
- bundleReplaced = currentMtimeMs !== initialBundleMtimeMs;
9833
- } catch {
10014
+ for (const [pid, _] of pidToTrackedSession.entries()) {
10015
+ try {
10016
+ process.kill(pid, 0);
10017
+ } catch (error) {
10018
+ persistence.logger.debug(`[DAEMON RUN] Finalizing stale session with PID ${pid} (process no longer exists)`);
10019
+ onChildExited(pid);
10020
+ }
9834
10021
  }
9835
- }
9836
- if (bundleReplaced) {
9837
- const preflight = await preflightNewBundle(bundlePath);
9838
- if (preflight.action === "hold") {
9839
- if (lastHandoverHold !== preflight.reason) {
9840
- lastHandoverHold = preflight.reason;
9841
- persistence.logger.warn(`[DAEMON RUN] holding handover: ${preflight.reason}`);
9842
- void apiMachine.setCliUpdateState(withHandoverHold(cliUpdateStateRef, preflight.reason));
10022
+ await updateController.tick();
10023
+ await updateController.withHandover(async () => {
10024
+ let bundleReplaced = false;
10025
+ if (initialBundleMtimeMs > 0) {
10026
+ try {
10027
+ const currentMtimeMs = fs$1.statSync(bundlePath).mtimeMs;
10028
+ bundleReplaced = currentMtimeMs !== initialBundleMtimeMs;
10029
+ } catch {
10030
+ }
9843
10031
  }
9844
- return;
10032
+ if (bundleReplaced && !updateController.isRunning() && pidToTrackedSession.size === 0 && !apiMachine.hasLiveTerminals()) {
10033
+ const preflight = await preflightNewBundle(bundlePath);
10034
+ if (preflight.action === "hold") {
10035
+ if (lastHandoverHold !== preflight.reason) {
10036
+ lastHandoverHold = preflight.reason;
10037
+ persistence.logger.warn(`[DAEMON RUN] holding handover: ${preflight.reason}`);
10038
+ }
10039
+ updateController.holdHandover(preflight.reason);
10040
+ return;
10041
+ }
10042
+ if (updateController.isRunning() || pidToTrackedSession.size > 0 || apiMachine.hasLiveTerminals()) return;
10043
+ lastHandoverHold = null;
10044
+ persistence.logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
10045
+ clearInterval(restartOnStaleVersionAndHeartbeat);
10046
+ clearInterval(cliUpdateInterval);
10047
+ claudeAuthService.stop();
10048
+ apiMachine.shutdown();
10049
+ await stopControlServer();
10050
+ await cleanupDaemonState();
10051
+ await persistence.releaseDaemonLock(daemonLockHandle);
10052
+ await stopCaffeinate();
10053
+ try {
10054
+ spawnHappyCLI(["daemon", "start"], {
10055
+ detached: true,
10056
+ stdio: "ignore"
10057
+ });
10058
+ } catch (error) {
10059
+ persistence.logger.debug("[DAEMON RUN] Failed to spawn new daemon, this is quite likely to happen during integration tests as we are cleaning out dist/ directory", error);
10060
+ }
10061
+ process.exit(0);
10062
+ }
10063
+ });
10064
+ const daemonState = await persistence.readDaemonState();
10065
+ if (daemonState && daemonState.pid !== process.pid) {
10066
+ persistence.logger.debug("[DAEMON RUN] Somehow a different daemon was started without killing us. We should kill ourselves.");
10067
+ requestShutdown("exception", "A different daemon was started without killing us. We should kill ourselves.");
9845
10068
  }
9846
- lastHandoverHold = null;
9847
- persistence.logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
9848
- clearInterval(restartOnStaleVersionAndHeartbeat);
9849
- clearInterval(cliUpdateInterval);
9850
- claudeAuthService.stop();
9851
- apiMachine.shutdown();
9852
- await stopControlServer();
9853
- await cleanupDaemonState();
9854
- await persistence.releaseDaemonLock(daemonLockHandle);
9855
- await stopCaffeinate();
9856
10069
  try {
9857
- spawnHappyCLI(["daemon", "start"], {
9858
- detached: true,
9859
- stdio: "ignore"
9860
- });
10070
+ fileState = withDaemonHeartbeat(fileState, (/* @__PURE__ */ new Date()).toLocaleString());
10071
+ persistence.writeDaemonState(fileState);
10072
+ if (process.env.DEBUG) {
10073
+ persistence.logger.debug(`[DAEMON RUN] Health check completed at ${fileState.lastHeartbeat}`);
10074
+ }
9861
10075
  } catch (error) {
9862
- persistence.logger.debug("[DAEMON RUN] Failed to spawn new daemon, this is quite likely to happen during integration tests as we are cleaning out dist/ directory", error);
9863
- }
9864
- process.exit(0);
9865
- }
9866
- const daemonState = await persistence.readDaemonState();
9867
- if (daemonState && daemonState.pid !== process.pid) {
9868
- persistence.logger.debug("[DAEMON RUN] Somehow a different daemon was started without killing us. We should kill ourselves.");
9869
- requestShutdown("exception", "A different daemon was started without killing us. We should kill ourselves.");
9870
- }
9871
- try {
9872
- fileState = withDaemonHeartbeat(fileState, (/* @__PURE__ */ new Date()).toLocaleString());
9873
- persistence.writeDaemonState(fileState);
9874
- if (process.env.DEBUG) {
9875
- persistence.logger.debug(`[DAEMON RUN] Health check completed at ${fileState.lastHeartbeat}`);
10076
+ persistence.logger.debug("[DAEMON RUN] Failed to write heartbeat", error);
9876
10077
  }
9877
10078
  } catch (error) {
9878
- persistence.logger.debug("[DAEMON RUN] Failed to write heartbeat", error);
10079
+ persistence.logger.debug("[DAEMON RUN] Heartbeat failed:", error);
9879
10080
  }
9880
- heartbeatRunning = false;
9881
- }, heartbeatIntervalMs);
10081
+ }), heartbeatIntervalMs);
9882
10082
  const cleanupAndShutdown = async (source, errorMessage) => {
9883
10083
  persistence.logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
9884
10084
  if (restartOnStaleVersionAndHeartbeat) {
@@ -11318,7 +11518,7 @@ function registerSideQuestionHandler(rpc, deps) {
11318
11518
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
11319
11519
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
11320
11520
  const run = deps.run ?? (async (input) => {
11321
- const { query } = await Promise.resolve().then(function () { return require('./index-Vjo1jIJH.cjs'); });
11521
+ const { query } = await Promise.resolve().then(function () { return require('./index-DWBKJ5zy.cjs'); });
11322
11522
  return runSideQuestion(query, input);
11323
11523
  });
11324
11524
  const slots = /* @__PURE__ */ new Map();
@@ -11420,7 +11620,7 @@ const MAX_TITLE_CHARS = 60;
11420
11620
  const GENERATION_TIMEOUT_MS = 3e4;
11421
11621
  function resolveClaudeBinary() {
11422
11622
  try {
11423
- 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-DObePs_K.cjs', document.baseURI).href)));
11623
+ 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-5b9YhV-1.cjs', document.baseURI).href)));
11424
11624
  const utilsPath = path.resolve(path.join(persistence.projectPath(), "scripts", "claude_version_utils.cjs"));
11425
11625
  const { getClaudeCliPath } = require$1(utilsPath);
11426
11626
  const path$1 = getClaudeCliPath();
@@ -13637,9 +13837,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
13637
13837
  };
13638
13838
  }
13639
13839
 
13640
- 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-DObePs_K.cjs', document.baseURI).href)));
13840
+ 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-5b9YhV-1.cjs', document.baseURI).href)));
13641
13841
  const __dirname$1 = path.dirname(__filename$1);
13642
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-DObePs_K.cjs', document.baseURI).href)));
13842
+ const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-5b9YhV-1.cjs', document.baseURI).href)));
13643
13843
  const PRISMA_QUERY_ENGINE_FILES = {
13644
13844
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
13645
13845
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -16269,7 +16469,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16269
16469
  process.exit(0);
16270
16470
  } else if (subcommand === "install-terminal-hooks") {
16271
16471
  try {
16272
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-DehsXLmV.cjs'); });
16472
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-DWCa5j7u.cjs'); });
16273
16473
  const command = parseTerminalHooksArgs(args.slice(1));
16274
16474
  if (command.action === "help") {
16275
16475
  console.log(TERMINAL_HOOKS_HELP);
@@ -16286,7 +16486,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16286
16486
  }
16287
16487
  } else if (subcommand === "spawn") {
16288
16488
  try {
16289
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-D3cwBlWI.cjs'); });
16489
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-BtV9dU1i.cjs'); });
16290
16490
  await handleSpawnCommand(args.slice(1));
16291
16491
  } catch (error) {
16292
16492
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16298,7 +16498,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16298
16498
  return;
16299
16499
  } else if (subcommand === "sessions") {
16300
16500
  try {
16301
- const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-DW4lrLOp.cjs'); });
16501
+ const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-DXYxV6Np.cjs'); });
16302
16502
  await handleSessionsCommand(args.slice(1));
16303
16503
  } catch (error) {
16304
16504
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16310,7 +16510,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16310
16510
  return;
16311
16511
  } else if (subcommand === "send") {
16312
16512
  try {
16313
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-Cq0NZCn8.cjs'); });
16513
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-BINA__Ke.cjs'); });
16314
16514
  await handleSendCommand(args.slice(1));
16315
16515
  } catch (error) {
16316
16516
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16416,9 +16616,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16416
16616
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
16417
16617
  const projectId = args[3];
16418
16618
  try {
16419
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-DxOhdFEx.cjs'); });
16420
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-COOYpI8_.cjs'); }).then(function (n) { return n.persistence; });
16421
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-COOYpI8_.cjs'); }).then(function (n) { return n.api; });
16619
+ const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-4RaHrVSV.cjs'); });
16620
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./types-Cd_tUN3U.cjs'); }).then(function (n) { return n.persistence; });
16621
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-Cd_tUN3U.cjs'); }).then(function (n) { return n.api; });
16422
16622
  let userEmail = void 0;
16423
16623
  try {
16424
16624
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -16449,7 +16649,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16449
16649
  }
16450
16650
  if (geminiSubcommand === "project" && args[2] === "get") {
16451
16651
  try {
16452
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-DxOhdFEx.cjs'); });
16652
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-4RaHrVSV.cjs'); });
16453
16653
  const config = readGeminiLocalConfig();
16454
16654
  if (config.googleCloudProject) {
16455
16655
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -16489,7 +16689,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16489
16689
  process.exit(0);
16490
16690
  }
16491
16691
  try {
16492
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DI8DipUx.cjs'); });
16692
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DmWxHIwS.cjs'); });
16493
16693
  let startedBy = void 0;
16494
16694
  for (let i = 1; i < args.length; i++) {
16495
16695
  if (args[i] === "--started-by") {
@@ -16519,7 +16719,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16519
16719
  }
16520
16720
  };
16521
16721
  try {
16522
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-Bv53Zygv.cjs'); });
16722
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-Beh0UZf8.cjs'); });
16523
16723
  const parsed = parsePiRunnerArgs(args.slice(1));
16524
16724
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
16525
16725
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -16546,7 +16746,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16546
16746
  return;
16547
16747
  } else if (subcommand === "acp") {
16548
16748
  try {
16549
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-Bv53Zygv.cjs'); });
16749
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-Beh0UZf8.cjs'); });
16550
16750
  let startedBy = void 0;
16551
16751
  let verbose = false;
16552
16752
  const acpArgs = [];
@@ -16586,7 +16786,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16586
16786
  return;
16587
16787
  } else if (subcommand === "openclaw") {
16588
16788
  try {
16589
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-CetdjTl-.cjs'); });
16789
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-boyJtPBq.cjs'); });
16590
16790
  let startedBy = void 0;
16591
16791
  let verbose = false;
16592
16792
  let gatewayUrl;
@@ -16637,7 +16837,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16637
16837
  return;
16638
16838
  } else if (subcommand === "mcp" && args.length === 1) {
16639
16839
  try {
16640
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-Cpx9Qlch.cjs'); });
16840
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-BBya5dsZ.cjs'); });
16641
16841
  await handleMcpCommand();
16642
16842
  } catch (error) {
16643
16843
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}