@runuai/host 0.9.14 → 0.9.43
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/README.md +22 -5
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/meta/_journal.json +21 -0
- package/db/schema.ts +80 -30
- package/images/standard/Dockerfile +36 -10
- package/images/standard/README.md +63 -18
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +308 -38
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/lib/agent-cli.ts +33 -2
- package/lib/agent.ts +46 -7
- package/lib/agents/claude.ts +13 -8
- package/lib/agents/codex.ts +11 -6
- package/lib/agents/cursor.ts +39 -29
- package/lib/agents/durable-proc.ts +20 -27
- package/lib/agents/factory.ts +9 -25
- package/lib/agents/grok.ts +43 -30
- package/lib/agents/kimi.ts +44 -29
- package/lib/agents/opencode.ts +43 -31
- package/lib/agents/proc.ts +149 -114
- package/lib/agents/transport.ts +62 -50
- package/lib/agents/types.ts +6 -4
- package/lib/apple-runtime-recycle.ts +236 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/browser-testing.ts +233 -93
- package/lib/codex-auth.ts +40 -6
- package/lib/command-db.ts +20 -0
- package/lib/container-runtime.ts +1338 -0
- package/lib/db.ts +1 -0
- package/lib/docker-exec.ts +87 -5
- package/lib/engine-accounts.ts +68 -5
- package/lib/engine-login.ts +1952 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +155 -0
- package/lib/env.ts +4 -0
- package/lib/git-diff.ts +98 -32
- package/lib/git-identity.ts +199 -87
- package/lib/github-tokens.ts +202 -91
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-logs.ts +962 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +49 -0
- package/lib/managed-runtime.ts +3644 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-gateway.ts +450 -23
- package/lib/orchestrator.ts +3051 -200
- package/lib/preview-sidecar.ts +68 -14
- package/lib/release-manifest.ts +708 -0
- package/lib/release-trust.ts +28 -0
- package/lib/runtime-activation-tail.ts +232 -0
- package/lib/runtime-archive.ts +1086 -0
- package/lib/runtime-authority.ts +79 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-state.ts +232 -12
- package/lib/skills.ts +24 -3
- package/lib/ssh.ts +18 -0
- package/lib/standard-image.ts +1104 -141
- package/lib/stopped-task-status-queue.ts +44 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-diff.ts +66 -46
- package/lib/task-environment/apple-container.ts +757 -0
- package/lib/task-environment/docker.ts +956 -0
- package/lib/task-environment/index.ts +364 -0
- package/lib/task-environment/legacy-adoption.ts +459 -0
- package/lib/task-environment/registry.ts +58 -0
- package/lib/task-environment/types.ts +408 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +585 -0
- package/lib/tunnel-registry.ts +135 -19
- package/lib/tunnel-runtime.ts +235 -0
- package/package.json +1 -1
- package/scripts/agent/_common.sh +123 -3
- package/scripts/agent/task-down.sh +146 -38
- package/scripts/agent/task-status.sh +19 -3
- package/scripts/agent/task-up.sh +1405 -107
- package/scripts/install/darwin.ts +848 -50
- package/scripts/install/linux.ts +838 -35
- package/scripts/install/types.ts +43 -0
- package/scripts/install/util.ts +215 -8
- package/scripts/install/win.ts +12 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/cli.ts +1464 -72
- package/src/event-outbox.ts +83 -4
- package/src/index.ts +766 -42
- package/src/main.ts +1398 -255
- package/src/paths.ts +17 -1
- package/src/protocol.ts +695 -1
- package/src/runtime-bootstrap.ts +165 -0
- package/src/ui/server.ts +46 -10
- package/src/ui/types.ts +37 -0
package/lib/agents/proc.ts
CHANGED
|
@@ -1,125 +1,139 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LineProcess — a child process whose stdout is consumed as
|
|
3
|
-
* newline-delimited text (one JSON value per line).
|
|
4
|
-
*
|
|
5
|
-
* Both real agent adapters (ClaudeSession, CodexSession) drive a CLI
|
|
6
|
-
* running *inside the task container* (ADR-010) via
|
|
7
|
-
* `docker exec -i task-<id>-app-1 <cli> …`. That CLI speaks
|
|
8
|
-
* newline-delimited JSON over stdio — Claude's stream-json, Codex's
|
|
9
|
-
* JSON-RPC. This class owns the mechanics shared by both: spawn,
|
|
10
|
-
* line-buffer stdout, capture stderr, write lines to stdin, tear down.
|
|
11
|
-
*
|
|
12
|
-
* It is deliberately protocol-agnostic — it knows nothing about
|
|
13
|
-
* stream-json or JSON-RPC. The adapters layer that on top.
|
|
14
|
-
*/
|
|
1
|
+
/** Provider-backed process adapters shared by the agent protocols. */
|
|
15
2
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* When set *and* `UAI_DEBUG_AGENTS` is truthy in the environment,
|
|
25
|
-
* spawn / raw stdio / exit are logged to the server console with
|
|
26
|
-
* this label — the way to see what an agent CLI is actually doing.
|
|
27
|
-
*/
|
|
28
|
-
debugLabel?: string;
|
|
29
|
-
}
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
TaskEnvironmentAgentSessionSurface,
|
|
7
|
+
TaskEnvironmentProcess,
|
|
8
|
+
TaskEnvironmentSessionRequest,
|
|
9
|
+
} from "../task-environment/types";
|
|
30
10
|
|
|
31
11
|
export type LineHandler = (line: string) => void;
|
|
32
12
|
export type ExitHandler = (code: number | null) => void;
|
|
33
13
|
|
|
34
14
|
/** How much trailing stderr to retain for error reporting. */
|
|
35
15
|
const STDERR_CAP = 8 * 1024;
|
|
16
|
+
/** Maximum unread stdout+stderr retained by a provider for an agent process. */
|
|
17
|
+
export const AGENT_SESSION_OUTPUT_BUFFER_BYTES = 1024 * 1024;
|
|
36
18
|
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Line-oriented adapter over the provider-neutral environment
|
|
21
|
+
* stream. Startup may be asynchronous, so writes are serialized behind the
|
|
22
|
+
* provider's spawn promise without exposing a container command to callers.
|
|
23
|
+
*/
|
|
24
|
+
export class EnvironmentLineProcess {
|
|
25
|
+
private readonly processPromise: Promise<TaskEnvironmentProcess>;
|
|
26
|
+
private process: TaskEnvironmentProcess | null = null;
|
|
39
27
|
private stdoutBuf = "";
|
|
40
28
|
private stderrBuf = "";
|
|
41
29
|
private readonly lineHandlers = new Set<LineHandler>();
|
|
42
30
|
private readonly exitHandlers = new Set<ExitHandler>();
|
|
31
|
+
private inputChain: Promise<void> = Promise.resolve();
|
|
43
32
|
private closed = false;
|
|
33
|
+
private finished = false;
|
|
34
|
+
private exitCode: number | null = null;
|
|
44
35
|
private readonly debug: string | null;
|
|
45
36
|
|
|
46
|
-
constructor(opts:
|
|
37
|
+
constructor(opts: {
|
|
38
|
+
environment: TaskEnvironmentAgentSessionSurface;
|
|
39
|
+
request: TaskEnvironmentSessionRequest;
|
|
40
|
+
debugLabel?: string;
|
|
41
|
+
}) {
|
|
47
42
|
this.debug =
|
|
48
43
|
opts.debugLabel && process.env.UAI_DEBUG_AGENTS ? opts.debugLabel : null;
|
|
49
|
-
if (this.debug) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.child = spawn(opts.command, opts.args, {
|
|
54
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
this.child.stdout?.setEncoding("utf8");
|
|
58
|
-
this.child.stdout?.on("data", (chunk: string) => this.onStdout(chunk));
|
|
44
|
+
if (this.debug) this.log(`spawn: ${opts.request.argv.join(" ")}`);
|
|
45
|
+
this.processPromise = opts.environment.spawnSession(opts.request);
|
|
46
|
+
void this.consume();
|
|
47
|
+
}
|
|
59
48
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.stderrBuf = (this.stderrBuf + chunk).slice(-STDERR_CAP);
|
|
64
|
-
if (this.debug) this.log(`stderr: ${chunk.trimEnd()}`);
|
|
65
|
-
});
|
|
49
|
+
private log(message: string): void {
|
|
50
|
+
console.error(`[uai-agent ${this.debug}] ${message}`);
|
|
51
|
+
}
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
private async consume(): Promise<void> {
|
|
54
|
+
try {
|
|
55
|
+
const process = await this.processPromise;
|
|
56
|
+
this.process = process;
|
|
57
|
+
const stdout = this.consumeStdout(process.stdout);
|
|
58
|
+
const stderr = this.consumeStderr(process.stderr);
|
|
59
|
+
const exit = await process.completion;
|
|
60
|
+
await Promise.allSettled([stdout, stderr]);
|
|
61
|
+
this.finish(exit.exitCode);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
this.stderrBuf = (
|
|
64
|
+
this.stderrBuf +
|
|
65
|
+
(error instanceof Error ? error.message : String(error))
|
|
66
|
+
).slice(-STDERR_CAP);
|
|
70
67
|
this.finish(null);
|
|
71
|
-
}
|
|
68
|
+
}
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
private
|
|
75
|
-
|
|
71
|
+
private async consumeStdout(stream: AsyncIterable<Uint8Array>): Promise<void> {
|
|
72
|
+
for await (const chunk of stream) this.onStdout(Buffer.from(chunk).toString("utf8"));
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
private async consumeStderr(stream: AsyncIterable<Uint8Array>): Promise<void> {
|
|
76
|
+
for await (const chunk of stream) {
|
|
77
|
+
const text = Buffer.from(chunk).toString("utf8");
|
|
78
|
+
this.stderrBuf = (this.stderrBuf + text).slice(-STDERR_CAP);
|
|
79
|
+
if (this.debug) this.log(`stderr: ${text.trimEnd()}`);
|
|
80
|
+
}
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
/** Split the rolling buffer on newlines; emit each complete line. */
|
|
86
83
|
private onStdout(chunk: string): void {
|
|
87
84
|
this.stdoutBuf += chunk;
|
|
88
|
-
let
|
|
89
|
-
while ((
|
|
90
|
-
const line = this.stdoutBuf.slice(0,
|
|
91
|
-
this.stdoutBuf = this.stdoutBuf.slice(
|
|
92
|
-
if (line
|
|
85
|
+
let newline: number;
|
|
86
|
+
while ((newline = this.stdoutBuf.indexOf("\n")) >= 0) {
|
|
87
|
+
const line = this.stdoutBuf.slice(0, newline).trim();
|
|
88
|
+
this.stdoutBuf = this.stdoutBuf.slice(newline + 1);
|
|
89
|
+
if (!line) continue;
|
|
93
90
|
if (this.debug) this.log(`<- ${line.slice(0, 1000)}`);
|
|
94
|
-
for (const
|
|
91
|
+
for (const handler of this.lineHandlers) {
|
|
95
92
|
try {
|
|
96
|
-
|
|
93
|
+
handler(line);
|
|
97
94
|
} catch {
|
|
98
|
-
// A broken handler must not wedge
|
|
95
|
+
// A broken protocol handler must not wedge stream consumption.
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
98
|
}
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
|
|
101
|
+
private finish(code: number | null): void {
|
|
102
|
+
if (this.finished) return;
|
|
103
|
+
this.finished = true;
|
|
104
|
+
this.closed = true;
|
|
105
|
+
this.exitCode = code;
|
|
106
|
+
if (this.debug) this.log(`exit: code ${code}`);
|
|
107
|
+
for (const handler of this.exitHandlers) handler(code);
|
|
108
|
+
}
|
|
109
|
+
|
|
105
110
|
onLine(handler: LineHandler): void {
|
|
106
111
|
this.lineHandlers.add(handler);
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
/** Subscribe to process exit. `code` is null on spawn error. */
|
|
110
114
|
onExit(handler: ExitHandler): void {
|
|
111
115
|
this.exitHandlers.add(handler);
|
|
116
|
+
if (this.finished) queueMicrotask(() => handler(this.exitCode));
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
/** Serialise `value` as JSON and write it as one newline-delimited line. */
|
|
115
119
|
writeLine(value: unknown): void {
|
|
116
120
|
if (this.closed) return;
|
|
117
121
|
const json = JSON.stringify(value);
|
|
118
122
|
if (this.debug) this.log(`-> ${json.slice(0, 1000)}`);
|
|
119
|
-
this.
|
|
123
|
+
this.inputChain = this.inputChain
|
|
124
|
+
.then(async () => {
|
|
125
|
+
const process = await this.processPromise;
|
|
126
|
+
await process.write(Buffer.from(`${json}\n`));
|
|
127
|
+
})
|
|
128
|
+
.catch((error: unknown) => {
|
|
129
|
+
this.stderrBuf = (
|
|
130
|
+
this.stderrBuf +
|
|
131
|
+
(error instanceof Error ? error.message : String(error))
|
|
132
|
+
).slice(-STDERR_CAP);
|
|
133
|
+
this.finish(null);
|
|
134
|
+
});
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
/** Last few KB of stderr — surfaced in error events. */
|
|
123
137
|
get stderrTail(): string {
|
|
124
138
|
return this.stderrBuf;
|
|
125
139
|
}
|
|
@@ -128,54 +142,75 @@ export class LineProcess {
|
|
|
128
142
|
return this.closed;
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
/** Terminate the process. Idempotent. */
|
|
132
145
|
async close(): Promise<void> {
|
|
133
|
-
if (this.closed)
|
|
134
|
-
// Already exited — nothing to kill.
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
146
|
+
if (this.closed) return;
|
|
137
147
|
try {
|
|
138
|
-
this.
|
|
148
|
+
const process = this.process ?? (await this.processPromise);
|
|
149
|
+
await process.closeInput().catch(() => {});
|
|
150
|
+
await process.terminate("SIGTERM").catch(() => {});
|
|
139
151
|
} catch {
|
|
140
|
-
//
|
|
152
|
+
// Startup already failed; consume() owns the terminal notification.
|
|
141
153
|
}
|
|
142
|
-
this.child.kill("SIGTERM");
|
|
143
154
|
}
|
|
144
155
|
}
|
|
145
156
|
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
157
|
+
/** Minimal ChildProcess-shaped view retained by the one-shot adapters. */
|
|
158
|
+
export interface AgentEnvironmentProcess {
|
|
159
|
+
readonly stdout: EventEmitter;
|
|
160
|
+
readonly stderr: EventEmitter;
|
|
161
|
+
on(event: "exit", listener: (code: number | null) => void): this;
|
|
162
|
+
on(event: "error", listener: (error: Error) => void): this;
|
|
163
|
+
kill(signal?: "SIGTERM" | "SIGKILL"): boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Bridge a provider stream into the event shape used by one-shot adapters. */
|
|
167
|
+
export function spawnEnvironmentProcess(
|
|
168
|
+
environment: TaskEnvironmentAgentSessionSurface,
|
|
169
|
+
request: TaskEnvironmentSessionRequest,
|
|
170
|
+
): AgentEnvironmentProcess {
|
|
171
|
+
class EnvironmentProcess extends EventEmitter implements AgentEnvironmentProcess {
|
|
172
|
+
readonly stdout = new EventEmitter();
|
|
173
|
+
readonly stderr = new EventEmitter();
|
|
174
|
+
private process: TaskEnvironmentProcess | null = null;
|
|
175
|
+
private readonly processPromise: Promise<TaskEnvironmentProcess>;
|
|
176
|
+
|
|
177
|
+
constructor() {
|
|
178
|
+
super();
|
|
179
|
+
this.processPromise = environment.spawnSession(request);
|
|
180
|
+
void this.consume();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async consume(): Promise<void> {
|
|
184
|
+
try {
|
|
185
|
+
const process = await this.processPromise;
|
|
186
|
+
this.process = process;
|
|
187
|
+
const stdout = pumpEnvironmentStream(process.stdout, this.stdout);
|
|
188
|
+
const stderr = pumpEnvironmentStream(process.stderr, this.stderr);
|
|
189
|
+
const exit = await process.completion;
|
|
190
|
+
await Promise.allSettled([stdout, stderr]);
|
|
191
|
+
this.emit("exit", exit.exitCode);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.emit(
|
|
194
|
+
"error",
|
|
195
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
kill(signal: "SIGTERM" | "SIGKILL" = "SIGTERM"): boolean {
|
|
201
|
+
const process = this.process;
|
|
202
|
+
if (process) void process.terminate(signal).catch(() => {});
|
|
203
|
+
else void this.processPromise.then((pending) => pending.terminate(signal)).catch(() => {});
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
176
206
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
207
|
+
|
|
208
|
+
return new EnvironmentProcess();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function pumpEnvironmentStream(
|
|
212
|
+
stream: AsyncIterable<Uint8Array>,
|
|
213
|
+
target: EventEmitter,
|
|
214
|
+
): Promise<void> {
|
|
215
|
+
for await (const chunk of stream) target.emit("data", Buffer.from(chunk));
|
|
181
216
|
}
|
package/lib/agents/transport.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Agent transport selection (ADR-061). Adapters call createAgentTransport()
|
|
3
3
|
* where they used to build a LineProcess directly; it returns either:
|
|
4
4
|
*
|
|
5
|
-
* -
|
|
5
|
+
* - provider-owned interactive pipes when
|
|
6
6
|
* UAI_DURABLE_SESSIONS=0, or
|
|
7
7
|
* - the durable path: an in-container runner owning the CLI, driven through
|
|
8
8
|
* inbox/outbox files on the workspace mount (DurableProcess).
|
|
@@ -17,9 +17,8 @@
|
|
|
17
17
|
* via its inbox first.
|
|
18
18
|
*
|
|
19
19
|
* The runner script is COPIED into each session dir (not bind-mounted):
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* feature, and each spawn ships the runner version matching this host.
|
|
20
|
+
* the task environment sees the workspace at its provider path, so each
|
|
21
|
+
* durable spawn ships the runner version matching this host.
|
|
23
22
|
*/
|
|
24
23
|
|
|
25
24
|
import {
|
|
@@ -37,8 +36,15 @@ import { and, eq } from "drizzle-orm";
|
|
|
37
36
|
|
|
38
37
|
import { getDb, schema } from "../db";
|
|
39
38
|
import { taskWorkspaceDir } from "../env";
|
|
39
|
+
import { requireContainerRuntimeOperational } from "../runtime-guard";
|
|
40
|
+
import type { TaskEnvironmentAgentSessionSurface } from "../task-environment/types";
|
|
40
41
|
import { DurableProcess, runnerScriptPath } from "./durable-proc";
|
|
41
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
AGENT_SESSION_OUTPUT_BUFFER_BYTES,
|
|
44
|
+
EnvironmentLineProcess,
|
|
45
|
+
type ExitHandler,
|
|
46
|
+
type LineHandler,
|
|
47
|
+
} from "./proc";
|
|
42
48
|
|
|
43
49
|
/** The shared surface adapters program against (LineProcess's shape). */
|
|
44
50
|
export interface LineTransport {
|
|
@@ -53,13 +59,13 @@ export interface LineTransport {
|
|
|
53
59
|
export interface AgentTransportOptions {
|
|
54
60
|
taskId: string;
|
|
55
61
|
agentId: string;
|
|
56
|
-
|
|
57
|
-
/** CLI +
|
|
62
|
+
environment: TaskEnvironmentAgentSessionSurface;
|
|
63
|
+
/** CLI + literal argv as observed inside the task environment. */
|
|
58
64
|
cli: string;
|
|
59
65
|
cliArgs: string[];
|
|
60
|
-
/** Env names forwarded from the host's own
|
|
66
|
+
/** Env names forwarded from the host's own environment. */
|
|
61
67
|
passEnv?: string[];
|
|
62
|
-
/** Explicit per-
|
|
68
|
+
/** Explicit per-session environment values. */
|
|
63
69
|
explicitEnv?: Record<string, string>;
|
|
64
70
|
/**
|
|
65
71
|
* Whether a live runner from a previous host process may be attached to.
|
|
@@ -100,16 +106,13 @@ function durableEnabled(): boolean {
|
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
export function createAgentTransport(opts: AgentTransportOptions): LineTransport {
|
|
109
|
+
// Session creation can happen after channel/task lifecycle queues drain, well
|
|
110
|
+
// after the command-level runtime preflight. Recheck at the actual attach or
|
|
111
|
+
// spawn boundary so a cached ready verdict cannot launch container work.
|
|
112
|
+
requireContainerRuntimeOperational();
|
|
103
113
|
if (!durableEnabled()) {
|
|
104
114
|
clearCurrentSession(opts.taskId, opts.agentId);
|
|
105
|
-
|
|
106
|
-
opts.containerName,
|
|
107
|
-
opts.cli,
|
|
108
|
-
opts.cliArgs,
|
|
109
|
-
opts.passEnv ?? [],
|
|
110
|
-
opts.explicitEnv ?? {},
|
|
111
|
-
);
|
|
112
|
-
return new LineProcess({ command, args, debugLabel: opts.debugLabel });
|
|
115
|
+
return directEnvironmentTransport(opts);
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
const db = getDb();
|
|
@@ -154,6 +157,7 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
154
157
|
opts.allowAttach &&
|
|
155
158
|
row &&
|
|
156
159
|
row.status === "running" &&
|
|
160
|
+
row.containerName === opts.environment.durableIdentity &&
|
|
157
161
|
(row.attachCompatibilityKey ?? null) ===
|
|
158
162
|
(opts.attachCompatibilityKey ?? null) &&
|
|
159
163
|
heartbeatFresh(row.sessionDir)
|
|
@@ -188,13 +192,21 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
188
192
|
".uai",
|
|
189
193
|
"sessions",
|
|
190
194
|
opts.agentId,
|
|
191
|
-
String(Date.now())
|
|
195
|
+
`${String(Date.now())}-${process.pid}`,
|
|
192
196
|
);
|
|
193
197
|
mkdirSync(sessionDir, { recursive: true });
|
|
194
198
|
// Ship this host's runner with the session: works in containers created
|
|
195
199
|
// before this feature (the workspace is mounted at its host path) and
|
|
196
200
|
// never skews against the host version.
|
|
197
201
|
const runnerInSession = join(sessionDir, "runner.mjs");
|
|
202
|
+
const environmentSessionDir = join(
|
|
203
|
+
opts.environment.descriptor.workspacePath,
|
|
204
|
+
".uai",
|
|
205
|
+
"sessions",
|
|
206
|
+
opts.agentId,
|
|
207
|
+
basename(sessionDir),
|
|
208
|
+
);
|
|
209
|
+
const runnerInEnvironment = join(environmentSessionDir, "runner.mjs");
|
|
198
210
|
try {
|
|
199
211
|
copyFileSync(runnerScriptPath(), runnerInSession);
|
|
200
212
|
} catch (err) {
|
|
@@ -205,22 +217,7 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
205
217
|
`[transport] runner unavailable (${err instanceof Error ? err.message : err}) — falling back to direct pipes for ${opts.agentId}`,
|
|
206
218
|
);
|
|
207
219
|
clearCurrentSession(opts.taskId, opts.agentId);
|
|
208
|
-
|
|
209
|
-
opts.containerName,
|
|
210
|
-
opts.cli,
|
|
211
|
-
opts.cliArgs,
|
|
212
|
-
opts.passEnv ?? [],
|
|
213
|
-
opts.explicitEnv ?? {},
|
|
214
|
-
);
|
|
215
|
-
return new LineProcess({ command, args, debugLabel: opts.debugLabel });
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const envArgs: string[] = [];
|
|
219
|
-
for (const name of opts.passEnv ?? []) {
|
|
220
|
-
if (process.env[name]) envArgs.push("-e", name);
|
|
221
|
-
}
|
|
222
|
-
for (const [name, value] of Object.entries(opts.explicitEnv ?? {})) {
|
|
223
|
-
envArgs.push("-e", `${name}=${value}`);
|
|
220
|
+
return directEnvironmentTransport(opts);
|
|
224
221
|
}
|
|
225
222
|
|
|
226
223
|
const proc = new DurableProcess({
|
|
@@ -228,20 +225,22 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
228
225
|
onOffsetAdvance: persistOffset,
|
|
229
226
|
onCloseRequested: markClosed,
|
|
230
227
|
debugLabel: opts.debugLabel,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
opts.
|
|
243
|
-
|
|
244
|
-
|
|
228
|
+
launch: async () => {
|
|
229
|
+
const result = await opts.environment.launchDetachedSession({
|
|
230
|
+
argv: [
|
|
231
|
+
"node",
|
|
232
|
+
runnerInEnvironment,
|
|
233
|
+
environmentSessionDir,
|
|
234
|
+
"--",
|
|
235
|
+
opts.cli,
|
|
236
|
+
...opts.cliArgs,
|
|
237
|
+
],
|
|
238
|
+
inheritEnv: opts.passEnv ?? [],
|
|
239
|
+
env: opts.explicitEnv ?? {},
|
|
240
|
+
launchTimeoutMs: 30_000,
|
|
241
|
+
maxOutputBytes: 256 * 1024,
|
|
242
|
+
});
|
|
243
|
+
return { exitCode: result.exitCode, stderr: result.stderr };
|
|
245
244
|
},
|
|
246
245
|
});
|
|
247
246
|
|
|
@@ -251,7 +250,7 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
251
250
|
taskId: opts.taskId,
|
|
252
251
|
agentId: opts.agentId,
|
|
253
252
|
sessionDir,
|
|
254
|
-
containerName: opts.
|
|
253
|
+
containerName: opts.environment.durableIdentity,
|
|
255
254
|
kind: opts.kind,
|
|
256
255
|
attachCompatibilityKey: opts.attachCompatibilityKey ?? null,
|
|
257
256
|
outboxOffset: 0,
|
|
@@ -263,7 +262,7 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
263
262
|
target: [schema.hostAgentSessions.taskId, schema.hostAgentSessions.agentId],
|
|
264
263
|
set: {
|
|
265
264
|
sessionDir,
|
|
266
|
-
containerName: opts.
|
|
265
|
+
containerName: opts.environment.durableIdentity,
|
|
267
266
|
kind: opts.kind,
|
|
268
267
|
attachCompatibilityKey: opts.attachCompatibilityKey ?? null,
|
|
269
268
|
outboxOffset: 0,
|
|
@@ -278,6 +277,19 @@ export function createAgentTransport(opts: AgentTransportOptions): LineTransport
|
|
|
278
277
|
return claimTail(tailKey, proc);
|
|
279
278
|
}
|
|
280
279
|
|
|
280
|
+
function directEnvironmentTransport(opts: AgentTransportOptions): LineTransport {
|
|
281
|
+
return new EnvironmentLineProcess({
|
|
282
|
+
environment: opts.environment,
|
|
283
|
+
request: {
|
|
284
|
+
argv: [opts.cli, ...opts.cliArgs],
|
|
285
|
+
inheritEnv: opts.passEnv ?? [],
|
|
286
|
+
env: opts.explicitEnv ?? {},
|
|
287
|
+
maxOutputBytes: AGENT_SESSION_OUTPUT_BUFFER_BYTES,
|
|
288
|
+
},
|
|
289
|
+
debugLabel: opts.debugLabel,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
281
293
|
/**
|
|
282
294
|
* Make the opaque generation directory discoverable from inside the task.
|
|
283
295
|
* The relative link resolves on both sides of the workspace bind mount. The
|
package/lib/agents/types.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
|
|
16
|
+
import type { TaskEnvironmentAgentSessionSurface } from "../task-environment/types";
|
|
17
|
+
|
|
16
18
|
/** The host-advertised adapter kind ("claude" | "codex" | "gemini" | …). */
|
|
17
19
|
export const AgentKind = z
|
|
18
20
|
.string()
|
|
@@ -168,8 +170,8 @@ export interface AgentSessionFactory {
|
|
|
168
170
|
create(args: {
|
|
169
171
|
taskId: string;
|
|
170
172
|
agent: RosterAgent;
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
+
/** Provider-owned process/session surface for this task environment. */
|
|
174
|
+
environment: TaskEnvironmentAgentSessionSurface;
|
|
173
175
|
/** Initial briefing — project.defaultPrompt — sent on session start. */
|
|
174
176
|
systemPreamble: string;
|
|
175
177
|
/** Host-derived restricted execution policy (ADR-083). Never supplied by
|
|
@@ -179,8 +181,8 @@ export interface AgentSessionFactory {
|
|
|
179
181
|
* supplies a new key so restart recovery replaces, rather than attaches,
|
|
180
182
|
* a process launched with incompatible argv/preamble/tooling. */
|
|
181
183
|
attachCompatibilityKey?: string;
|
|
182
|
-
/** ADR-048: extra per-agent
|
|
183
|
-
*
|
|
184
|
+
/** ADR-048: extra per-agent process environment (e.g. this agent's own
|
|
185
|
+
* UAI_TASK_TOKEN so its `uai` CLI carries only its own permissions). */
|
|
184
186
|
agentEnv?: Record<string, string>;
|
|
185
187
|
}): Promise<AgentSession>;
|
|
186
188
|
}
|