@runuai/host 0.9.146 → 0.9.148
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 +17 -2
- package/lib/agents/attach-compatibility.ts +51 -0
- package/lib/agents/codex.ts +128 -12
- package/lib/agents/durable-proc.ts +2 -0
- package/lib/agents/machine-durable.ts +15 -0
- package/lib/agents/session-state.ts +107 -0
- package/lib/agents/transport.ts +116 -53
- package/lib/agents/types.ts +3 -3
- package/lib/orchestrator.ts +1294 -73
- package/lib/stopped-task-status-queue.ts +24 -6
- package/lib/ulid.ts +10 -1
- package/package.json +1 -1
- package/src/main.ts +18 -6
- package/src/protocol.ts +35 -3
|
@@ -4,8 +4,21 @@
|
|
|
4
4
|
* mirror remains the authority at flush time so a later Resume wins over an
|
|
5
5
|
* older queued stop.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Why the host stopped the task, when the host knows.
|
|
9
|
+
*
|
|
10
|
+
* `"manual"` is ADR-028's local-UI Stop — a person at the machine pressed it.
|
|
11
|
+
* Everything else the host reports through this frame is automatic (writable
|
|
12
|
+
* -container quarantine during a runtime activation or a shared-volume
|
|
13
|
+
* repair), and carries no reason. The cloud needs the difference because
|
|
14
|
+
* ADR-144 lets a collaborator resume a task that stopped on its own but not
|
|
15
|
+
* one a human deliberately paused, and until now both arrived as the same
|
|
16
|
+
* three fields.
|
|
17
|
+
*/
|
|
18
|
+
export type StoppedTaskReason = "manual";
|
|
19
|
+
|
|
7
20
|
export class StoppedTaskStatusQueue {
|
|
8
|
-
private readonly pending = new
|
|
21
|
+
private readonly pending = new Map<string, StoppedTaskReason | undefined>();
|
|
9
22
|
|
|
10
23
|
constructor(
|
|
11
24
|
private readonly statusForTask: (
|
|
@@ -13,8 +26,13 @@ export class StoppedTaskStatusQueue {
|
|
|
13
26
|
) => string | null | undefined,
|
|
14
27
|
) {}
|
|
15
28
|
|
|
16
|
-
enqueue(taskId: string): void {
|
|
17
|
-
|
|
29
|
+
enqueue(taskId: string, reason?: StoppedTaskReason): void {
|
|
30
|
+
// A reason, once known, STICKS across a re-enqueue. The same task can be
|
|
31
|
+
// queued twice before a flush — an operator Stop, then a quarantine sweep
|
|
32
|
+
// that finds its container already down — and the automatic pass arriving
|
|
33
|
+
// second does not make the human's decision retroactively automatic.
|
|
34
|
+
// Dropping it would silently hand the task back to every collaborator.
|
|
35
|
+
this.pending.set(taskId, reason ?? this.pending.get(taskId));
|
|
18
36
|
}
|
|
19
37
|
|
|
20
38
|
/** A failed taskUp may have stopped a legacy runtime before recreation lost
|
|
@@ -35,9 +53,9 @@ export class StoppedTaskStatusQueue {
|
|
|
35
53
|
return true;
|
|
36
54
|
}
|
|
37
55
|
|
|
38
|
-
flush(publish: (taskId: string) => void): void {
|
|
39
|
-
for (const taskId of [...this.pending]) {
|
|
40
|
-
if (this.statusForTask(taskId) === "stopped") publish(taskId);
|
|
56
|
+
flush(publish: (taskId: string, reason?: StoppedTaskReason) => void): void {
|
|
57
|
+
for (const [taskId, reason] of [...this.pending]) {
|
|
58
|
+
if (this.statusForTask(taskId) === "stopped") publish(taskId, reason);
|
|
41
59
|
this.pending.delete(taskId);
|
|
42
60
|
}
|
|
43
61
|
}
|
package/lib/ulid.ts
CHANGED
|
@@ -4,4 +4,13 @@ export function newId(): string {
|
|
|
4
4
|
return ulid().toLowerCase();
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A ULID with its canonical UPPERCASE base32, unlike `newId()` above.
|
|
9
|
+
*
|
|
10
|
+
* Renamed from `monotonicUlid`, which it never was: the plain generator
|
|
11
|
+
* randomises the suffix, so two ids minted in the same millisecond do not
|
|
12
|
+
* sort in call order. Nothing in this package imports it — it is kept only to
|
|
13
|
+
* mirror `lib/ulid.ts`, and it is renamed here too so the false promise is
|
|
14
|
+
* not sitting in the copy someone reads next.
|
|
15
|
+
*/
|
|
16
|
+
export { ulid as rawUlid };
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -28,7 +28,10 @@ import {
|
|
|
28
28
|
getHostTask,
|
|
29
29
|
hasInFlightHostTaskLifecycle,
|
|
30
30
|
} from "../lib/runtime-state";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
StoppedTaskStatusQueue,
|
|
33
|
+
type StoppedTaskReason,
|
|
34
|
+
} from "../lib/stopped-task-status-queue";
|
|
32
35
|
import {
|
|
33
36
|
getOrchestrator,
|
|
34
37
|
quarantineWritableRuntimeContainers,
|
|
@@ -209,6 +212,7 @@ import {
|
|
|
209
212
|
HOST_ENGINE_ACCOUNT_SCOPES_PROTOCOL_FEATURE,
|
|
210
213
|
HOST_ENGINE_DISCONNECT_PROTOCOL_FEATURE,
|
|
211
214
|
HOST_ENGINE_LOGIN_PROTOCOL_FEATURE,
|
|
215
|
+
LIVE_ROSTER_EDIT_PROTOCOL_FEATURE,
|
|
212
216
|
isEngineAccountScope,
|
|
213
217
|
HOST_LOGS_PROTOCOL_FEATURE,
|
|
214
218
|
HOST_TASK_INVENTORY_PROTOCOL_FEATURE,
|
|
@@ -866,7 +870,11 @@ async function startLocalUi(): Promise<void> {
|
|
|
866
870
|
invalidateMachineTunnelRoute(taskId);
|
|
867
871
|
try {
|
|
868
872
|
const result = await getOrchestrator().stopTask(taskId);
|
|
869
|
-
|
|
873
|
+
// A PERSON pressed Stop on this machine's own UI — ADR-028. Labelled
|
|
874
|
+
// as such because the cloud otherwise cannot tell it apart from the
|
|
875
|
+
// quarantine sweeps below, which reach the bridge through this same
|
|
876
|
+
// frame, and ADR-144 turns that difference into who may undo it.
|
|
877
|
+
if (result.ok) queueStoppedTaskStatus(taskId, "manual");
|
|
870
878
|
return result;
|
|
871
879
|
} finally {
|
|
872
880
|
invalidateMachineTunnelRoute(taskId);
|
|
@@ -909,6 +917,7 @@ function buildCapabilities(): HostCapabilities {
|
|
|
909
917
|
HOST_ENGINE_ACCOUNT_SCOPES_PROTOCOL_FEATURE,
|
|
910
918
|
HOST_ENGINE_ACCOUNT_MANAGE_PROTOCOL_FEATURE,
|
|
911
919
|
HOST_ENGINE_DISCONNECT_PROTOCOL_FEATURE,
|
|
920
|
+
LIVE_ROSTER_EDIT_PROTOCOL_FEATURE,
|
|
912
921
|
// The echo adapter cannot execute the in-task CLI. Advertising typed
|
|
913
922
|
// dispatch in mock mode would let the composer create a Secretary that
|
|
914
923
|
// has no way to wake crew.
|
|
@@ -1042,15 +1051,18 @@ function sendCapabilities(): void {
|
|
|
1042
1051
|
|
|
1043
1052
|
/** Keep a rolling-isolation/local-UI stop from remaining cloud-ghost-running
|
|
1044
1053
|
* merely because the bridge was between connections at the transition. */
|
|
1045
|
-
function queueStoppedTaskStatus(
|
|
1046
|
-
|
|
1054
|
+
function queueStoppedTaskStatus(
|
|
1055
|
+
taskId: string,
|
|
1056
|
+
reason?: StoppedTaskReason,
|
|
1057
|
+
): void {
|
|
1058
|
+
pendingRuntimeIsolationStatuses.enqueue(taskId, reason);
|
|
1047
1059
|
flushStoppedTaskStatuses();
|
|
1048
1060
|
}
|
|
1049
1061
|
|
|
1050
1062
|
function flushStoppedTaskStatuses(socket: WebSocket | null = ws): void {
|
|
1051
1063
|
if (!socket || socket.readyState !== WebSocket.OPEN) return;
|
|
1052
|
-
pendingRuntimeIsolationStatuses.flush((taskId) => {
|
|
1053
|
-
send(socket, { kind: "task.status", taskId, status: "stopped" });
|
|
1064
|
+
pendingRuntimeIsolationStatuses.flush((taskId, reason) => {
|
|
1065
|
+
send(socket, { kind: "task.status", taskId, status: "stopped", reason });
|
|
1054
1066
|
});
|
|
1055
1067
|
}
|
|
1056
1068
|
|
package/src/protocol.ts
CHANGED
|
@@ -120,6 +120,14 @@ export const HOST_ENGINE_ACCOUNT_MANAGE_PROTOCOL_FEATURE =
|
|
|
120
120
|
"host-engine-account-manage-v1";
|
|
121
121
|
export const HOST_ENGINE_ACCOUNT_SCOPES_PROTOCOL_FEATURE =
|
|
122
122
|
"host-engine-account-scopes-v1";
|
|
123
|
+
/** ADR-049 addendum: live roster edits. On channel.ensure the host diffs the
|
|
124
|
+
* live roster against the spec — an agent whose kind, model or effort changed
|
|
125
|
+
* has its session recycled so the reconciler respawns it on the new settings,
|
|
126
|
+
* and an agent missing from the spec has its session stopped (its session dir
|
|
127
|
+
* stays). A host without this keeps a changed agent's old process until some
|
|
128
|
+
* unrelated respawn and never stops a removed one, so the cloud refuses to
|
|
129
|
+
* edit or remove a running task's agents on it. */
|
|
130
|
+
export const LIVE_ROSTER_EDIT_PROTOCOL_FEATURE = "live-roster-edit-v1";
|
|
123
131
|
/** The owner-level account scope; every pre-scope account lives here. */
|
|
124
132
|
export const HOST_ACCOUNT_SCOPE = "host";
|
|
125
133
|
const ORG_ACCOUNT_SCOPE_RE = /^org:[A-Za-z0-9_-]{1,128}$/;
|
|
@@ -1812,9 +1820,21 @@ export type HostToCloud =
|
|
|
1812
1820
|
// ADR-103: `seq` is the outbox sequence number (monotonic within a bootId).
|
|
1813
1821
|
// Absent from pre-103 hosts; the cloud dedups replay overlap by it.
|
|
1814
1822
|
| { kind: "event"; event: HostEvent; seq?: number }
|
|
1815
|
-
// Host-pushed task lifecycle
|
|
1816
|
-
//
|
|
1817
|
-
|
|
1823
|
+
// Host-pushed task lifecycle: the cloud mirrors the status (currently
|
|
1824
|
+
// "stopped"). TWO different events arrive through this one frame — ADR-028's
|
|
1825
|
+
// local-UI Stop, where the host operator paused the task's containers, and
|
|
1826
|
+
// the automatic writable-container quarantine a runtime activation or
|
|
1827
|
+
// shared-volume repair performs. `reason: "manual"` is the first; its
|
|
1828
|
+
// absence is the second.
|
|
1829
|
+
//
|
|
1830
|
+
// The cloud cannot infer it. ADR-144 lets a collaborator resume a task that
|
|
1831
|
+
// stopped on its own and not one a human deliberately paused, and with both
|
|
1832
|
+
// stops indistinguishable on the wire the deliberate one was reversible by
|
|
1833
|
+
// anyone on the task. OPTIONAL so an older host stays compatible: it sends
|
|
1834
|
+
// no reason, the cloud records none, and that host's operator Stop keeps the
|
|
1835
|
+
// pre-ADR-144 behaviour rather than mislabelling its quarantines as manual
|
|
1836
|
+
// and blocking the recovery path they exist to enable.
|
|
1837
|
+
| { kind: "task.status"; taskId: string; status: string; reason?: string }
|
|
1818
1838
|
| { kind: "ping"; ts: number }
|
|
1819
1839
|
// ADR-133: a sidecar asks for (or refreshes) its task-scoped AWS lease.
|
|
1820
1840
|
| { kind: "lease.request"; opId: string; taskId: string }
|
|
@@ -2025,6 +2045,18 @@ export type HostEvent =
|
|
|
2025
2045
|
agentId: string;
|
|
2026
2046
|
reason: string;
|
|
2027
2047
|
}
|
|
2048
|
+
// A roster change moved an agent's session (LIVE_ROSTER_EDIT_PROTOCOL_FEATURE).
|
|
2049
|
+
// `restarting`: its kind/model/effort changed and the old session was stopped
|
|
2050
|
+
// (at once when idle, otherwise when its turn ended) — the reconciler is
|
|
2051
|
+
// respawning it. `ready`: that replacement session bound. `removed`: the
|
|
2052
|
+
// agent left the roster and its session was stopped. An older cloud ignores
|
|
2053
|
+
// the unknown kind.
|
|
2054
|
+
| {
|
|
2055
|
+
kind: "agent.session";
|
|
2056
|
+
taskId: string;
|
|
2057
|
+
agentId: string;
|
|
2058
|
+
state: "restarting" | "ready" | "removed";
|
|
2059
|
+
}
|
|
2028
2060
|
// A host-originated system note for the task channel (ADR-027) — e.g. gh
|
|
2029
2061
|
// auth expired. The cloud renders it as an author:"system" message.
|
|
2030
2062
|
| {
|