@ouro.bot/cli 0.1.0-alpha.271 → 0.1.0-alpha.272

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.
package/changelog.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.272",
6
+ "changes": [
7
+ "fix(daemon): the daemon's periodic update checker can now actually auto-update itself. The `onUpdate` callback in `daemon.ts` invoked `performStagedRestart` which (a) ran `npm install -g @ouro.bot/cli@{version}` to install to the global node prefix, and (b) tried to find the new code via `node -e \"console.log(require.resolve('@ouro.bot/cli/package.json'))\"` which depends on the daemon process's NODE_PATH. Neither path was actually reachable from the daemon process running out of `~/.ouro-cli/versions/{version}/...`, so every auto-update attempt bailed at `daemon.staged_restart_path_failed` (\"could not resolve new code path\") and the daemon never updated itself. The user had to manually run `ouro up` to pick up new versions. Fix: switch the staged restart to use the version-managed installer (same one the CLI's `up` flow uses) — `installVersion(version)` puts files at `~/.ouro-cli/versions/{version}/node_modules/@ouro.bot/cli` (deterministic, computable), then `activateVersion(version)` flips the CurrentVersion symlink so the next user-driven `ouro up` sees the same version the daemon is running. `performStagedRestart` gained an optional `installNewVersion` dep that production callers inject; the legacy `npm install -g` fallback path is preserved for tests. Two new regression tests in `staged-restart.test.ts`.",
8
+ "fix(cli): `ouro up` no longer prints the 'ouro updated to ...' message twice during npx invocations. Three independent paths can detect a CLI version change: (1) `checkForCliUpdate` finds a newer version on npm and re-execs (cross-process print), (2) `ensureCurrentVersionInstalled` flips the CurrentVersion symlink during `performSystemSetup` because the running package version is newer than what the symlink pointed at (path 2, in-process), (3) `bundle-meta.json`'s stored runtime version differs from the running version (path 3, in-process fallback). Path 3's existing guard `linkedVersionBeforeUp !== currentVersion` correctly skipped path 3 when path 1 had already printed in a different process, but did NOT catch the case where path 2 fired in the same process. Verified live on 2026-04-08: `npx --yes @ouro.bot/cli@alpha up` printed the message twice. Fix: track an in-process `printedUpdateMessage` flag set by path 2; path 3 checks it before printing. Path 3 still acts as a fallback when path 2 didn't fire. New regression test in `daemon-cli-update-flow.test.ts` simulates the npx scenario and asserts exactly one print.",
9
+ "fix(hooks): Claude Code lifecycle hooks (`ouro hook session-start|stop|post-tool-use`) now short-circuit when the daemon socket file doesn't exist, instead of attempting `sendDaemonCommand` and logging two ENOENT errors per hook fire (one for `message.send`, one for `inner.wake`). Every Claude Code event during a daemon-down window was producing noisy `connect ENOENT /tmp/ouroboros-daemon.sock` entries in `ouro.ndjson`, which made it hard to read logs around outages. The hook is best-effort by design — dropping notifications when the daemon is down is correct behavior; we just don't want to log spam about it. New nerves event `daemon.hook_skipped_no_socket` (info level) when the short-circuit fires."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.271",
6
14
  "changes": [
@@ -791,6 +791,27 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
791
791
  }
792
792
  }
793
793
  await performSystemSetup(deps);
794
+ // Track whether we've already printed the "ouro updated to" message
795
+ // this turn so the bundle-meta-fallback path below doesn't double-print.
796
+ // There are three independent paths that can detect "the binary just
797
+ // got newer":
798
+ // 1. checkForCliUpdate found a newer version on npm (above) — this
799
+ // path always re-execs, so the duplicate-detect runs in a
800
+ // different process and the in-process tracker doesn't apply.
801
+ // 2. ensureCurrentVersionInstalled (called from performSystemSetup)
802
+ // flipped the CurrentVersion symlink because the running package
803
+ // version is newer than what the symlink pointed at. This is the
804
+ // common npx path.
805
+ // 3. bundle-meta.json's stored runtime version differs from the
806
+ // running version (fallback for when path 2 couldn't activate
807
+ // but the binary is still newer).
808
+ //
809
+ // Verified live on 2026-04-08: npx download triggered both path 2 and
810
+ // path 3 in the same process and the user saw the message printed twice.
811
+ // Path 3's existing `linkedVersionBeforeUp !== currentVersion` guard
812
+ // catches the cross-process re-exec case (path 1) but not the
813
+ // in-process double-fire case (path 2 + path 3 in the same process).
814
+ let printedUpdateMessage = false;
794
815
  const linkedVersionAfterSetup = deps.getCurrentCliVersion?.() ?? null;
