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

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,12 @@
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.271",
6
+ "changes": [
7
+ "fix(daemon): unblock daemon.stop deadlock that hung `ouro up` after a CLI auto-update. When the running daemon's version drifted from the local CLI version, `ensureDaemonRunning` would send `daemon.stop` over the socket, the daemon's command handler would `await this.stop()`, and `stop()` would `await server.close()`. server.close() resolves only after every open connection has closed — but the calling client's connection was the ONE thing keeping the server open: its `flushResponse()` was awaiting THIS function call. Both processes sat in kevent forever. Verified live on 2026-04-08: alpha.268 daemon hung at `daemon.server_end` log line for 5+ minutes after a fresh alpha.270 ouro process sent daemon.stop, and the alpha.270 ouro process hung waiting for the response. The deadlock had existed since the original `await server.close()` line was added (2026-03-05) but was masked for weeks by the half-close behavior in socket-client: the client called `client.end()` after writing its command, which (with `allowHalfOpen: false`) caused node to auto-tear-down the server's writable side, incidentally unblocking server.close() before the response was sent. The fix in #303/#334/#339 (which removed `client.end()` and switched to `allowHalfOpen: true` to stop dropping long-running responses like agent.senseTurn) accidentally exposed the underlying deadlock. Fix: don't `await` server.close() in stop() — just fire it. Once stop() returns, the daemon.stop case returns its response, flushResponse calls connection.end(response), the connection closes, and server.close()'s pending callback fires asynchronously. Includes a new daemon-stop-deadlock.test.ts that uses real net sockets to drive daemon.stop and asserts the response comes back within 2s — the test fails (24s timeout) without the fix and passes (110ms) with it."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.270",
6
12
  "changes": [
@@ -745,9 +745,36 @@ class OuroDaemon {
745
745
  await this.processManager.stopAll();
746
746
  await this.senseManager?.stopAll();
747
747
  if (this.server) {
748
- await new Promise((resolve) => {
749
- this.server?.close(() => resolve());
750
- });
748
+ // DO NOT `await` server.close() here. server.close() resolves only
749
+ // after every open connection has closed. When stop() is invoked
750
+ // from the daemon.stop command handler, the calling client's
751
+ // connection is STILL open — its flushResponse() is currently
752
+ // awaiting THIS function. Awaiting close() creates a deadlock:
753
+ //
754
+ // client → flushResponse → handleRawPayload → daemon.stop case
755
+ // → stop() → await server.close() (waits for client's connection)
756
+ // → client's connection waits for flushResponse to call
757
+ // connection.end() → DEADLOCK
758
+ //
759
+ // Both processes sit in kevent forever. Verified live on
760
+ // 2026-04-08: alpha.268 daemon hung at `daemon.server_end` log
761
+ // line for 5+ minutes after a client sent daemon.stop, while the
762
+ // client (alpha.270 ouro up) hung waiting for the response.
763
+ //
764
+ // This regressed when #303/#334/#339 stopped half-closing the
765
+ // client socket and switched the server to allowHalfOpen: true.
766
+ // Previously, the client called .end() after writing its command,
767
+ // which (with allowHalfOpen: false) caused node to auto-tear-down
768
+ // the server's writable side — incidentally unblocking
769
+ // server.close() before the response was sent. The half-close
770
+ // breakage masked this deadlock; the fix exposed it.
771
+ //
772
+ // Solution: fire close() and let it complete asynchronously. Once
773
+ // stop() returns, the daemon.stop case returns its response,
774
+ // flushResponse() calls connection.end(response), the connection
775
+ // closes, and server.close()'s pending callback fires. The event
776
+ // loop drains and the daemon exits cleanly.
777
+ this.server.close();
751
778
  this.server = null;
752
779
  }
753
780
  /* v8 ignore start -- outlookServer stop: only reachable when the outlook HTTP server successfully bound to port 6876 during start(). Coverage fluctuates locally because a running production daemon holds the port and tests get EADDRINUSE, leaving outlookServer null. In CI the port is free and this block runs. Not worth fighting port contention to cover two trivial lines. @preserve */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.270",
3
+ "version": "0.1.0-alpha.271",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",