@runuai/host 0.4.3 → 0.6.0
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/db/migrations/0009_host_agent_sessions.sql +15 -0
- package/db/migrations/meta/_journal.json +7 -0
- package/db/schema.ts +33 -0
- package/lib/agents/claude.ts +28 -13
- package/lib/agents/codex.ts +21 -14
- package/lib/agents/durable-proc.ts +306 -0
- package/lib/agents/transport.ts +229 -0
- package/lib/command-db.ts +10 -2
- package/lib/mcp-connections.ts +77 -45
- package/lib/mcp-gateway.ts +42 -12
- package/lib/orchestrator.ts +103 -9
- package/lib/shared-files.ts +156 -0
- package/lib/standard-image.ts +10 -0
- package/package.json +2 -1
- package/runner/runner.mjs +208 -0
- package/scripts/agent/task-up.sh +36 -0
- package/src/index.ts +10 -0
- package/src/main.ts +51 -0
- package/src/protocol.ts +47 -0
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { agent, AgentError } from "../lib/agent";
|
|
2
2
|
import { cloneRepo } from "../lib/repo-clone";
|
|
3
|
+
import { handleFilesOp } from "../lib/shared-files";
|
|
3
4
|
import { getOrchestrator } from "../lib/orchestrator";
|
|
4
5
|
import { storeTaskCliSecret } from "../lib/agent-cli";
|
|
5
6
|
import { setupTaskGithub, clearRefresh } from "../lib/github-tokens";
|
|
@@ -308,6 +309,15 @@ export const hostCommands: HostCommands = {
|
|
|
308
309
|
}
|
|
309
310
|
},
|
|
310
311
|
|
|
312
|
+
async filesOp(ctx, input) {
|
|
313
|
+
logCommand(ctx, "filesOp", input.op, `${input.scope}:${input.path}`);
|
|
314
|
+
try {
|
|
315
|
+
return ok(await handleFilesOp(input));
|
|
316
|
+
} catch (err) {
|
|
317
|
+
return failFromUnknown(err);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
|
|
311
321
|
async channelInterrupt(ctx, taskId, agentId) {
|
|
312
322
|
logCommand(ctx, "channelInterrupt", taskId, agentId);
|
|
313
323
|
try {
|
package/src/main.ts
CHANGED
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
type TaskDiffInput,
|
|
85
85
|
type TaskDownInput,
|
|
86
86
|
type TaskLaunchInput,
|
|
87
|
+
FilesOpInput,
|
|
87
88
|
} from "./protocol";
|
|
88
89
|
|
|
89
90
|
const PING_INTERVAL_MS = 15_000;
|
|
@@ -875,6 +876,8 @@ function dispatchCommand(
|
|
|
875
876
|
ctx,
|
|
876
877
|
expectAttachmentReadInput(args, 0),
|
|
877
878
|
);
|
|
879
|
+
case "filesOp":
|
|
880
|
+
return hostCommands.filesOp(ctx, expectFilesOpInput(args, 0));
|
|
878
881
|
case "channelInterrupt":
|
|
879
882
|
return hostCommands.channelInterrupt(
|
|
880
883
|
ctx,
|
|
@@ -1123,6 +1126,7 @@ function isHostCommand(command: string): command is keyof HostCommands {
|
|
|
1123
1126
|
"taskDiff",
|
|
1124
1127
|
"attachmentWrite",
|
|
1125
1128
|
"attachmentRead",
|
|
1129
|
+
"filesOp",
|
|
1126
1130
|
"channelInterrupt",
|
|
1127
1131
|
"appendTranscript",
|
|
1128
1132
|
"previewEnsure",
|
|
@@ -1277,6 +1281,37 @@ function expectAttachmentReadInput(
|
|
|
1277
1281
|
};
|
|
1278
1282
|
}
|
|
1279
1283
|
|
|
1284
|
+
/** ADR-062: the THIRD whitelist for filesOp — op/scope enums, relative path
|
|
1285
|
+
* only (containment is re-checked in lib/shared-files.ts, the final gate). */
|
|
1286
|
+
function expectFilesOpInput(args: unknown[], index: number): FilesOpInput {
|
|
1287
|
+
const input = expectRecord(args[index], "files op input");
|
|
1288
|
+
const op = expectStringValue(input.op, "op");
|
|
1289
|
+
if (!["list", "read", "write", "mkdir", "delete"].includes(op)) {
|
|
1290
|
+
throw new Error(`invalid files op: ${op}`);
|
|
1291
|
+
}
|
|
1292
|
+
const scope = expectStringValue(input.scope, "scope");
|
|
1293
|
+
if (scope !== "org" && scope !== "me") {
|
|
1294
|
+
throw new Error(`invalid files scope: ${scope}`);
|
|
1295
|
+
}
|
|
1296
|
+
const out: FilesOpInput = {
|
|
1297
|
+
op: op as FilesOpInput["op"],
|
|
1298
|
+
hostId: typeof input.hostId === "string" ? input.hostId : "",
|
|
1299
|
+
scope,
|
|
1300
|
+
orgId: typeof input.orgId === "string" ? input.orgId : "",
|
|
1301
|
+
userId: typeof input.userId === "string" ? input.userId : "",
|
|
1302
|
+
path: typeof input.path === "string" ? input.path : "",
|
|
1303
|
+
};
|
|
1304
|
+
if (typeof input.offset === "number" && Number.isFinite(input.offset)) {
|
|
1305
|
+
out.offset = input.offset;
|
|
1306
|
+
}
|
|
1307
|
+
if (typeof input.length === "number" && Number.isFinite(input.length)) {
|
|
1308
|
+
out.length = input.length;
|
|
1309
|
+
}
|
|
1310
|
+
if (typeof input.dataBase64 === "string") out.dataBase64 = input.dataBase64;
|
|
1311
|
+
if (input.append === true) out.append = true;
|
|
1312
|
+
return out;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1280
1315
|
function expectChannelEnsureInput(
|
|
1281
1316
|
args: unknown[],
|
|
1282
1317
|
index: number,
|
|
@@ -1334,6 +1369,14 @@ function expectChannelEnsureInput(
|
|
|
1334
1369
|
});
|
|
1335
1370
|
if (connections.length > 0) out.mcpConnections = connections;
|
|
1336
1371
|
}
|
|
1372
|
+
// ADR-062: shared-files mode for the preamble "## Files" section.
|
|
1373
|
+
if (
|
|
1374
|
+
input.sharedFiles === "off" ||
|
|
1375
|
+
input.sharedFiles === "ro" ||
|
|
1376
|
+
input.sharedFiles === "rw"
|
|
1377
|
+
) {
|
|
1378
|
+
out.sharedFiles = input.sharedFiles;
|
|
1379
|
+
}
|
|
1337
1380
|
return out;
|
|
1338
1381
|
}
|
|
1339
1382
|
|
|
@@ -1373,6 +1416,14 @@ function expectTaskCommandTask(value: unknown): TaskCommandTask {
|
|
|
1373
1416
|
}
|
|
1374
1417
|
if (Object.keys(pe).length > 0) out.previewEnv = pe;
|
|
1375
1418
|
}
|
|
1419
|
+
if (typeof row.ownerOrgId === "string") out.ownerOrgId = row.ownerOrgId;
|
|
1420
|
+
if (
|
|
1421
|
+
row.sharedFiles === "off" ||
|
|
1422
|
+
row.sharedFiles === "ro" ||
|
|
1423
|
+
row.sharedFiles === "rw"
|
|
1424
|
+
) {
|
|
1425
|
+
out.sharedFiles = row.sharedFiles;
|
|
1426
|
+
}
|
|
1376
1427
|
return out;
|
|
1377
1428
|
}
|
|
1378
1429
|
|
package/src/protocol.ts
CHANGED
|
@@ -144,6 +144,10 @@ export interface TaskCommandTask {
|
|
|
144
144
|
* `urlEnv` and the cloud has a preview base domain configured.
|
|
145
145
|
*/
|
|
146
146
|
previewEnv?: Record<string, string>;
|
|
147
|
+
/** ADR-062: owning org — locates the org shared-files root on the host. */
|
|
148
|
+
ownerOrgId?: string;
|
|
149
|
+
/** ADR-062: shared-files mount mode ("off" | "ro" | "rw"; default "ro"). */
|
|
150
|
+
sharedFiles?: string;
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
/**
|
|
@@ -203,6 +207,9 @@ export interface ChannelEnsureInput {
|
|
|
203
207
|
/** ADR-057: the owner's usable MCP connections (policy "on", connected,
|
|
204
208
|
* remote). The host writes gateway-URL MCP configs at session start. */
|
|
205
209
|
mcpConnections?: Array<{ id: string; slug: string }>;
|
|
210
|
+
/** ADR-062: shared-files mount mode — drives the preamble "## Files"
|
|
211
|
+
* section ("off" | "ro" | "rw"). */
|
|
212
|
+
sharedFiles?: string;
|
|
206
213
|
globalContext?: string;
|
|
207
214
|
projects: Array<{ slug: string; defaultPrompt: string }>;
|
|
208
215
|
branch: string;
|
|
@@ -256,6 +263,39 @@ export interface TaskDiffResult {
|
|
|
256
263
|
repos: TaskDiffRepo[];
|
|
257
264
|
}
|
|
258
265
|
|
|
266
|
+
/** ADR-062 shared-files op (cloud → host). Scope picks the root:
|
|
267
|
+
* "org" → <workspaceRoot>/shared/orgs/<orgId>, "me" → shared/users/<userId>. */
|
|
268
|
+
export interface FilesOpInput {
|
|
269
|
+
op: "list" | "read" | "write" | "mkdir" | "delete";
|
|
270
|
+
/** Routing only (cloud→bridge): which host's roots to touch. */
|
|
271
|
+
hostId: string;
|
|
272
|
+
scope: "org" | "me";
|
|
273
|
+
orgId: string;
|
|
274
|
+
userId: string;
|
|
275
|
+
/** Relative path inside the scope root ("" = root). */
|
|
276
|
+
path: string;
|
|
277
|
+
/** read: byte offset; default 0. */
|
|
278
|
+
offset?: number;
|
|
279
|
+
/** read: max bytes per chunk (host caps it regardless). */
|
|
280
|
+
length?: number;
|
|
281
|
+
/** write: base64 chunk. */
|
|
282
|
+
dataBase64?: string;
|
|
283
|
+
/** write: append to existing (true) or truncate/create (false/absent). */
|
|
284
|
+
append?: boolean;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export interface FilesEntry {
|
|
288
|
+
name: string;
|
|
289
|
+
dir: boolean;
|
|
290
|
+
size: number;
|
|
291
|
+
mtimeMs: number;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export type FilesOpValue =
|
|
295
|
+
| { entries: FilesEntry[] } // list
|
|
296
|
+
| { dataBase64: string; size: number; eof: boolean } // read
|
|
297
|
+
| Record<string, never>; // write / mkdir / delete
|
|
298
|
+
|
|
259
299
|
export interface HostCommands {
|
|
260
300
|
taskUp(
|
|
261
301
|
ctx: CommandContext,
|
|
@@ -321,6 +361,13 @@ export interface HostCommands {
|
|
|
321
361
|
ctx: CommandContext,
|
|
322
362
|
input: { taskId: string; filename: string },
|
|
323
363
|
): Promise<HostCommandResult<{ dataBase64: string }>>;
|
|
364
|
+
/** ADR-062 shared files — list/read/write/mkdir/delete inside the host's
|
|
365
|
+
* org or personal shared root. Paths are RELATIVE; the host normalizes and
|
|
366
|
+
* rejects escapes. Reads/writes are chunked base64 (offset/append). */
|
|
367
|
+
filesOp(
|
|
368
|
+
ctx: CommandContext,
|
|
369
|
+
input: FilesOpInput,
|
|
370
|
+
): Promise<HostCommandResult<FilesOpValue>>;
|
|
324
371
|
channelInterrupt(
|
|
325
372
|
ctx: CommandContext,
|
|
326
373
|
taskId: string,
|