795
816
  const runtimeVersion = (0, bundle_manifest_1.getPackageVersion)();
796
817
  if (linkedVersionBeforeUp && linkedVersionBeforeUp !== runtimeVersion && linkedVersionAfterSetup === runtimeVersion) {
@@ -799,6 +820,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
799
820
  if (changelogCommand) {
800
821
  deps.writeStdout(`review changes with: ${changelogCommand}`);
801
822
  }
823
+ printedUpdateMessage = true;
802
824
  }
803
825
  // Run update hooks before starting daemon so user sees the output
804
826
  (0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
@@ -812,9 +834,14 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
812
834
  // Notify about CLI binary update (npx downloaded a new version).
813
835
  // Skip when the symlink already points to the running version — that
814
836
  // means path 1 (checkForCliUpdate + reExecFromNewVersion) already
815
- // printed the update message before re-exec.
837
+ // printed the update message before re-exec. Also skip when path 2
838
+ // (the symlink-flip detector above) already printed in this same
839
+ // process — otherwise npx invocations print the message twice.
816
840
  /* v8 ignore start -- CLI update detection: tested via daemon-cli-version-detect.test.ts @preserve */
817
- if (previousCliVersion && previousCliVersion !== currentVersion && linkedVersionBeforeUp !== currentVersion) {
841
+ if (!printedUpdateMessage
842
+ && previousCliVersion
843
+ && previousCliVersion !== currentVersion
844
+ && linkedVersionBeforeUp !== currentVersion) {
818
845
  deps.writeStdout(`ouro updated to ${currentVersion} (was ${previousCliVersion})`);
819
846
  const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(previousCliVersion, currentVersion);
820
847
  /* v8 ignore next -- buildChangelogCommand is non-null when previous/current runtime versions differ @preserve */
@@ -1122,12 +1149,29 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
1122
1149
  else {
1123
1150
  content = `[Claude Code hook: ${eventType} in session ${sessionId}]`;
1124
1151
  }
1125
- // Send to the specific agent configured for this hook
1126
- try {
1127
- await deps.sendCommand(deps.socketPath, { kind: "message.send", from: `claude-code:${sessionId}`, to: command.agent, content }).catch(() => { });
1128
- await deps.sendCommand(deps.socketPath, { kind: "inner.wake", agent: command.agent }).catch(() => { });
1152
+ // Send to the specific agent configured for this hook. Short-circuit
1153
+ // when the daemon socket file doesn't exist — otherwise every
1154
+ // Claude Code lifecycle event during a daemon-down window logs two
1155
+ // ENOENT errors in ouro.ndjson (one for message.send, one for
1156
+ // inner.wake) which makes it hard to read the log around outages.
1157
+ // The hook is best-effort: dropping notifications when the daemon
1158
+ // is down is the correct behavior; we just don't want to log spam
1159
+ // about it.
1160
+ if (require("fs").existsSync(deps.socketPath)) {
1161
+ try {
1162
+ await deps.sendCommand(deps.socketPath, { kind: "message.send", from: `claude-code:${sessionId}`, to: command.agent, content }).catch(() => { });
1163
+ await deps.sendCommand(deps.socketPath, { kind: "inner.wake", agent: command.agent }).catch(() => { });
1164
+ }
1165
+ catch { /* daemon not running — silent */ }
1166
+ }
1167
+ else {
1168
+ (0, runtime_1.emitNervesEvent)({
1169
+ component: "daemon",
1170
+ event: "daemon.hook_skipped_no_socket",
1171
+ message: "claude code hook skipped — daemon socket missing",
1172
+ meta: { socketPath: deps.socketPath, eventType, agent: command.agent },
1173
+ });
1129
1174
  }
1130
- catch { /* daemon not running — silent */ }
1131
1175
  // Output for Claude Code hook system
1132
1176
  deps.writeStdout(JSON.stringify({ continue: true }));
1133
1177
  return JSON.stringify({ continue: true });
@@ -54,6 +54,7 @@ const agent_config_v2_1 = require("./hooks/agent-config-v2");
54
54
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
55
55
  const update_checker_1 = require("../versioning/update-checker");
56
56
  const staged_restart_1 = require("../versioning/staged-restart");
57
+ const ouro_version_manager_1 = require("../versioning/ouro-version-manager");
57
58
  const child_process_1 = require("child_process");
58
59
  const pending_1 = require("../../mind/pending");
59
60
  const agent_service_1 = require("./agent-service");
@@ -427,17 +428,34 @@ class OuroDaemon {
427
428
  onUpdate: /* v8 ignore start -- integration: real npm install + process spawn @preserve */ async (result) => {
428
429
  if (!result.latestVersion)
429
430
  return;
431
+ // Install via the version manager (NOT `npm install -g`). The
432
+ // global install path doesn't end up on the daemon process's
433
+ // NODE_PATH, so the previous `require.resolve('@ouro.bot/cli')`
434
+ // -based path lookup always returned null and the staged restart
435
+ // never actually completed. Verified live on 2026-04-08:
436
+ // alpha.268 daemon detected alpha.270 was available, ran the
437
+ // staged restart, and bailed at `staged_restart_path_failed` —
438
+ // meaning the daemon could never auto-update itself and required
439
+ // manual `ouro up` to pick up new versions.
440
+ //
441
+ // Switch to the version-managed layout the CLI itself uses:
442
+ // installVersion(version) puts files at
443
+ // ~/.ouro-cli/versions/{version}/node_modules/@ouro.bot/cli
444
+ // which is a known path we can compute deterministically.
445
+ // Then activateVersion(version) flips the CurrentVersion symlink
446
+ // so the next `ouro up` from the user sees the same version
447
+ // the daemon is running.
448
+ const cliHome = (0, ouro_version_manager_1.getOuroCliHome)();
430
449
  await (0, staged_restart_1.performStagedRestart)(result.latestVersion, {
431
450
  execSync: (cmd) => (0, child_process_1.execSync)(cmd, { stdio: "inherit" }),
432
451
  spawnSync: child_process_1.spawnSync,
433
- resolveNewCodePath: (_version) => {
434
- try {
435
- const resolved = (0, child_process_1.execSync)(`node -e "console.log(require.resolve('@ouro.bot/cli/package.json'))"`, { encoding: "utf-8" }).trim();
436
- return resolved ? path.dirname(resolved) : null;
437
- }
438
- catch {
439
- return null;
440
- }
452
+ installNewVersion: (version) => {
453
+ (0, ouro_version_manager_1.installVersion)(version, {});
454
+ (0, ouro_version_manager_1.activateVersion)(version, {});
455
+ },
456
+ resolveNewCodePath: (version) => {
457
+ const versionPath = path.join(cliHome, "versions", version, "node_modules", "@ouro.bot", "cli");
458
+ return fs.existsSync(versionPath) ? versionPath : null;
441
459
  },
442
460
  gracefulShutdown: () => daemon.stop(),
443
461
  spawnNewDaemon: (entryPath, sock) => {
@@ -45,14 +45,22 @@ async function performStagedRestart(version, deps) {
45
45
  });
46
46
  // Step 1: Install new version
47
47
  try {
48
- deps.execSync(`npm install -g @ouro.bot/cli@${version}`);
48
+ if (deps.installNewVersion) {
49
+ deps.installNewVersion(version);
50
+ }
51
+ else {
52
+ // Backward-compat fallback for callers that haven't migrated to the
53
+ // version-managed installer. Tests use this path with a mocked
54
+ // execSync; production callers inject installNewVersion.
55
+ deps.execSync(`npm install -g @ouro.bot/cli@${version}`);
56
+ }
49
57
  }
50
58
  catch (err) {
51
59
  const errorMessage = err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err);
52
60
  (0, runtime_1.emitNervesEvent)({
53
61
  component: "daemon",
54
62
  event: "daemon.staged_restart_install_failed",
55
- message: "npm install failed",
63
+ message: "install failed",
56
64
  meta: { version, error: errorMessage },
57
65
  });
58
66
  return { ok: false, error: errorMessage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.271",
3
+ "version": "0.1.0-alpha.272",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",