@runuai/host 0.9.119 → 0.9.121
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/agents/transport.ts +2 -1
- package/lib/browser-testing.ts +2 -1
- package/lib/credential-store.ts +4 -0
- package/lib/lease.ts +216 -0
- package/lib/mcp-gateway.ts +2 -1
- package/lib/orchestrator.ts +17 -6
- package/lib/shared-files-s3.ts +153 -0
- package/lib/sidecar.ts +18 -0
- package/lib/standard-image.ts +7 -0
- package/lib/task-environment/index.ts +27 -6
- package/lib/task-environment/legacy-adoption.ts +2 -1
- package/lib/task-environment/machine-task-up.ts +24 -6
- package/lib/task-environment/self.ts +346 -0
- package/lib/task-environment/types.ts +13 -0
- package/package.json +1 -1
- package/src/cli.ts +7 -2
- package/src/index.ts +5 -4
- package/src/main.ts +115 -47
- package/src/protocol.ts +29 -1
package/src/main.ts
CHANGED
|
@@ -248,6 +248,10 @@ import {
|
|
|
248
248
|
isEngineAccountLabel,
|
|
249
249
|
isEngineConnectKind,
|
|
250
250
|
} from "./protocol";
|
|
251
|
+
import { isEnvironmentTransportProvider } from "../lib/task-environment/types";
|
|
252
|
+
import { LeaseManager } from "../lib/lease";
|
|
253
|
+
import { sidecarMode, sidecarTaskId } from "../lib/sidecar";
|
|
254
|
+
import { declareAgentClisReady } from "../lib/standard-image";
|
|
251
255
|
|
|
252
256
|
setContainerRuntimeOperationalCheck(() => containerRuntimeProblem() === null);
|
|
253
257
|
|
|
@@ -457,24 +461,53 @@ migrateHostDb();
|
|
|
457
461
|
// credential store. Import what this host lacks and upload what was never
|
|
458
462
|
// mirrored, off the startup path — task-up resolves locally either way.
|
|
459
463
|
configureEngineAccountStore({ hostId });
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
)
|
|
471
|
-
|
|
464
|
+
/** The credential-store reconciles. At boot on a host with its own AWS
|
|
465
|
+
* identity; after the first lease on a sidecar (ADR-133), which has no
|
|
466
|
+
* credentials before that. */
|
|
467
|
+
function reconcileStores(): void {
|
|
468
|
+
void reconcileEngineAccountsFromStore({ hostId })
|
|
469
|
+
.then((r) => {
|
|
470
|
+
if (r.skipped) return;
|
|
471
|
+
console.log(
|
|
472
|
+
`[engine-accounts] credential store reconciled: ${r.imported} imported, ${r.uploaded} uploaded, ${r.removed} removed, ${r.errors} errors`,
|
|
473
|
+
);
|
|
474
|
+
})
|
|
475
|
+
.catch((error: unknown) => {
|
|
476
|
+
console.warn(
|
|
477
|
+
`[engine-accounts] credential store reconcile failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
void reconcileUserCredentialsFromStore()
|
|
481
|
+
.then((r) => {
|
|
482
|
+
if (r.skipped) return;
|
|
483
|
+
console.log(
|
|
484
|
+
`[user-credentials] credential store reconciled: ${r.imported} imported, ${r.uploaded} uploaded, ${r.removed} removed, ${r.errors} errors`,
|
|
485
|
+
);
|
|
486
|
+
})
|
|
487
|
+
.catch((error: unknown) => {
|
|
488
|
+
console.warn(
|
|
489
|
+
`[user-credentials] credential store reconcile failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
490
|
+
);
|
|
491
|
+
});
|
|
492
|
+
void reconcileProjectEnvFromStore()
|
|
493
|
+
.then((r) => {
|
|
494
|
+
if (r.skipped) return;
|
|
495
|
+
console.log(
|
|
496
|
+
`[project-env] credential store reconciled: ${r.imported} imported, ${r.uploaded} uploaded, ${r.removed} removed, ${r.errors} errors`,
|
|
497
|
+
);
|
|
498
|
+
})
|
|
499
|
+
.catch((error: unknown) => {
|
|
500
|
+
console.warn(
|
|
501
|
+
`[project-env] credential store reconcile failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
472
505
|
// Image + agent-CLI maintenance runs where the compute is owned: an owned
|
|
473
506
|
// host keeps its own image current on an hourly schedule (a task-up reuses
|
|
474
507
|
// the last pass instead of blocking on one); a Uai-operated host sets
|
|
475
508
|
// UAI_IMAGE_MAINTENANCE=external — a cloud job keeps the payload AMI fresh —
|
|
476
|
-
// and reconciles only once, at start.
|
|
477
|
-
if (env.UAI_IMAGE_MAINTENANCE === "host") {
|
|
509
|
+
// and reconciles only once, at start. A sidecar (ADR-133) has no image.
|
|
510
|
+
if (env.UAI_IMAGE_MAINTENANCE === "host" && !sidecarMode()) {
|
|
478
511
|
const maintenanceTimer = setInterval(() => {
|
|
479
512
|
void ensureStandardImage().catch(() => undefined);
|
|
480
513
|
}, MAINTENANCE_INTERVAL_MS);
|
|
@@ -489,38 +522,50 @@ const repushBrokeredCopies = async (kind: string): Promise<void> => {
|
|
|
489
522
|
await getOrchestrator().refreshEngineAccountCopies(kind);
|
|
490
523
|
};
|
|
491
524
|
configureTokenBroker({ repush: repushBrokeredCopies });
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
525
|
+
// ADR-133: a sidecar never refreshes sign-ins (the broker Lambda does), and
|
|
526
|
+
// reads the store only once it holds a lease — see leaseManager below.
|
|
527
|
+
if (!sidecarMode()) {
|
|
528
|
+
startTokenBrokerLoop();
|
|
529
|
+
startCredentialStorePickup({ repush: repushBrokeredCopies });
|
|
530
|
+
reconcileStores();
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* ADR-133 sidecar: the task-scoped AWS lease. Requested when the bridge is
|
|
534
|
+
* ready, refreshed before expiry; the first grant is what lets the store
|
|
535
|
+
* reconciles and the S3 file mounts run. A denial means the task is no
|
|
536
|
+
* longer ours to run — exit 3 and let the cloud's sweep reclaim the machine.
|
|
537
|
+
*/
|
|
538
|
+
const leaseManager: LeaseManager | null = sidecarMode()
|
|
539
|
+
? new LeaseManager({
|
|
540
|
+
taskId: sidecarTaskId()!,
|
|
541
|
+
send: (frame) => {
|
|
542
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
|
543
|
+
send(ws, frame);
|
|
544
|
+
return true;
|
|
545
|
+
},
|
|
546
|
+
onFirstGrant: () => {
|
|
547
|
+
console.log("[lease] granted — reconciling the credential store");
|
|
548
|
+
reconcileStores();
|
|
549
|
+
startCredentialStorePickup({ repush: repushBrokeredCopies });
|
|
550
|
+
},
|
|
551
|
+
onGrant: (lease) => {
|
|
552
|
+
console.log(
|
|
553
|
+
`[lease] holds until ${new Date(lease.expiresAt).toISOString()}`,
|
|
554
|
+
);
|
|
555
|
+
},
|
|
556
|
+
onDenied: (error) => {
|
|
557
|
+
console.error(`[lease] denied: ${error} — this machine no longer runs its task`);
|
|
558
|
+
process.exit(3);
|
|
559
|
+
},
|
|
560
|
+
})
|
|
561
|
+
: null;
|
|
562
|
+
if (sidecarMode()) declareAgentClisReady();
|
|
518
563
|
// Reconcile a durable retention-off setting before recovery/connect can make
|
|
519
564
|
// crash-left owner-facing terminal history observable again. Correctness
|
|
520
565
|
// tombstones remain intact inside applyTerminalHistoryRetention.
|
|
521
566
|
applyTerminalHistoryRetention();
|
|
522
567
|
try {
|
|
523
|
-
await configureBundledContainerRuntime();
|
|
568
|
+
if (!sidecarMode()) await configureBundledContainerRuntime();
|
|
524
569
|
} catch {
|
|
525
570
|
// A malformed/tampered active generation must never fall back to PATH or an
|
|
526
571
|
// inferred asset layout. Keep Docker-first operation available and surface
|
|
@@ -529,7 +574,17 @@ try {
|
|
|
529
574
|
"[host-agent] signed bundled runtime validation failed; the bundled runtime is disabled",
|
|
530
575
|
);
|
|
531
576
|
}
|
|
532
|
-
const initialRuntime
|
|
577
|
+
const initialRuntime: Awaited<ReturnType<typeof initializeContainerRuntime>> =
|
|
578
|
+
sidecarMode()
|
|
579
|
+
? // ADR-133: a task machine runs its agents directly; nothing here ever
|
|
580
|
+
// dials a container runtime.
|
|
581
|
+
{
|
|
582
|
+
status: "no-runtime",
|
|
583
|
+
preference: "auto",
|
|
584
|
+
message: "sidecar mode — no container runtime on a task machine",
|
|
585
|
+
action: "none: a task machine runs its agents directly",
|
|
586
|
+
}
|
|
587
|
+
: await initializeContainerRuntime();
|
|
533
588
|
// Do not touch the selected daemon merely because detection succeeded. The
|
|
534
589
|
// activation gate below must first prove that every durable TaskEnvironment
|
|
535
590
|
// belongs to this exact provider/endpoint. The manager's first admitted login
|
|
@@ -612,12 +667,16 @@ const stopAgentClisReadySubscription = onAgentClisReady(() =>
|
|
|
612
667
|
void connect();
|
|
613
668
|
// Local browser UI (ADR-028) — same single process, alongside the WSS client.
|
|
614
669
|
// Best-effort: a UI bind failure must not take the host service down.
|
|
615
|
-
|
|
670
|
+
// A task machine has no operator sitting at it (ADR-133): no local UI, no
|
|
671
|
+
// Mac vnode watch.
|
|
672
|
+
if (!sidecarMode()) void startLocalUi();
|
|
616
673
|
// ADR-118 follow-up: vnode-pressure observability (macOS/apple-container
|
|
617
674
|
// hosts). Logs only on band transitions; the sensor caches its sysctl read.
|
|
618
|
-
|
|
619
|
-
vnodePressureTimer
|
|
620
|
-
|
|
675
|
+
if (!sidecarMode()) {
|
|
676
|
+
const vnodePressureTimer = setInterval(() => void logVnodePressure(), 60_000);
|
|
677
|
+
vnodePressureTimer.unref?.();
|
|
678
|
+
void logVnodePressure();
|
|
679
|
+
}
|
|
621
680
|
|
|
622
681
|
async function startLocalUi(): Promise<void> {
|
|
623
682
|
try {
|
|
@@ -717,6 +776,7 @@ function buildCapabilities(): HostCapabilities {
|
|
|
717
776
|
},
|
|
718
777
|
}
|
|
719
778
|
: {}),
|
|
779
|
+
...(sidecarTaskId() ? { sidecar: { taskId: sidecarTaskId()! } } : {}),
|
|
720
780
|
maintenanceReady: areAgentClisReady(),
|
|
721
781
|
mcpGateway: mcpGateway.state(),
|
|
722
782
|
engineLogins: engineLoginManager.capabilities(),
|
|
@@ -1206,6 +1266,7 @@ async function connect(): Promise<void> {
|
|
|
1206
1266
|
reconnectAttempt = 0;
|
|
1207
1267
|
markConnected();
|
|
1208
1268
|
console.log(`[host-agent] connected as ${hostId}`);
|
|
1269
|
+
leaseManager?.reconnected();
|
|
1209
1270
|
setHostObsTag(hostId);
|
|
1210
1271
|
addHostBreadcrumb("bridge", "connected");
|
|
1211
1272
|
|
|
@@ -1405,6 +1466,10 @@ async function connect(): Promise<void> {
|
|
|
1405
1466
|
case "tunnel.close":
|
|
1406
1467
|
closeTunnel(socket, frame.tunnelId, frame.reason);
|
|
1407
1468
|
break;
|
|
1469
|
+
case "lease.grant":
|
|
1470
|
+
case "lease.deny":
|
|
1471
|
+
leaseManager?.handle(frame);
|
|
1472
|
+
break;
|
|
1408
1473
|
case "gh.connect.set": {
|
|
1409
1474
|
const runtimeProblem = containerRuntimeProblem();
|
|
1410
1475
|
if (runtimeProblem) {
|
|
@@ -2141,7 +2206,7 @@ async function resolveTunnelTarget(
|
|
|
2141
2206
|
// operational. After a daemon restart an unrelated local process could bind
|
|
2142
2207
|
// a stale port before recovery refreshes the task's mappings.
|
|
2143
2208
|
const machineTask = getHostTask(frame.taskId);
|
|
2144
|
-
if (machineTask
|
|
2209
|
+
if (machineTask && isEnvironmentTransportProvider(machineTask.environmentProvider)) {
|
|
2145
2210
|
// ADR-121: a machine's address is orchestrator-routable (bridge IP on
|
|
2146
2211
|
// the Linux guinea pig, VPC-private on EC2) — editor and previews dial
|
|
2147
2212
|
// it straight via the handle's ownership-proven route, independent of
|
|
@@ -3100,6 +3165,9 @@ function rejectFrameDuringShutdown(
|
|
|
3100
3165
|
case "tunnel.data":
|
|
3101
3166
|
case "tunnel.requestEnd":
|
|
3102
3167
|
case "tunnel.close":
|
|
3168
|
+
// A lease answer is housekeeping the sidecar still wants during a drain.
|
|
3169
|
+
case "lease.grant":
|
|
3170
|
+
case "lease.deny":
|
|
3103
3171
|
return false;
|
|
3104
3172
|
}
|
|
3105
3173
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -1074,6 +1074,9 @@ export interface HostCapabilities {
|
|
|
1074
1074
|
* Present when the host is configured for them; the cloud shows the
|
|
1075
1075
|
* provider/region on the host page and, eventually, per-task backing
|
|
1076
1076
|
* choice keys off it. */
|
|
1077
|
+
/** ADR-133 sidecar mode: this host IS the task's machine — exactly one
|
|
1078
|
+
* task, no container runtime, tunnels on loopback. */
|
|
1079
|
+
sidecar?: { taskId: string };
|
|
1077
1080
|
machineTasks?: {
|
|
1078
1081
|
provider: string;
|
|
1079
1082
|
region?: string;
|
|
@@ -1695,9 +1698,32 @@ export type CloudToHost =
|
|
|
1695
1698
|
// only entries it never transmitted, replaying nothing.
|
|
1696
1699
|
| { kind: "event.resume"; afterSeq: number | null }
|
|
1697
1700
|
// ADR-103: cumulative — the host trims outbox entries with seq <= seq.
|
|
1698
|
-
| { kind: "event.ack"; seq: number }
|
|
1701
|
+
| { kind: "event.ack"; seq: number }
|
|
1702
|
+
// ADR-133 leases: the cloud's answer to a sidecar's `lease.request`.
|
|
1703
|
+
| { kind: "lease.grant"; opId: string; lease: TaskLease }
|
|
1704
|
+
| { kind: "lease.deny"; opId: string; error: string };
|
|
1699
1705
|
|
|
1700
1706
|
/** One MCP-connection operation (ADR-057), executed by the host. */
|
|
1707
|
+
/**
|
|
1708
|
+
* ADR-133: the short-lived AWS credentials a task machine runs on — an STS
|
|
1709
|
+
* session tagged with the task's workspace and owner, issued by the cloud
|
|
1710
|
+
* over the bridge and held in the sidecar's memory only. `expiresAt` is the
|
|
1711
|
+
* session expiry (epoch ms); the sidecar re-requests before it.
|
|
1712
|
+
*/
|
|
1713
|
+
export interface TaskLease {
|
|
1714
|
+
accessKeyId: string;
|
|
1715
|
+
secretAccessKey: string;
|
|
1716
|
+
sessionToken: string;
|
|
1717
|
+
expiresAt: number;
|
|
1718
|
+
region: string;
|
|
1719
|
+
/** Files bucket and this task's prefixes inside it (S3 shared files). */
|
|
1720
|
+
files: {
|
|
1721
|
+
bucket: string;
|
|
1722
|
+
orgPrefix: string | null;
|
|
1723
|
+
userPrefix: string;
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1701
1727
|
export type McpOp =
|
|
1702
1728
|
// Probe `url` (MCP initialize). No auth → connected. 401 → walk the OAuth
|
|
1703
1729
|
// discovery chain (RFC 9728 → 8414 → 7591), prepare PKCE, and ack
|
|
@@ -1751,6 +1777,8 @@ export type HostToCloud =
|
|
|
1751
1777
|
// a task's containers, so the cloud mirrors the status (currently "stopped").
|
|
1752
1778
|
| { kind: "task.status"; taskId: string; status: string }
|
|
1753
1779
|
| { kind: "ping"; ts: number }
|
|
1780
|
+
// ADR-133: a sidecar asks for (or refreshes) its task-scoped AWS lease.
|
|
1781
|
+
| { kind: "lease.request"; opId: string; taskId: string }
|
|
1754
1782
|
| {
|
|
1755
1783
|
kind: "tunnel.ack";
|
|
1756
1784
|
tunnelId: string;
|