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
+54
View File
@@ -0,0 +1,54 @@
import { type Express } from "express";
import { z } from "zod";
import { executeMemoryManager, executeSkillManager } from "../learning/toolManagers.js";
import { type MemoryStore } from "../memory/store.js";
import { isTrustedInternalBridgeRequest } from "../product/internal-bridge-auth.js";
import { getRuntimeSessionContext } from "../runtime/sessionContext.js";
import { type SkillStore } from "../skills/store.js";
const memoryInput = z.object({
action: z.enum(["add", "list", "replace", "remove"]),
scope: z.string().max(32),
reason: z.string().max(2000).optional(),
content: z.string().max(20000).optional(),
target_id: z.string().max(128).optional(),
}).strict();
const skillInput = z.object({
action: z.enum(["list", "write_skill", "remove_skill", "append_pattern", "remove_pattern", "write_reference", "remove_reference", "write_script", "remove_script"]),
skill_path: z.string().max(512),
reason: z.string().max(2000).optional(),
content: z.string().max(200000).optional(),
file_path: z.string().max(512).optional(),
pattern: z.string().max(20000).optional(),
target_id: z.string().max(128).optional(),
}).strict();
export const registerLearningToolBridge = (app: Express, options: {
internalToken: string;
memoryStore: MemoryStore;
skillStore: SkillStore;
}) => {
for (const name of ["memory-manager", "skill-manager"] as const) {
app.post(`/internal/tools/${name}`, async (req, res) => {
if (!isTrustedInternalBridgeRequest(req, options.internalToken)) {
res.status(403).json({ message: "forbidden" });
return;
}
const context = typeof req.body?.session_id === "string"
? getRuntimeSessionContext(req.body.session_id) : null;
if (!context) {
res.status(404).json({ message: "session context not found" });
return;
}
if (name === "memory-manager") {
const input = memoryInput.safeParse(req.body.input);
if (!input.success) { res.status(400).json({ message: "invalid memory input" }); return; }
res.json(await executeMemoryManager(options.memoryStore, context, input.data));
} else {
const input = skillInput.safeParse(req.body.input);
if (!input.success) { res.status(400).json({ message: "invalid skill input" }); return; }
res.json(await executeSkillManager(options.skillStore, context, input.data));
}
});
}
};