@runuai/host 0.8.26 → 0.8.27
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/standard-image.ts +28 -6
- package/package.json +1 -1
- package/src/index.ts +22 -11
package/lib/standard-image.ts
CHANGED
|
@@ -305,13 +305,22 @@ async function upgradeVolumeAgentClis(): Promise<void> {
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/** Outcome of ensureStandardImage — `error` carries WHY when not ready, so
|
|
309
|
+
* the taskUp path can surface a build failure to the cloud (not just the host
|
|
310
|
+
* log). Callers that only fire-and-forget at boot ignore the return. */
|
|
311
|
+
export interface StandardImageResult {
|
|
312
|
+
ok: boolean;
|
|
313
|
+
error?: string;
|
|
314
|
+
}
|
|
315
|
+
|
|
308
316
|
/**
|
|
309
317
|
* Ensure the standard image and the shared asdf data volume exist, and that the
|
|
310
318
|
* agent CLIs are present inside the volume. Builds the image only when `docker
|
|
311
|
-
* image inspect` fails
|
|
312
|
-
*
|
|
319
|
+
* image inspect` fails or the content hash is stale. Never throws — returns
|
|
320
|
+
* `{ ok, error? }` so the caller decides (boot: fire-and-forget; taskUp:
|
|
321
|
+
* surface the reason).
|
|
313
322
|
*/
|
|
314
|
-
export async function ensureStandardImage(): Promise<
|
|
323
|
+
export async function ensureStandardImage(): Promise<StandardImageResult> {
|
|
315
324
|
// 1. Shared asdf data volume — idempotent.
|
|
316
325
|
const vol = await run("docker", ["volume", "create", ASDF_DATA_VOLUME]);
|
|
317
326
|
if (vol.code !== 0) {
|
|
@@ -321,7 +330,9 @@ export async function ensureStandardImage(): Promise<void> {
|
|
|
321
330
|
);
|
|
322
331
|
// If docker itself is unavailable, the image step will also fail; bail
|
|
323
332
|
// early so we don't double-log a confusing build error.
|
|
324
|
-
if (vol.code === null)
|
|
333
|
+
if (vol.code === null) {
|
|
334
|
+
return { ok: false, error: "Docker is not available on this host." };
|
|
335
|
+
}
|
|
325
336
|
}
|
|
326
337
|
|
|
327
338
|
// 2. Ensure the image is built AND current. "Present" is not enough — a
|
|
@@ -330,6 +341,7 @@ export async function ensureStandardImage(): Promise<void> {
|
|
|
330
341
|
// landed). The build context is content-hashed into an image label;
|
|
331
342
|
// a mismatch triggers a rebuild (layer cache keeps it cheap).
|
|
332
343
|
let imageReady = false;
|
|
344
|
+
let buildError: string | null = null;
|
|
333
345
|
// Install only the optional engines the operator has configured; the flags
|
|
334
346
|
// are build args AND part of the content hash (so a new login rebuilds).
|
|
335
347
|
const engines = configuredOptionalEngines();
|
|
@@ -356,7 +368,7 @@ export async function ensureStandardImage(): Promise<void> {
|
|
|
356
368
|
"[host-agent] docker unavailable; skipping standard image build. " +
|
|
357
369
|
"Tasks will fail until docker is running.",
|
|
358
370
|
);
|
|
359
|
-
return;
|
|
371
|
+
return { ok: false, error: "Docker is not available on this host." };
|
|
360
372
|
}
|
|
361
373
|
const labeledHash = inspect.code === 0 ? inspect.stdout.trim() : null;
|
|
362
374
|
if (inspect.code === 0 && contextHash !== null && labeledHash === contextHash) {
|
|
@@ -387,10 +399,13 @@ export async function ensureStandardImage(): Promise<void> {
|
|
|
387
399
|
console.log(`[host-agent] built standard image ${STANDARD_IMAGE_TAG}`);
|
|
388
400
|
imageReady = true;
|
|
389
401
|
} else {
|
|
402
|
+
buildError =
|
|
403
|
+
build.stderr.trim() ||
|
|
404
|
+
`docker build exited ${build.code ?? "spawn error"}`;
|
|
390
405
|
console.warn(
|
|
391
406
|
`[host-agent] standard image build failed (exit ${build.code ?? "spawn"}); ` +
|
|
392
407
|
"continuing. Tasks needing the image will surface this error.\n" +
|
|
393
|
-
|
|
408
|
+
buildError,
|
|
394
409
|
);
|
|
395
410
|
// A stale-but-working image is better than none.
|
|
396
411
|
imageReady = labeledHash !== null;
|
|
@@ -402,5 +417,12 @@ export async function ensureStandardImage(): Promise<void> {
|
|
|
402
417
|
if (imageReady) {
|
|
403
418
|
await ensureVolumeAgentClis();
|
|
404
419
|
await upgradeVolumeAgentClis();
|
|
420
|
+
return { ok: true };
|
|
405
421
|
}
|
|
422
|
+
return {
|
|
423
|
+
ok: false,
|
|
424
|
+
error:
|
|
425
|
+
buildError ??
|
|
426
|
+
"the standard base image (uai-standard:dev) is not built and no build error was captured.",
|
|
427
|
+
};
|
|
406
428
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { agent, AgentError } from "../lib/agent";
|
|
1
|
+
import { agent, AgentError, toolStderrDetail } from "../lib/agent";
|
|
2
2
|
import { ensureStandardImage } from "../lib/standard-image";
|
|
3
3
|
import { cloneRepo } from "../lib/repo-clone";
|
|
4
4
|
import { handleFilesOp } from "../lib/shared-files";
|
|
@@ -120,16 +120,27 @@ export const hostCommands: HostCommands = {
|
|
|
120
120
|
storeTaskCliSecret(input.task.id, input.task.cliSecret);
|
|
121
121
|
}
|
|
122
122
|
recordHostEvent(input.task.id, "task.created");
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
const result = await wrapAgent(ctx, "taskUp", async () => {
|
|
124
|
+
// Self-heal the standard base image before task-up needs it. The
|
|
125
|
+
// boot-time build is best-effort + one-shot: a host that started before
|
|
126
|
+
// Docker was ready (common on Docker Desktop) never built
|
|
127
|
+
// uai-standard:dev, so every task-up died on a doomed registry pull
|
|
128
|
+
// (live 2026-07-22). This is idempotent — a fast inspect when present,
|
|
129
|
+
// a real build only when missing/stale (Docker is definitely up now, a
|
|
130
|
+
// task just arrived). On a build FAILURE, surface WHY to the cloud (not
|
|
131
|
+
// just the host log — the operator can't read a remote user's machine).
|
|
132
|
+
const img = await ensureStandardImage();
|
|
133
|
+
if (!img.ok) {
|
|
134
|
+
throw new AgentError(
|
|
135
|
+
"STANDARD_IMAGE_UNAVAILABLE",
|
|
136
|
+
img.error
|
|
137
|
+
? `could not prepare the host base image uai-standard:dev — the build failed. ${toolStderrDetail(img.error)}`
|
|
138
|
+
: "could not prepare the host base image uai-standard:dev — see the host log.",
|
|
139
|
+
{},
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return agent.taskUp(input);
|
|
143
|
+
});
|
|
133
144
|
if (result.ok) {
|
|
134
145
|
recordTaskUpResult(input.task.id, result.value);
|
|
135
146
|
recordHostEvent(input.task.id, "task.started");
|