@cydm/happy-elves 0.1.0-beta.633 → 0.1.0-beta.634

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/README.md CHANGED
@@ -6,8 +6,8 @@ Public beta CLI for Happy Elves.
6
6
 
7
7
  Run once in the existing user account; it detects %USERPROFILE%\.happy-elves\hosted, keeps the current daemon and sessions running, installs the independent updater, and registers per-user startup.
8
8
 
9
- npm.cmd install --prefix $HOME\.happy-elves\management-entry --ignore-scripts --no-audit --no-fund --package-lock=false @cydm/happy-elves@0.1.0-beta.633
10
- npm.cmd exec --yes --package @cydm/happy-elves@0.1.0-beta.633 -- happy-elves-updater install --state-root $HOME\.happy-elves\hosted --autostart
9
+ npm.cmd install --prefix $HOME\.happy-elves\management-entry --ignore-scripts --no-audit --no-fund --package-lock=false @cydm/happy-elves@0.1.0-beta.634
10
+ npm.cmd exec --yes --package @cydm/happy-elves@0.1.0-beta.634 -- happy-elves-updater install --state-root $HOME\.happy-elves\hosted --autostart
11
11
 
12
12
  The same updater entrypoint works on macOS after selecting a permanent user prefix.
13
13
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-cli",
3
- "version": "0.1.0-beta.633",
3
+ "version": "0.1.0-beta.634",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -748,6 +748,9 @@ export async function connectOnce(config) {
748
748
  token: config.machineToken,
749
749
  nodeKind: "machine",
750
750
  nodeId: config.machineId,
751
+ // The daemon only needs presence, manifests and machine commands.
752
+ // Full retained transcripts can exceed the Relay send budget.
753
+ snapshotMode: "routing",
751
754
  };
752
755
  ws.send(JSON.stringify(hello));
753
756
  });
@@ -776,7 +779,7 @@ export async function connectOnce(config) {
776
779
  await appendUnscopedRelayError(config, "DAEMON_COMMAND_FAILED", error);
777
780
  });
778
781
  });
779
- ws.addEventListener("close", () => {
782
+ ws.addEventListener("close", (event) => {
780
783
  authenticated = false;
781
784
  clearActiveRelaySocket(ws);
782
785
  if (loopTimer)
@@ -785,7 +788,7 @@ export async function connectOnce(config) {
785
788
  clearInterval(scheduleTimer);
786
789
  if (orchestrationTimer)
787
790
  clearInterval(orchestrationTimer);
788
- console.log("Relay connection closed; reconnecting...");
791
+ console.log(`Relay connection closed${event.code ? ` (code ${event.code})` : ""}; reconnecting...`);
789
792
  resolve();
790
793
  });
791
794
  ws.addEventListener("error", () => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.633",
3
+ "version": "0.1.0-beta.634",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.633",
3
+ "version": "0.1.0-beta.634",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
- "buildId": "02be6bbf307f8df0ffc87f57b3dcf919",
6
- "sourceCommit": "5c8b2c6423509ed2c89e9778fa054af1ddbe7495",
5
+ "buildId": "8ddea898d758800d92712964ca969e81",
6
+ "sourceCommit": "5619fccb7a9bfaa976dd1384e1a399f6707889ec",
7
7
  "sourceDirty": false,
8
8
  "type": "module",
9
9
  "bin": {
@@ -41,6 +41,8 @@ class CodexHeadlessRuntimeProvider {
41
41
  sidechat: { strategy: "native", forkIsolation: "clean" },
42
42
  };
43
43
  clients = new Map();
44
+ connectingClients = new Map();
45
+ disposing = false;
44
46
  // Native history is read through a short-lived app-server so a long-lived
45
47
  // writer connection cannot replay its cached thread snapshot after an
46
48
  // external Codex CLI turn. The reader is never used for resume/start/turn
@@ -702,10 +704,11 @@ class CodexHeadlessRuntimeProvider {
702
704
  await active.client.request("turn/steer", { threadId: active.codexThreadId, expectedTurnId: codexTurnId, input: input.input });
703
705
  }
704
706
  async dispose() {
707
+ this.disposing = true;
705
708
  const active = [...this.activeTurnsByRuntimeTurn.values()];
706
709
  this.failActiveTurns(new Error("Codex app-server disposed."));
707
710
  await Promise.all(active.map((turn) => turn.queue.catch(() => undefined)));
708
- const clients = [...new Set(this.clients.values())];
711
+ const clients = [...new Set([...this.clients.values(), ...[...this.connectingClients.values()].map((entry) => entry.client)])];
709
712
  this.clients.clear();
710
713
  this.nativeHistoryRefreshPending.clear();
711
714
  await Promise.all(clients.map((client) => client.close()));
@@ -713,9 +716,14 @@ class CodexHeadlessRuntimeProvider {
713
716
  this.pendingSidechatCleanup.clear();
714
717
  }
715
718
  async ensureClient(cwd, clientProfile = "default", clientKey = codexCatalogClientKey(clientProfile)) {
719
+ if (this.disposing)
720
+ throw new HeadlessRuntimeCoreError("INVALID_REQUEST", "Codex app-server provider is disposed.");
716
721
  const existing = this.clients.get(clientKey);
717
722
  if (existing?.isAlive())
718
723
  return existing;
724
+ const connecting = this.connectingClients.get(clientKey);
725
+ if (connecting)
726
+ return await connecting.ready;
719
727
  const client = new CodexAppServerClient({
720
728
  command: this.options.command ?? process.env.HAPPY_ELVES_CODEX_COMMAND ?? "codex",
721
729
  args: codexAppServerArgs(clientProfile),
@@ -738,15 +746,25 @@ class CodexHeadlessRuntimeProvider {
738
746
  this.failActiveTurns(error, client);
739
747
  },
740
748
  });
741
- try {
742
- await client.connect();
743
- }
744
- catch (error) {
745
- await client.close().catch(() => undefined);
746
- throw error;
747
- }
748
- this.clients.set(clientKey, client);
749
- return client;
749
+ const ready = (async () => {
750
+ try {
751
+ await client.connect();
752
+ if (this.disposing)
753
+ throw new HeadlessRuntimeCoreError("INVALID_REQUEST", "Codex app-server provider is disposed.");
754
+ this.clients.set(clientKey, client);
755
+ return client;
756
+ }
757
+ catch (error) {
758
+ await client.close().catch(() => undefined);
759
+ throw error;
760
+ }
761
+ finally {
762
+ if (this.connectingClients.get(clientKey)?.client === client)
763
+ this.connectingClients.delete(clientKey);
764
+ }
765
+ })();
766
+ this.connectingClients.set(clientKey, { client, ready });
767
+ return await ready;
750
768
  }
751
769
  async replaceClientAfterNativeHistoryRefresh(cwd, clientProfile, threadId) {
752
770
  const clientKey = codexClientKeyForThread(threadId, clientProfile);