@runuai/host 0.9.70 → 0.9.72
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/agent-cli.ts +36 -0
- package/lib/agents/machine-durable.ts +492 -0
- package/lib/agents/transport.ts +186 -2
- package/lib/orchestrator.ts +49 -4
- package/lib/runtime-state.ts +6 -0
- package/lib/task-environment/index.ts +64 -2
- package/lib/task-environment/legacy-adoption.ts +6 -0
- package/lib/task-environment/machine-task-up.ts +333 -0
- package/lib/task-environment/machine.ts +50 -19
- package/lib/transcript.ts +83 -5
- package/package.json +1 -1
- package/src/index.ts +117 -11
package/src/index.ts
CHANGED
|
@@ -19,7 +19,9 @@ import {
|
|
|
19
19
|
} from "../lib/agent-cli";
|
|
20
20
|
import {
|
|
21
21
|
clearRefresh,
|
|
22
|
+
injectIntoContainer,
|
|
22
23
|
reconcileTaskGitAuth,
|
|
24
|
+
requestAccessToken,
|
|
23
25
|
} from "../lib/github-tokens";
|
|
24
26
|
import {
|
|
25
27
|
prepareTaskGithubGitCredential,
|
|
@@ -35,7 +37,10 @@ import {
|
|
|
35
37
|
missingRequiredKeys,
|
|
36
38
|
parseEnvSchema,
|
|
37
39
|
} from "../lib/env-schema";
|
|
38
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
appendTranscript as writeTranscript,
|
|
42
|
+
appendTranscriptViaEnvironment,
|
|
43
|
+
} from "../lib/transcript";
|
|
39
44
|
import { buildTaskDiff } from "../lib/task-diff";
|
|
40
45
|
import {
|
|
41
46
|
deleteHostTask,
|
|
@@ -448,6 +453,37 @@ export const hostCommands: HostCommands = {
|
|
|
448
453
|
runtimeQuarantinedForRecreate = true;
|
|
449
454
|
}
|
|
450
455
|
}
|
|
456
|
+
} else if (
|
|
457
|
+
existingTask?.statusMirror === "running" &&
|
|
458
|
+
existingTask.environmentProvider === "machine"
|
|
459
|
+
) {
|
|
460
|
+
// ADR-121: a machine-backed task revalidates through its own
|
|
461
|
+
// provider — the compose fast path below speaks docker and would
|
|
462
|
+
// misread a machine row. Running machine → persisted success;
|
|
463
|
+
// anything else falls through to provisionTaskEnvironment, whose
|
|
464
|
+
// machine provider owns recovery semantics.
|
|
465
|
+
try {
|
|
466
|
+
const environment = await reconstructPersistedTaskEnvironment(
|
|
467
|
+
existingTask,
|
|
468
|
+
);
|
|
469
|
+
if (environment) {
|
|
470
|
+
const machineStatus = await environment.status();
|
|
471
|
+
if (machineStatus.state === "running") {
|
|
472
|
+
orchestrator.allowChannel(input.task.id);
|
|
473
|
+
return {
|
|
474
|
+
ok: true,
|
|
475
|
+
value: {
|
|
476
|
+
composeProject: existingTask.composeProject ?? "",
|
|
477
|
+
worktreePath: existingTask.worktreePath ?? "",
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.warn(
|
|
484
|
+
`[machine] task ${input.task.id}: running-row revalidation failed, continuing to provision: ${error instanceof Error ? error.message : String(error)}`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
451
487
|
} else if (existingTask?.statusMirror === "running") {
|
|
452
488
|
if (!existingTask.composeProject || !existingTask.worktreePath) {
|
|
453
489
|
return {
|
|
@@ -813,16 +849,33 @@ export const hostCommands: HostCommands = {
|
|
|
813
849
|
// and mark a running task as errored. The reconciler injects + schedules
|
|
814
850
|
// (or emits a system note on failure) on its own; the agents come up
|
|
815
851
|
// regardless and the token lands well before the first `gh` call.
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
)
|
|
825
|
-
|
|
852
|
+
// ADR-121: machine-backed tasks take a direct environment injection —
|
|
853
|
+
// the compose reconciler would probe for an app container, find none,
|
|
854
|
+
// and silently skip. Reconnect re-injection for machines is a
|
|
855
|
+
// follow-up alongside the machine recovery pass.
|
|
856
|
+
if (getHostTask(input.task.id)?.environmentProvider === "machine") {
|
|
857
|
+
void machineTaskGithubAuth(
|
|
858
|
+
input.task.id,
|
|
859
|
+
input.task.ownerUserId,
|
|
860
|
+
).catch((err) =>
|
|
861
|
+
console.warn(
|
|
862
|
+
`[github] machine task ${input.task.id}: auth injection failed: ${
|
|
863
|
+
err instanceof Error ? err.message : String(err)
|
|
864
|
+
}`,
|
|
865
|
+
),
|
|
866
|
+
);
|
|
867
|
+
} else {
|
|
868
|
+
void reconcileTaskGitAuth(
|
|
869
|
+
input.task.id,
|
|
870
|
+
input.task.ownerUserId,
|
|
871
|
+
).catch((err) =>
|
|
872
|
+
console.warn(
|
|
873
|
+
`[github] task ${input.task.id}: post-start reconciliation failed: ${
|
|
874
|
+
err instanceof Error ? err.message : String(err)
|
|
875
|
+
}`,
|
|
876
|
+
),
|
|
877
|
+
);
|
|
878
|
+
}
|
|
826
879
|
} else if (result.code === HostErrorCode.HostUnavailable) {
|
|
827
880
|
// A daemon outage is retryable infrastructure state, not proof that
|
|
828
881
|
// the task itself failed. Undo the optimistic local `starting` mirror
|
|
@@ -904,6 +957,17 @@ export const hostCommands: HostCommands = {
|
|
|
904
957
|
}
|
|
905
958
|
const result = await wrapAgent(ctx, "taskDown", async () => {
|
|
906
959
|
if (environment === null) return agent.taskDown(input);
|
|
960
|
+
// ADR-121: an ordinary stop of a machine task must keep the machine's
|
|
961
|
+
// disk — the workspace lives THERE, not on a host worktree the way
|
|
962
|
+
// compose teardown preserves it. Stop is the resumable gesture
|
|
963
|
+
// (provision restarts a stopped machine); only orphan GC terminates.
|
|
964
|
+
if (
|
|
965
|
+
!orphanGc &&
|
|
966
|
+
environment.descriptor.locator.provider === "machine"
|
|
967
|
+
) {
|
|
968
|
+
await environment.stop();
|
|
969
|
+
return taskDownResultForInput(input, { status: "stopped" });
|
|
970
|
+
}
|
|
907
971
|
return taskDownResultForInput(input, await environment.teardown());
|
|
908
972
|
});
|
|
909
973
|
if (result.ok) {
|
|
@@ -1265,6 +1329,24 @@ export const hostCommands: HostCommands = {
|
|
|
1265
1329
|
async appendTranscript(_ctx, taskId, author, text, targets) {
|
|
1266
1330
|
// Per-message + high-frequency, so no logCommand (avoid log spam).
|
|
1267
1331
|
try {
|
|
1332
|
+
// ADR-121: a machine-backed workspace lives in the machine's world —
|
|
1333
|
+
// the append must travel over the environment transport. Container
|
|
1334
|
+
// tasks keep the direct host-FS write (their workspace is a bind
|
|
1335
|
+
// mount and the sync path is cheaper than a docker exec per message).
|
|
1336
|
+
const task = getHostTask(taskId);
|
|
1337
|
+
if (task?.environmentProvider === "machine") {
|
|
1338
|
+
const environment = await reconstructHostTaskEnvironment(taskId);
|
|
1339
|
+
if (environment) {
|
|
1340
|
+
await appendTranscriptViaEnvironment(
|
|
1341
|
+
taskId,
|
|
1342
|
+
environment,
|
|
1343
|
+
author,
|
|
1344
|
+
text,
|
|
1345
|
+
targets,
|
|
1346
|
+
);
|
|
1347
|
+
return ok(undefined);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1268
1350
|
writeTranscript(taskId, author, text, targets);
|
|
1269
1351
|
return ok(undefined);
|
|
1270
1352
|
} catch (err) {
|
|
@@ -1277,6 +1359,30 @@ function normalizeChannelSpec(input: ChannelEnsureInput): ChannelEnsureInput {
|
|
|
1277
1359
|
return { ...input, workspacePath: "/workspace" };
|
|
1278
1360
|
}
|
|
1279
1361
|
|
|
1362
|
+
/**
|
|
1363
|
+
* ADR-121: gh auth for a machine-backed task, injected over the environment
|
|
1364
|
+
* transport (token via exec stdin — same `gh auth login --with-token` +
|
|
1365
|
+
* `setup-git` gesture as containers, no compose probing). Best-effort like
|
|
1366
|
+
* the compose reconciler: absence of a GitHub connection is a quiet no-op.
|
|
1367
|
+
*/
|
|
1368
|
+
async function machineTaskGithubAuth(
|
|
1369
|
+
taskId: string,
|
|
1370
|
+
userId: string,
|
|
1371
|
+
): Promise<void> {
|
|
1372
|
+
const environment = await reconstructHostTaskEnvironment(taskId);
|
|
1373
|
+
if (!environment) return;
|
|
1374
|
+
const token = await requestAccessToken(userId);
|
|
1375
|
+
if (!token) return;
|
|
1376
|
+
// Machine work is not container work: no docker/apple runtime admission.
|
|
1377
|
+
await injectIntoContainer(
|
|
1378
|
+
taskId,
|
|
1379
|
+
token.accessToken,
|
|
1380
|
+
undefined,
|
|
1381
|
+
() => {},
|
|
1382
|
+
environment,
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1280
1386
|
async function reconstructHostTaskEnvironment(
|
|
1281
1387
|
taskId: string,
|
|
1282
1388
|
): Promise<TaskEnvironmentHandle<TaskDownResult> | null> {
|