@runuai/host 0.9.105 → 0.9.107
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/machine-provider-aws.ts +26 -3
- package/package.json +1 -1
- package/src/index.ts +11 -0
- package/src/main.ts +14 -0
- package/src/protocol.ts +10 -0
|
@@ -279,10 +279,19 @@ async function hostEgressIp(): Promise<string> {
|
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/** How long a resolved payload AMI is trusted before the newest published
|
|
283
|
+
* image is looked up again. Long enough that a burst of launches shares one
|
|
284
|
+
* describe-images; short enough that a freshly published image reaches every
|
|
285
|
+
* RUNNING host — shared and BYO — on its next task without a host restart
|
|
286
|
+
* (before 0.9.106 the answer was kept for the process lifetime). */
|
|
287
|
+
export const PAYLOAD_AMI_TTL_MS = 5 * 60_000;
|
|
288
|
+
|
|
282
289
|
export function createAwsMachineProvider(
|
|
283
290
|
config: AwsMachineConfig,
|
|
284
291
|
runner: Runner = defaultRunner(config.region),
|
|
292
|
+
deps: { now?: () => number } = {},
|
|
285
293
|
): MachineProvider {
|
|
294
|
+
const now = deps.now ?? (() => Date.now());
|
|
286
295
|
const taskIdOf = (machineId: string): string => {
|
|
287
296
|
if (!machineId.startsWith("uai-machine-")) {
|
|
288
297
|
throw new Error(`not a uai machine id: ${machineId}`);
|
|
@@ -332,10 +341,12 @@ export function createAwsMachineProvider(
|
|
|
332
341
|
// orchestrator can never reach.
|
|
333
342
|
// -------------------------------------------------------------------------
|
|
334
343
|
|
|
335
|
-
let resolvedImage: string | null = null;
|
|
344
|
+
let resolvedImage: { id: string; at: number } | null = null;
|
|
336
345
|
async function resolveImage(specImage: string): Promise<string> {
|
|
337
346
|
if (specImage.startsWith("ami-")) return specImage;
|
|
338
|
-
if (resolvedImage)
|
|
347
|
+
if (resolvedImage && now() - resolvedImage.at < PAYLOAD_AMI_TTL_MS) {
|
|
348
|
+
return resolvedImage.id;
|
|
349
|
+
}
|
|
339
350
|
const res = await runner([
|
|
340
351
|
"ec2",
|
|
341
352
|
"describe-images",
|
|
@@ -356,12 +367,24 @@ export function createAwsMachineProvider(
|
|
|
356
367
|
}
|
|
357
368
|
}
|
|
358
369
|
if (typeof image !== "string" || !image.startsWith("ami-")) {
|
|
370
|
+
// A stale-but-known image beats a failed launch when the lookup itself
|
|
371
|
+
// hiccups; only a host that never resolved one has nothing to offer.
|
|
372
|
+
if (resolvedImage) return resolvedImage.id;
|
|
359
373
|
throw new Error(
|
|
360
374
|
`no uai machine payload AMI is published in ${config.region} — set ` +
|
|
361
375
|
"UAI_MACHINE_IMAGE to an AMI id, or ask Uai to publish this region",
|
|
362
376
|
);
|
|
363
377
|
}
|
|
364
|
-
resolvedImage
|
|
378
|
+
if (resolvedImage?.id !== image) {
|
|
379
|
+
// The journal line that tells an operator which payload a task got —
|
|
380
|
+
// an exit 127 for a missing CLI is otherwise the only symptom of a
|
|
381
|
+
// region that never received the latest publish.
|
|
382
|
+
console.log(
|
|
383
|
+
`[machine-aws] payload image for ${config.region}: ${image}` +
|
|
384
|
+
(resolvedImage ? ` (was ${resolvedImage.id})` : ""),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
resolvedImage = { id: image, at: now() };
|
|
365
388
|
return image;
|
|
366
389
|
}
|
|
367
390
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -123,6 +123,7 @@ import {
|
|
|
123
123
|
renameEngineAccount,
|
|
124
124
|
reorderEngineAccounts,
|
|
125
125
|
} from "../lib/engine-accounts";
|
|
126
|
+
import { disconnectEngine } from "../lib/engines";
|
|
126
127
|
|
|
127
128
|
export {
|
|
128
129
|
parseMentions,
|
|
@@ -1271,6 +1272,16 @@ export const hostCommands: HostCommands = {
|
|
|
1271
1272
|
}
|
|
1272
1273
|
},
|
|
1273
1274
|
|
|
1275
|
+
async engineDisconnect(ctx, input) {
|
|
1276
|
+
logCommand(ctx, "engineDisconnect", input.engine);
|
|
1277
|
+
try {
|
|
1278
|
+
disconnectEngine(input.engine);
|
|
1279
|
+
return ok({ disconnected: true });
|
|
1280
|
+
} catch (err) {
|
|
1281
|
+
return failFromUnknown(err);
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
|
|
1274
1285
|
async channelResolvePermission(ctx, taskId, agentId, requestId, decision) {
|
|
1275
1286
|
logCommand(ctx, "channelResolvePermission", taskId, agentId, requestId);
|
|
1276
1287
|
try {
|
package/src/main.ts
CHANGED
|
@@ -188,6 +188,7 @@ import {
|
|
|
188
188
|
HOST_ENGINE_CONNECT_PROTOCOL_FEATURE,
|
|
189
189
|
HOST_ENGINE_ACCOUNT_MANAGE_PROTOCOL_FEATURE,
|
|
190
190
|
HOST_ENGINE_ACCOUNT_SCOPES_PROTOCOL_FEATURE,
|
|
191
|
+
HOST_ENGINE_DISCONNECT_PROTOCOL_FEATURE,
|
|
191
192
|
HOST_ENGINE_LOGIN_PROTOCOL_FEATURE,
|
|
192
193
|
isEngineAccountScope,
|
|
193
194
|
HOST_LOGS_PROTOCOL_FEATURE,
|
|
@@ -232,6 +233,7 @@ import {
|
|
|
232
233
|
type FilesOpInput,
|
|
233
234
|
engineAccountScopeFromWire,
|
|
234
235
|
isEngineAccountLabel,
|
|
236
|
+
isEngineConnectKind,
|
|
235
237
|
} from "./protocol";
|
|
236
238
|
|
|
237
239
|
setContainerRuntimeOperationalCheck(() => containerRuntimeProblem() === null);
|
|
@@ -615,6 +617,7 @@ function buildCapabilities(): HostCapabilities {
|
|
|
615
617
|
HOST_ENGINE_CONNECT_PROTOCOL_FEATURE,
|
|
616
618
|
HOST_ENGINE_ACCOUNT_SCOPES_PROTOCOL_FEATURE,
|
|
617
619
|
HOST_ENGINE_ACCOUNT_MANAGE_PROTOCOL_FEATURE,
|
|
620
|
+
HOST_ENGINE_DISCONNECT_PROTOCOL_FEATURE,
|
|
618
621
|
// The echo adapter cannot execute the in-task CLI. Advertising typed
|
|
619
622
|
// dispatch in mock mode would let the composer create a Secretary that
|
|
620
623
|
// has no way to wake crew.
|
|
@@ -2465,6 +2468,16 @@ async function dispatchCommand(
|
|
|
2465
2468
|
if (reordered.ok) sendCapabilities();
|
|
2466
2469
|
return reordered;
|
|
2467
2470
|
}
|
|
2471
|
+
case "engineDisconnect": {
|
|
2472
|
+
const value = args[0];
|
|
2473
|
+
const engine = (value as { engine?: unknown } | undefined)?.engine;
|
|
2474
|
+
if (!isEngineConnectKind(engine)) {
|
|
2475
|
+
throw new Error("invalid command args: expected an engine kind at 0");
|
|
2476
|
+
}
|
|
2477
|
+
const result = await hostCommands.engineDisconnect(ctx, { engine });
|
|
2478
|
+
if (result.ok) sendCapabilities();
|
|
2479
|
+
return result;
|
|
2480
|
+
}
|
|
2468
2481
|
}
|
|
2469
2482
|
}
|
|
2470
2483
|
|
|
@@ -3050,6 +3063,7 @@ const HOST_COMMAND_NAMES = {
|
|
|
3050
3063
|
engineAccountRemove: true,
|
|
3051
3064
|
engineAccountRename: true,
|
|
3052
3065
|
engineAccountReorder: true,
|
|
3066
|
+
engineDisconnect: true,
|
|
3053
3067
|
} satisfies Record<keyof HostCommands, true>;
|
|
3054
3068
|
|
|
3055
3069
|
function isHostCommand(command: string): command is keyof HostCommands {
|
package/src/protocol.ts
CHANGED
|
@@ -113,6 +113,9 @@ export const HOST_ENGINE_CONNECT_PROTOCOL_FEATURE = "host-engine-connect-v1";
|
|
|
113
113
|
* `engineAccountReorder` commands; login `succeeded` events carry the created
|
|
114
114
|
* `accountId` for labeled captures; codex `awaiting_confirmation` carries the
|
|
115
115
|
* device `code`. Extras are advertised in priority order (position). */
|
|
116
|
+
/** Host 0.9.107: `engineDisconnect` — forget the HOST's own credential for an
|
|
117
|
+
* engine (the default slot: .env.local key / login file). Owner-only. */
|
|
118
|
+
export const HOST_ENGINE_DISCONNECT_PROTOCOL_FEATURE = "host-engine-disconnect-v1";
|
|
116
119
|
export const HOST_ENGINE_ACCOUNT_MANAGE_PROTOCOL_FEATURE =
|
|
117
120
|
"host-engine-account-manage-v1";
|
|
118
121
|
export const HOST_ENGINE_ACCOUNT_SCOPES_PROTOCOL_FEATURE =
|
|
@@ -1493,6 +1496,13 @@ export interface HostCommands {
|
|
|
1493
1496
|
ctx: CommandContext,
|
|
1494
1497
|
input: { kind: string; ids: string[]; scope?: string },
|
|
1495
1498
|
): Promise<HostCommandResult<{ reordered: boolean; message?: string }>>;
|
|
1499
|
+
/** Disconnect-v1: forget the host's DEFAULT credential for an engine — the
|
|
1500
|
+
* .env.local key and/or the login file the adapter detects. Extras are
|
|
1501
|
+
* untouched (remove them by id). Re-advertises on success. */
|
|
1502
|
+
engineDisconnect(
|
|
1503
|
+
ctx: CommandContext,
|
|
1504
|
+
input: { engine: EngineConnectKind },
|
|
1505
|
+
): Promise<HostCommandResult<{ disconnected: boolean; message?: string }>>;
|
|
1496
1506
|
cloneRepo(
|
|
1497
1507
|
ctx: CommandContext,
|
|
1498
1508
|
input: CloneRepoInput,
|