@runuai/host 0.9.121 → 0.9.122

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/lib/sidecar.ts CHANGED
@@ -16,3 +16,43 @@ export function sidecarTaskId(): string | null {
16
16
  export function sidecarMode(): boolean {
17
17
  return sidecarTaskId() !== null;
18
18
  }
19
+
20
+ /**
21
+ * A sidecar is ready to run its task only once it holds a lease AND the
22
+ * credential store has been reconciled under it — before that there is no
23
+ * engine account, GitHub token, or project env on the machine, and a task-up
24
+ * that raced the first grant would clone without credentials and spawn
25
+ * agents that cannot sign in (the dispatch arrives the moment the bridge
26
+ * connects; the lease answer is one round trip behind it). Resolved by
27
+ * main.ts after the first grant's reconciles settle; never on an owned host.
28
+ */
29
+ let markReady: (() => void) | null = null;
30
+ let ready = false;
31
+ export const sidecarReady: Promise<void> = new Promise<void>((resolve) => {
32
+ markReady = resolve;
33
+ });
34
+ export function markSidecarReady(): void {
35
+ if (ready) return;
36
+ ready = true;
37
+ markReady?.();
38
+ markReady = null;
39
+ }
40
+ export function isSidecarReady(): boolean {
41
+ return ready;
42
+ }
43
+ /** How long a task-up waits for the lease before giving up. */
44
+ export const SIDECAR_READY_TIMEOUT_MS = 3 * 60_000;
45
+ export function awaitSidecarReady(timeoutMs = SIDECAR_READY_TIMEOUT_MS): Promise<void> {
46
+ if (!sidecarMode() || ready) return Promise.resolve();
47
+ return new Promise<void>((resolve, reject) => {
48
+ const timer = setTimeout(
49
+ () => reject(new Error("the task's machine has no credentials yet (no lease granted)")),
50
+ timeoutMs,
51
+ );
52
+ timer.unref?.();
53
+ void sidecarReady.then(() => {
54
+ clearTimeout(timer);
55
+ resolve();
56
+ });
57
+ });
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.121",
3
+ "version": "0.9.122",
4
4
  "description": "Uai host — runs ephemeral AI tasks in containers on a machine you control.",
5
5
  "license": "MIT",
6
6
  "author": "Uai Tech <team@runuai.com>",
package/src/index.ts CHANGED
@@ -185,6 +185,7 @@ export type {
185
185
  PreviewPortRuntime,
186
186
  } from "../lib/preview-ports";
187
187
  import { isEnvironmentTransportProvider } from "../lib/task-environment/types";
188
+ import { awaitSidecarReady } from "../lib/sidecar";
188
189
 
189
190
  export const hostEvents: HostEventStream = {
190
191
  subscribe(handler) {
@@ -824,6 +825,9 @@ export const hostCommands: HostCommands = {
824
825
  "GitHub is connected on this host, but Uai could not prepare its credential for Git. Retry the task; if it persists, reconnect GitHub on this host.",
825
826
  );
826
827
  }
828
+ // ADR-133 sidecar: nothing to clone or spawn with until the lease
829
+ // has landed and the store is reconciled under it.
830
+ await awaitSidecarReady();
827
831
  // Launch screen: the first stage boundary. A machine-backed task
828
832
  // spends the next minute launching an EC2 instance; a container
829
833
  // task goes straight to preparing its workspace.
package/src/main.ts CHANGED
@@ -250,7 +250,7 @@ import {
250
250
  } from "./protocol";
251
251
  import { isEnvironmentTransportProvider } from "../lib/task-environment/types";
252
252
  import { LeaseManager } from "../lib/lease";
253
- import { sidecarMode, sidecarTaskId } from "../lib/sidecar";
253
+ import { markSidecarReady, sidecarMode, sidecarTaskId } from "../lib/sidecar";
254
254
  import { declareAgentClisReady } from "../lib/standard-image";
255
255
 
256
256
  setContainerRuntimeOperationalCheck(() => containerRuntimeProblem() === null);
@@ -464,8 +464,8 @@ configureEngineAccountStore({ hostId });
464
464
  /** The credential-store reconciles. At boot on a host with its own AWS
465
465
  * identity; after the first lease on a sidecar (ADR-133), which has no
466
466
  * credentials before that. */
467
- function reconcileStores(): void {
468
- void reconcileEngineAccountsFromStore({ hostId })
467
+ function reconcileStores(): Promise<void> {
468
+ const engineAccounts = reconcileEngineAccountsFromStore({ hostId })
469
469
  .then((r) => {
470
470
  if (r.skipped) return;
471
471
  console.log(
@@ -477,7 +477,7 @@ function reconcileStores(): void {
477
477
  `[engine-accounts] credential store reconcile failed: ${error instanceof Error ? error.message : String(error)}`,
478
478
  );
479
479
  });
480
- void reconcileUserCredentialsFromStore()
480
+ const userCredentials = reconcileUserCredentialsFromStore()
481
481
  .then((r) => {
482
482
  if (r.skipped) return;
483
483
  console.log(
@@ -489,7 +489,7 @@ function reconcileStores(): void {
489
489
  `[user-credentials] credential store reconcile failed: ${error instanceof Error ? error.message : String(error)}`,
490
490
  );
491
491
  });
492
- void reconcileProjectEnvFromStore()
492
+ const projectEnv = reconcileProjectEnvFromStore()
493
493
  .then((r) => {
494
494
  if (r.skipped) return;
495
495
  console.log(
@@ -501,6 +501,7 @@ function reconcileStores(): void {
501
501
  `[project-env] credential store reconcile failed: ${error instanceof Error ? error.message : String(error)}`,
502
502
  );
503
503
  });
504
+ return Promise.all([engineAccounts, userCredentials, projectEnv]).then(() => undefined);
504
505
  }
505
506
  // Image + agent-CLI maintenance runs where the compute is owned: an owned
506
507
  // host keeps its own image current on an hourly schedule (a task-up reuses
@@ -527,7 +528,7 @@ configureTokenBroker({ repush: repushBrokeredCopies });
527
528
  if (!sidecarMode()) {
528
529
  startTokenBrokerLoop();
529
530
  startCredentialStorePickup({ repush: repushBrokeredCopies });
530
- reconcileStores();
531
+ void reconcileStores();
531
532
  }
532
533
  /**
533
534
  * ADR-133 sidecar: the task-scoped AWS lease. Requested when the bridge is
@@ -545,7 +546,12 @@ const leaseManager: LeaseManager | null = sidecarMode()
545
546
  },
546
547
  onFirstGrant: () => {
547
548
  console.log("[lease] granted — reconciling the credential store");
548
- reconcileStores();
549
+ void reconcileStores().finally(() => {
550
+ // Task-up waits for this: only now are engine accounts, GitHub
551
+ // tokens, and project env on the machine (lib/sidecar.ts).
552
+ markSidecarReady();
553
+ console.log("[lease] credential store reconciled — the task may start");
554
+ });
549
555
  startCredentialStorePickup({ repush: repushBrokeredCopies });
550
556
  },
551
557
  onGrant: (lease) => {