feat(agent): migrate OpenCode 2 runtime and sandbox tools
Upgrade the runtime and native plugin API to OpenCode 2.0.24. Restore session-scoped learning, credential renewal, typed project queries and controlled frontend actions from the legacy Agent. Replace host Shell execution with an approved Landlock/seccomp executor and persistent per-session workspaces. Add typed Bocha search and Tianditu geocoding bridges using the owning session credential. Require a working kernel sandbox before startup and persist workspace files in deployments. Validation completed before this commit: TypeScript checks, 150 Bun tests, Docker build and UID 10001 sandbox execution, native Shell approval and session recovery. Tianditu succeeded; Bocha currently reports exhausted account quota. Unrelated investigation-report contract work is excluded.
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { mkdir, readFile } from "node:fs/promises";
|
||||
import { createServer } from "node:net";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import dotenv from "dotenv";
|
||||
import { serviceOpencodeConfig } from "../src/runtime/opencode2-config.js";
|
||||
import { startOpencode2 } from "../src/runtime/opencode2-server.js";
|
||||
|
||||
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
dotenv.config({ path: join(projectRoot, ".local.env"), override: false, quiet: true });
|
||||
@@ -14,6 +16,7 @@ const opencodePort = readPort("INTEGRATION_OPENCODE_PORT", 14_096);
|
||||
const dataRoot = resolve(
|
||||
process.env.INTEGRATION_DATA_DIR ?? join(projectRoot, "data", `integration-${agentPort}`),
|
||||
);
|
||||
process.env.OPENCODE_SKILLS_ROOT_DIR = join(dataRoot, "skills");
|
||||
const bridgeToken = process.env.AGENT_INTERNAL_TOKEN?.trim() || randomBytes(32).toString("hex");
|
||||
const agentBaseUrl = `http://127.0.0.1:${agentPort}`;
|
||||
const opencodeBaseUrl = `http://127.0.0.1:${opencodePort}`;
|
||||
@@ -25,26 +28,25 @@ await mkdir(dataRoot, { recursive: true });
|
||||
const sharedEnvironment = {
|
||||
...process.env,
|
||||
AGENT_INTERNAL_TOKEN: bridgeToken,
|
||||
CONVERSATION_WORKSPACE_ROOT: join(dataRoot, "conversation-workspaces"),
|
||||
TJWATER_AGENT_WORKSPACE_ROOT: join(dataRoot, "conversation-workspaces"),
|
||||
TJWATER_AGENT_INTERNAL_BASE_URL: agentBaseUrl,
|
||||
// Retained for tools loaded by an older compatible OpenCode process.
|
||||
TJWATER_AGENT_INTERNAL_TOKEN: bridgeToken,
|
||||
};
|
||||
|
||||
const opencode = Bun.spawn([
|
||||
"opencode",
|
||||
"serve",
|
||||
"--hostname=127.0.0.1",
|
||||
`--port=${opencodePort}`,
|
||||
], {
|
||||
cwd: projectRoot,
|
||||
const opencode = await startOpencode2({
|
||||
command: process.env.OPENCODE_COMMAND ?? "opencode2",
|
||||
hostname: "127.0.0.1",
|
||||
port: opencodePort,
|
||||
timeout: 30_000,
|
||||
config: serviceOpencodeConfig(JSON.parse(await readFile(join(projectRoot, "opencode.json"), "utf8"))),
|
||||
env: {
|
||||
...sharedEnvironment,
|
||||
XDG_CACHE_HOME: join(dataRoot, "xdg-cache"),
|
||||
XDG_DATA_HOME: join(dataRoot, "xdg-data"),
|
||||
XDG_STATE_HOME: join(dataRoot, "xdg-state"),
|
||||
},
|
||||
stderr: "inherit",
|
||||
stdout: "inherit",
|
||||
});
|
||||
|
||||
let agent: ReturnType<typeof Bun.spawn> | undefined;
|
||||
@@ -53,7 +55,7 @@ const shutdown = async () => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
agent?.kill("SIGTERM");
|
||||
opencode.kill("SIGTERM");
|
||||
opencode.close();
|
||||
await Promise.allSettled([
|
||||
...(agent ? [agent.exited] : []),
|
||||
opencode.exited,
|
||||
@@ -64,7 +66,7 @@ process.once("SIGINT", () => void shutdown());
|
||||
process.once("SIGTERM", () => void shutdown());
|
||||
|
||||
try {
|
||||
await waitUntilHealthy(`${opencodeBaseUrl}/global/health`, "OpenCode", opencode);
|
||||
await opencode.client.server.info();
|
||||
agent = Bun.spawn([process.execPath, "src/server.ts"], {
|
||||
cwd: projectRoot,
|
||||
env: {
|
||||
@@ -73,6 +75,7 @@ try {
|
||||
PORT: String(agentPort),
|
||||
OPENCODE_MODE: "client",
|
||||
OPENCODE_CLIENT_BASE_URL: opencodeBaseUrl,
|
||||
OPENCODE_CLIENT_PASSWORD: opencode.password,
|
||||
LLM_REQUEST_AUDIT_LOG_PATH: join(dataRoot, "logs", "llm-request-audit.log"),
|
||||
LEARNING_AUDIT_LOG_PATH: join(dataRoot, "logs", "learning-audit.log"),
|
||||
MEMORY_STORAGE_DIR: join(dataRoot, "memory"),
|
||||
|
||||
Reference in New Issue
Block a user