77 lines
4.1 KiB
JavaScript
77 lines
4.1 KiB
JavaScript
// D2 driver: one managed coder session on the scratch root, real modules from the land tree.
|
|
// Usage: node d2-drive.mjs <pi|claude-code> <turn|noturn>. Prints no credential value.
|
|
import { readFileSync, existsSync, copyFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { homedir } from "node:os";
|
|
const LAND = "/mnt/storage/scratch/sage-r64/land";
|
|
const D2 = "/mnt/storage/scratch/sage-r64/d2";
|
|
const [harness, mode] = process.argv.slice(2);
|
|
const { Client } = await import(`${LAND}/packages/bus/src/client.mjs`);
|
|
const { readSessions, SESSION_CREDENTIALS } = await import(`${LAND}/packages/seat/src/session.mjs`);
|
|
const { bootConfig, loadSystem } = await import(`${LAND}/packages/cli/src/config.mjs`);
|
|
const { startHost, startTimeOf } = await import(`${LAND}/packages/cli/src/host.mjs`);
|
|
const { createLauncher } = await import(`${LAND}/packages/cli/src/launcher.mjs`);
|
|
const { AGENT_MARKERS } = await import(`${LAND}/packages/cli/src/transport.mjs`);
|
|
|
|
const env = { ...process.env, MOSAIC_CONFIG: join(D2, "config", "config.json"), MOSAIC_ROLES_DIR: join(LAND, "roles"), MOSAIC_CLAUDE_BIN: join(homedir(), ".local/bin/claude") };
|
|
for (const k of AGENT_MARKERS) delete env[k];
|
|
delete env.NODE_TEST_CONTEXT;
|
|
for (const k of SESSION_CREDENTIALS.flat()) delete env[k];
|
|
// The host's session credential file holds one name, CLAUDE_CODE_OAUTH_TOKEN. Loaded, never printed.
|
|
for (const line of readFileSync(join(homedir(), ".config/mosaic-dev/secrets/claude-session.env"), "utf8").split("\n")) {
|
|
const m = line.match(/^\s*(?:export\s+)?([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
if (m) env[m[1]] = m[2].replace(/^(['"])(.*)\1$/, "$2");
|
|
}
|
|
console.log("credential names loaded:", SESSION_CREDENTIALS.flat().filter((k) => env[k]).join(","));
|
|
|
|
const business = "mosaic-stack";
|
|
const logs = [];
|
|
const log = (l) => { logs.push(l); console.log("log:", l); };
|
|
const system = loadSystem({ env });
|
|
const boot = bootConfig({ system, businessId: business, env });
|
|
const host = await startHost({ boot, business, log });
|
|
const launcher = createLauncher({ host, system, env, sessionDefaults: { pollInterval: 100 }, log });
|
|
let code = 0;
|
|
try {
|
|
await launcher.listen();
|
|
const bind = async (role, run) => {
|
|
const b = await host.bindLaunch({ business, role, run, harness: "claude-code", pid: process.pid, startTime: startTimeOf(process.pid) });
|
|
const c = new Client({ path: host.path, cap: b.cap });
|
|
await c.call("role.claim");
|
|
return { b, c };
|
|
};
|
|
const chief = await bind("chief", `chief-d2-${harness}`);
|
|
const cto = await bind("cto", `cto-d2-${harness}`);
|
|
const launched = await launcher.fromSession(chief.b.cap, "coder");
|
|
console.log("launch:", JSON.stringify(launched));
|
|
if (!launched.ok) throw new Error("launch refused");
|
|
const run = launched.result.run;
|
|
const runDir = readSessions(boot.dataRoot ?? system.dataRoot).find((s) => s.run === run)?.runDir
|
|
?? join(system.dataRoot, "launches", business, run);
|
|
const manifest = join(runDir, "bundle", "manifest.json");
|
|
for (let i = 0; i < 100 && !existsSync(manifest); i++) await new Promise((r) => setTimeout(r, 100));
|
|
copyFileSync(manifest, join(D2, `manifest-${harness}.json`));
|
|
copyFileSync(join(runDir, "bundle", "prompt.md"), join(D2, `prompt-${harness}.md`));
|
|
console.log("runDir:", runDir);
|
|
if (mode === "turn") {
|
|
const sent = await cto.c.call("message.send", { to: "coder", class: "REQUEST", body: "D2 check for row 64. Reply in one line: list the skill names in your Skills section, and nothing else. Don't use any tool." });
|
|
const end = Date.now() + 240000;
|
|
let reply;
|
|
while (!reply && Date.now() < end) {
|
|
reply = (await cto.c.call("message.receive")).find((m) => m.in_reply_to === sent.id);
|
|
if (!reply) await new Promise((r) => setTimeout(r, 1000));
|
|
}
|
|
console.log("reply:", reply ? JSON.stringify({ from_role: reply.from_role, from_run: reply.from_run, body: reply.body.slice(-600) }) : "NONE in 240 s");
|
|
if (!reply) code = 3;
|
|
} else {
|
|
await new Promise((r) => setTimeout(r, 3000));
|
|
}
|
|
} catch (e) {
|
|
console.log("error:", e.message);
|
|
code = 2;
|
|
} finally {
|
|
await launcher.close();
|
|
await host.close(0);
|
|
}
|
|
process.exit(code);
|