@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 +1 -1
- package/scripts/agent/task-up.sh +6 -2
- package/src/index.ts +61 -0
- package/src/main.ts +9 -1
- package/src/protocol.ts +22 -0
package/package.json
CHANGED
package/scripts/agent/task-up.sh
CHANGED
|
@@ -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,
|