@runuai/host 0.9.61 → 0.9.63

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.61",
3
+ "version": "0.9.63",
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>",
@@ -83,6 +83,10 @@ project_env_json=$(printf '%s' "$project_env_json" | jq -c '.')
83
83
  task_dir="$UAI_WORKSPACE_ROOT/tasks/$task_id"
84
84
  task_workspace="$task_dir/workspace"
85
85
  task_uai_dir="$task_dir/.uai"
86
+ # Agents are told to save browser screenshots and other artifacts under
87
+ # /workspace/.uai/attachments/, but nothing created the directory — every
88
+ # first browser_take_screenshot hit ENOENT (found by an agent in the first
89
+ # apple-container task, 2026-08-24). Created with the .uai dir below.
86
90
  projects_root="$UAI_WORKSPACE_ROOT/projects"
87
91
  compose_project=$(compose_project_for_task "$task_id")
88
92
  app_container=$(app_container_for_task "$task_id")
@@ -1279,7 +1283,7 @@ done < <(jq -c '.[]' <<<"$projects_json")
1279
1283
  # uid-501 macOS host. The sanitizer above repairs its rewritten control files
1280
1284
  # on every later resume.
1281
1285
  step "WORKTREE_FAILED" "share task files with container"
1282
- mkdir -p "$task_uai_dir"
1286
+ mkdir -p "$task_uai_dir" "$task_uai_dir/attachments"
1283
1287
  # The shared group may be a broad host group (for example macOS `staff`). Keep
1284
1288
  # its writable leaves behind host-only parents; Docker bind-mounts the exact
1285
1289
  # workspace/repository roots, so node does not need host-path traversal here.
@@ -1327,7 +1331,7 @@ fi
1327
1331
 
1328
1332
  step "RENDER_FAILED" "generate compose"
1329
1333
 
1330
- mkdir -p "$task_uai_dir"
1334
+ mkdir -p "$task_uai_dir" "$task_uai_dir/attachments"
1331
1335
 
1332
1336
  STANDARD_CONTAINER_DIR="${UAI_STANDARD_CONTAINER_ASSETS_DIR:-$SCRIPT_DIR/../../images/standard/container}"
1333
1337
  if ! STANDARD_CONTAINER_DIR=$(cd "$STANDARD_CONTAINER_DIR" && pwd -P); then
package/src/index.ts CHANGED
@@ -168,7 +168,68 @@ export const hostEvents: HostEventStream = {
168
168
  },
169
169
  };
170
170
 
