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:
2026-10-08 15:46:37 +08:00
parent 1a9b0cafc1
commit 7383ed6d13
93 changed files with 8065 additions and 1098 deletions
+28 -31
View File
@@ -1,36 +1,33 @@
import { describe, expect, it } from "bun:test";
import plugin from "../../.opencode/plugins/tjwater-tools.js";
import { frontendActionCallIdPlugin } from "../../.opencode/plugins/frontend-action-call-id.js";
describe("frontend action call ID plugin", () => {
it("injects call IDs only for SCADA frontend actions", async () => {
const hooks = await frontendActionCallIdPlugin({} as never);
const before = hooks["tool.execute.before"];
if (!before) throw new Error("before hook missing");
const actionArgs: Record<string, unknown> = { items: [] };
const actionOutput: { args: Record<string, unknown> } = { args: actionArgs };
await before(
{ tool: "render_scada_analysis", callID: "call-1", sessionID: "s" },
actionOutput,
);
expect(actionOutput.args).toBe(actionArgs);
expect(actionArgs).toEqual({
items: [],
__frontend_action_call_id: "call-1",
});
const regularArgs = { query: "SCADA" };
const regularOutput = { args: regularArgs };
await before(
{ tool: "session_search", callID: "call-2", sessionID: "s" },
regularOutput,
);
expect(regularOutput.args).toBe(regularArgs);
expect(regularArgs).toEqual({ query: "SCADA" });
describe("OpenCode 2 business tool registration", () => {
it("registers business tools for direct dispatch, forwards the native call ID and validates inputs", async () => {
const tools = new Map<string, any>();
const storage = new Map<string, unknown>();
let rpcList: (() => Promise<unknown>) | undefined;
await plugin.setup({
tool: { transform: async (fn: (editor: unknown) => void) => fn({ add: (tool: any) => tools.set(tool.name, tool), remove: (name: string) => tools.delete(name), update: () => {} }), list: async () => Array.from(tools, ([id, tool]) => ({ ...tool, id })) },
shell: { hook: async () => {} },
permission: { hook: async () => {} },
session: { hook: async () => {} },
rpc: { register: async (_definition: unknown, handlers: { list: () => Promise<unknown> }) => { rpcList = handlers.list; } },
storage: { set: async (key: string, value: unknown) => storage.set(key, value), get: async (key: string) => storage.get(key) },
} as never);
expect(tools.get("render_scada_analysis").options).toEqual({ codemode: false, permission: "render_scada_analysis" });
const render = tools.get("render_scada_analysis");
await expect(render.execute({ reason: "test", items: [{ device_id: "x", level: "wrong" }] }, { sessionID: "s", id: "call-native" })).rejects.toThrow();
const todo = tools.get("todowrite");
const todos = [{ content: "check", status: "pending" }];
await todo.execute({ todos }, { sessionID: "s" });
expect(await tools.get("todoread").execute({}, { sessionID: "s" })).toEqual({ content: JSON.stringify(todos) });
expect(await tools.get("todoread").execute({}, { sessionID: "another" })).toEqual({ content: "[]" });
const discovery = await rpcList!();
expect(JSON.parse(JSON.stringify(discovery))).toEqual(discovery);
});
it("exports only plugin functions so OpenCode can load the module", async () => {
const pluginModule = await import("../../.opencode/plugins/frontend-action-call-id.js");
expect(Object.keys(pluginModule)).toEqual(["frontendActionCallIdPlugin"]);
expect(Object.values(pluginModule).every((value) => typeof value === "function")).toBeTrue();
it("exports a single native plugin entrypoint", async () => {
const module = await import("../../.opencode/plugins/tjwater-tools.js");
expect(Object.keys(module)).toEqual(["default"]);
expect(module.default.id).toBe("tjwater.tools");
});
});
+3 -5
View File
@@ -32,11 +32,9 @@ const start = (
};
describe("FrontendActionCoordinator", () => {
it("registers only the two view-risk SCADA actions with 15 second expiry", () => {
expect(frontendActionRegistry).toEqual([
expect.objectContaining({ id: "render_scada_analysis", risk: "view", timeoutMs: 15_000 }),
expect.objectContaining({ id: "clear_scada_analysis", risk: "view", timeoutMs: 15_000 }),
]);
it("registers the finite view actions with 15 second expiry", () => {
expect(frontendActionRegistry.map((action) => action.id).sort()).toEqual(["clear_scada_analysis", "locate_features", "render_scada_analysis", "show_chart", "view_history", "zoom_to_map"]);
expect(frontendActionRegistry.every((action) => action.risk === "view" && action.timeoutMs === 15_000)).toBeTrue();
});
it("emits once and resolves with validated browser output", async () => {