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:
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user