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
+19 -22
View File
@@ -1,3 +1,4 @@
import { bindSessionCredential, releaseSessionCredential, requestWithSessionCredential } from "./credential-refresh.js";
import { z } from "zod";
import { config } from "../config.js";
@@ -179,7 +180,6 @@ export const taskArtifactBridgeSchema = z.object({
{ message: "at least one task artifact is required" },
);
const credentialVault = new Map<string, ProductCredential>();
const taskRunStates = new Map<string, { runId: string; revision: number }>();
const taskRunMutationQueues = new Map<string, Promise<void>>();
@@ -187,11 +187,11 @@ export const bindOperationalTaskCredential = (
runtimeSessionId: string,
credential: ProductCredential | null,
) => {
if (credential) credentialVault.set(runtimeSessionId, { ...credential });
bindSessionCredential(runtimeSessionId, credential);
};
export const releaseOperationalTaskCredential = (runtimeSessionId: string) => {
credentialVault.delete(runtimeSessionId);
releaseSessionCredential(runtimeSessionId);
taskRunStates.delete(runtimeSessionId);
taskRunMutationQueues.delete(runtimeSessionId);
};
@@ -393,25 +393,22 @@ export class OperationalTaskClient {
}
private async request(runtimeSessionId: string, path: string, init: RequestInit) {
const credential = credentialVault.get(runtimeSessionId);
if (!credential) {
throw new OperationalTaskClientError(401, "task access requires a current product credential");
}
let response: Response;
try {
response = await fetch(new URL(path, this.baseUrl), {
...init,
signal: init.signal ?? AbortSignal.timeout(config.TJWATER_SERVER_TIMEOUT_MS),
headers: {
Accept: "application/json",
Authorization: credential.authorization,
"Content-Type": "application/json",
"X-Project-Id": credential.projectId,
},
});
} catch {
throw new OperationalTaskClientError(503, "TJWater Server task API unavailable");
}
const response = await requestWithSessionCredential(runtimeSessionId, async (credential) => {
try {
return await fetch(new URL(path, this.baseUrl), {
...init,
signal: init.signal ?? AbortSignal.timeout(config.TJWATER_SERVER_TIMEOUT_MS),
headers: {
Accept: "application/json",
Authorization: credential.authorization,
"Content-Type": "application/json",
"X-Project-Id": credential.projectId,
},
});
} catch {
throw new OperationalTaskClientError(503, "TJWater Server task API unavailable");
}
});
const text = await response.text();
const value = parseJson(text);
if (!response.ok) {