171
+ /** Set by main.ts at boot: the drain-then-supervisor-respawn used for
172
+ * staged activation. A hook (not an import) because main imports this
173
+ * module. */
174
+ let managedHostRestartHook: (() => void) | null = null;
175
+ export function setManagedHostRestartHook(hook: () => void): void {
176
+ managedHostRestartHook = hook;
177
+ }
178
+
171
179
  export const hostCommands: HostCommands = {
180
+ async hostUpdate(ctx) {
181
+ logCommand(ctx, "hostUpdate");
182
+ try {
183
+ const { updateManagedRuntime } = await import("../lib/managed-runtime");
184
+ const outcome = await updateManagedRuntime();
185
+ if (outcome.status === "not-managed") {
186
+ return {
187
+ ok: false,
188
+ code: HostErrorCode.Internal,
189
+ message:
190
+ "this host is not a managed install — update it where it runs",
191
+ retryable: false,
192
+ };
193
+ }
194
+ if (outcome.status === "deferred") {
195
+ // A rollback hold is an operator verdict that the newer release is
196
+ // unsafe on this host; a remote click must not override it.
197
+ return {
198
+ ok: true,
199
+ value: { version: outcome.version, staged: false, deferred: true },
200
+ };
201
+ }
202
+ if (outcome.status === "current") {
203
+ return { ok: true, value: { version: outcome.version, staged: false } };
204
+ }
205
+ managedHostRestartHook?.();
206
+ return { ok: true, value: { version: outcome.version, staged: true } };
207
+ } catch (error) {
208
+ return {
209
+ ok: false,
210
+ code: HostErrorCode.Internal,
211
+ message: `update failed: ${
212
+ error instanceof Error ? error.message : "unknown error"
213
+ }`,
214
+ retryable: true,
215
+ };
216
+ }
217
+ },
218
+ async hostRestart(ctx) {
219
+ logCommand(ctx, "hostRestart");
220
+ if (!managedHostRestartHook) {
221
+ return {
222
+ ok: false,
223
+ code: HostErrorCode.Internal,
224
+ message: "restart is unavailable in this process",
225
+ retryable: false,
226
+ };
227
+ }
228
+ // The result frame flushes before exit: shutdown drains in-flight
229
+ // commands (this one included) before the process terminates.
230
+ managedHostRestartHook();
231
+ return { ok: true, value: undefined };
232
+ },
172
233
  async taskUp(ctx, input) {
173
234
  logCommand(ctx, "taskUp", input.task.id);
174
235
  const admissionFailure = managedMaintenanceUnavailable();
package/src/main.ts CHANGED
@@ -161,7 +161,7 @@ import {
161
161
  onAgentClisReady,
162
162
  standardRuntimes,
163
163
  } from "../lib/standard-image";
164
- import { hostCommands, hostEvents } from "./index";
164
+ import { hostCommands, hostEvents, setManagedHostRestartHook } from "./index";
165
165
  import { EventOutbox, transmitEventOutbox } from "./event-outbox";
166
166
  import {
167
167
  HostErrorCode,
@@ -171,6 +171,7 @@ import {
171
171
  GITHUB_REPOSITORY_ACCESS_PROTOCOL_FEATURE,
172
172
  HOST_MAINTENANCE_READINESS_PROTOCOL_FEATURE,
173
173
  HOST_CONFIG_PROTOCOL_FEATURE,
174
+ HOST_REMOTE_LIFECYCLE_PROTOCOL_FEATURE,
174
175
  HOST_ENGINE_ACCOUNTS_PROTOCOL_FEATURE,
175
176
  HOST_ENGINE_CONNECT_PROTOCOL_FEATURE,
176
177
  HOST_ENGINE_LOGIN_PROTOCOL_FEATURE,
@@ -567,6 +568,7 @@ function buildCapabilities(): HostCapabilities {
567
568
  HOST_LOGS_PROTOCOL_FEATURE,
568
569
  HOST_ENGINE_LOGIN_PROTOCOL_FEATURE,
569
570
  HOST_CONFIG_PROTOCOL_FEATURE,
571
+ HOST_REMOTE_LIFECYCLE_PROTOCOL_FEATURE,
570
572
  HOST_ENGINE_ACCOUNTS_PROTOCOL_FEATURE,
571
573
  HOST_ENGINE_CONNECT_PROTOCOL_FEATURE,
572
574
  // The echo adapter cannot execute the in-task CLI. Advertising typed
@@ -2284,6 +2286,10 @@ async function dispatchCommand(
2284
2286
  expectString(args, 1),
2285
2287
  expectNumberArg(args, 2),
2286
2288
  );
2289
+ case "hostUpdate":
2290
+ return hostCommands.hostUpdate(ctx);
2291
+ case "hostRestart":
2292
+ return hostCommands.hostRestart(ctx);
2287
2293
  case "engineAccountRemove": {
2288
2294
  // ADR-116: mirror the local API's post-mutation behavior — the cloud's
2289
2295
  // account list self-heals through the capability re-advertisement.
@@ -3263,6 +3269,8 @@ export function requestManagedHostRestart(): void {
3263
3269
  shutdownExitCode = MANAGED_UPDATE_RESTART_EXIT_CODE;
3264
3270
  requestShutdown("SIGTERM");
3265
3271
  }
3272
+ // Remote hostUpdate/hostRestart commands reuse the exact activation restart.
3273
+ setManagedHostRestartHook(requestManagedHostRestart);
3266
3274
 
3267
3275
  /** Automatic host updates wait only for in-flight work to drain. Running
3268
3276
  * tasks are deliberately not counted: durable sessions survive the service
package/src/protocol.ts CHANGED
@@ -86,6 +86,9 @@ export const MCP_GATEWAY_HEALTH_PROTOCOL_FEATURE = "mcp-gateway-health-v1";
86
86
  export const HOST_LOGS_PROTOCOL_FEATURE = "host-logs-v1";
87
87
  export const HOST_ENGINE_LOGIN_PROTOCOL_FEATURE = "host-engine-login-v1";
88
88
  export const HOST_CONFIG_PROTOCOL_FEATURE = "host-config-v1";
89
+ /** Remote update/restart over the bridge (owner-gated cloud routes). */
90
+ export const HOST_REMOTE_LIFECYCLE_PROTOCOL_FEATURE =
91
+ "host-remote-lifecycle-v1";
89
92
  /** ADR-116: labeled engine accounts on the cloud host page — account list in
90
93
  * capabilities, account-targeted login (`label` on engine.login.start), and
91
94
  * the engineAccountRemove command. */
@@ -1250,7 +1253,26 @@ export type FilesOpValue =
1250
1253
  | { dataBase64: string; size: number; eof: boolean } // read
1251
1254
  | Record<string, never>; // write / mkdir / delete
1252
1255
 
1256
+ export interface HostUpdateOutcome {
1257
+ version: string;
1258
+ /** True when a newer signed release was staged and the host is about to
1259
+ * drain-restart into it; false when it was already current. */
1260
+ staged: boolean;
1261
+ /** Set when a rollback hold deferred the update (operator ran a rollback;
1262
+ * remote updates respect that verdict). */
1263
+ deferred?: boolean;
1264
+ }
1265
+
1253
1266
  export interface HostCommands {
1267
+ /**
1268
+ * Stage a signed update from the release feed and drain-restart into it.
1269
+ * Managed installs only; the host verifies the signature itself, so the
1270
+ * remote trigger adds no trust surface. The reconnect on the new version
1271
+ * is the real confirmation.
1272
+ */
1273
+ hostUpdate(ctx: CommandContext): Promise<HostCommandResult<HostUpdateOutcome>>;
1274
+ /** Drain in-flight work, then let the service supervisor respawn the host. */
1275
+ hostRestart(ctx: CommandContext): Promise<HostCommandResult<void>>;
1254
1276
  taskUp(
1255
1277
  ctx: CommandContext,
1256
1278
  input: TaskLaunchInput,