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
+65
View File
@@ -0,0 +1,65 @@
import { describe, expect, it } from "bun:test";
import { CredentialRefreshCoordinator, runWithCredentialRefresh, bindSessionCredential, credentialRefreshCoordinator, refreshSessionCredential, releaseSessionCredential, requestWithSessionCredential } from "../../src/product/credential-refresh.js";
describe("credential renewal", () => {
it("rejects stale replies and stops repeated requests when the renewed credential is still rejected", async () => {
const session = "rejected-credential-session";
bindSessionCredential(session, { authorization: "Bearer OLD", projectId: "p" });
let calls = 0;
let failures = 0;
const unsubscribe = credentialRefreshCoordinator.subscribe(session, (event) => {
if (event.type === "credential_refresh_required") {
expect(refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "other" }, event.requestId)).toBeFalse();
expect(refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "p" }, "stale")).toBeFalse();
refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "p" }, event.requestId);
} else if (event.type === "credential_refresh_failed") failures += 1;
});
try {
expect((await requestWithSessionCredential(session, async () => { calls += 1; return new Response(null, { status: 401 }); })).status).toBe(401);
await expect(requestWithSessionCredential(session, async () => { calls += 1; return new Response(null, { status: 200 }); })).rejects.toThrow("renewal failed");
expect(calls).toBe(2);
expect(failures).toBe(1);
} finally { unsubscribe(); releaseSessionCredential(session); }
});
it("coalesces concurrent 401 responses, resumes both calls, and retries only once", async () => {
const coordinator = new CredentialRefreshCoordinator(1000);
const old = { authorization: "Bearer TEST_OLD", projectId: "p" };
const renewed = { authorization: "Bearer TEST_NEW", projectId: "p" };
let requests = 0;
coordinator.subscribe("s", (event) => {
if (event.type === "credential_refresh_required") {
requests += 1;
queueMicrotask(() => coordinator.resolve("s", event.requestId, renewed));
}
});
const calls: string[] = [];
const execute = async (credential: typeof old) => {
calls.push(credential.authorization);
return { status: credential.authorization === old.authorization ? 401 : 200 };
};
expect((await Promise.all([runWithCredentialRefresh(coordinator, "s", old, execute), runWithCredentialRefresh(coordinator, "s", old, execute)])).map((result) => result.status)).toEqual([200, 200]);
expect(requests).toBe(1);
expect(calls).toHaveLength(4);
});
it("does not refresh 403 and fails when refresh is unavailable or cancelled", async () => {
const coordinator = new CredentialRefreshCoordinator(1000);
const credential = { authorization: "Bearer TEST", projectId: "p" };
expect((await runWithCredentialRefresh(coordinator, "s", credential, async () => ({ status: 403 }))).status).toBe(403);
await expect(runWithCredentialRefresh(coordinator, "s", credential, async () => ({ status: 401 }))).rejects.toThrow("unavailable");
coordinator.enableBackground("s");
const pending = coordinator.request("s", "access_token_expired");
coordinator.cancelSession("s");
await expect(pending).rejects.toThrow("cancelled");
});
it("renews before near-expiry execution and exposes the same pending request for reconnect", async () => {
const coordinator = new CredentialRefreshCoordinator(1000);
const old = { authorization: "Bearer OLD", projectId: "p", expiresAt: new Date(Date.now() + 1000).toISOString() };
coordinator.subscribe("s", (event) => {
if (event.type === "credential_refresh_required") {
expect(coordinator.getPendingEvent("s")?.requestId).toBe(event.requestId);
coordinator.resolve("s", event.requestId, { authorization: "Bearer NEW", projectId: "p" });
}
});
expect(await runWithCredentialRefresh(coordinator, "s", old, async (credential) => ({ status: 200, authorization: credential.authorization }))).toMatchObject({ authorization: "Bearer NEW" });
});
});
@@ -0,0 +1,63 @@
import { afterEach, describe, expect, it } from "bun:test";
import { ExternalInformationClient, geocodeInput, webSearchInput } from "../../src/product/external-information-client.js";
import { bindSessionCredential, credentialRefreshCoordinator, refreshSessionCredential, releaseSessionCredential } from "../../src/product/credential-refresh.js";
const originalFetch = globalThis.fetch;
const session = "external-information-test";
afterEach(() => { globalThis.fetch = originalFetch; releaseSessionCredential(session); });
describe("typed external information", () => {
it("uses the fixed Server endpoint and trusted project, translating freshness", async () => {
bindSessionCredential(session, { authorization: "Bearer TEST", projectId: "trusted" });
globalThis.fetch = (async (url: Parameters<typeof fetch>[0], init?: RequestInit) => {
expect(String(url)).toBe("http://server.test/api/v1/web-searches");
expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer TEST");
expect(new Headers(init?.headers).get("X-Project-Id")).toBe("trusted");
expect(JSON.parse(String(init?.body))).toEqual({ query: "供水政策", freshness: "oneWeek", summary: true, count: 10, include: ["gov.cn"] });
return Response.json({ data: { webPages: { value: [{ url: "https://gov.cn/example", name: "example" }] } } });
}) as unknown as typeof fetch;
const result = await new ExternalInformationClient("http://server.test").webSearch(session, webSearchInput.parse({ query: "供水政策", freshness: "one_week", include: ["gov.cn"] }));
expect(result).toHaveProperty("data.webPages.value");
});
it("normalizes valid Tianditu coordinates for zoom_to_map and rejects fabricated coordinates", async () => {
bindSessionCredential(session, { authorization: "Bearer TEST", projectId: "trusted" });
globalThis.fetch = (async (url: Parameters<typeof fetch>[0], init?: RequestInit) => {
expect(new URL(String(url)).pathname).toBe("/api/v1/geocoding-requests");
expect(JSON.parse(String(init?.body))).toEqual({ keyword: "北京市人民政府" });
return Response.json({ status: "0", location: { lon: "116.4", lat: "39.9", level: 1 } });
}) as unknown as typeof fetch;
expect(await new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京市人民政府" })).toMatchObject({ source_crs: "EPSG:4326", center: [116.4, 39.9], location: { lon: 116.4, lat: 39.9 } });
for (const data of [null, { status: "0", location: { lon: "", lat: " " } }, { status: "1" }, { status: "0", location: { lon: 181, lat: 39 } }, { status: "0", location: { lon: null, lat: null } }]) {
globalThis.fetch = (async () => Response.json(data)) as unknown as typeof fetch;
await expect(new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京市人民政府" })).rejects.toThrow("no valid coordinate");
}
});
it("renews a rejected credential once and never turns a 403 into a renewal", async () => {
bindSessionCredential(session, { authorization: "Bearer OLD", projectId: "trusted" });
let requests = 0;
const unsubscribe = credentialRefreshCoordinator.subscribe(session, (event) => {
if (event.type === "credential_refresh_required") refreshSessionCredential(session, { authorization: "Bearer NEW", projectId: "trusted" }, event.requestId);
});
try {
globalThis.fetch = (async (_url: Parameters<typeof fetch>[0], init?: RequestInit) => {
requests++;
return new Headers(init?.headers).get("Authorization") === "Bearer OLD" ? new Response(null, { status: 401 }) : Response.json({ status: "0", location: { lon: 116, lat: 39 } });
}) as unknown as typeof fetch;
await new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京" });
expect(requests).toBe(2);
globalThis.fetch = (async () => { requests++; return new Response(null, { status: 403 }); }) as unknown as typeof fetch;
await expect(new ExternalInformationClient("http://server.test").geocode(session, { keyword: "北京" })).rejects.toMatchObject({ status: 403 });
expect(requests).toBe(3);
} finally { unsubscribe(); }
});
it("rejects scope, URL overrides, empty queries and invalid counts before dispatch", async () => {
expect(webSearchInput.safeParse({ query: "test", url: "http://untrusted" }).success).toBeFalse();
expect(webSearchInput.safeParse({ query: "test", project_id: "other" }).success).toBeFalse();
expect(webSearchInput.safeParse({ query: "test", count: 51 }).success).toBeFalse();
expect(webSearchInput.safeParse({ query: "test", include: ["https://example.com/path"] }).success).toBeFalse();
expect(geocodeInput.safeParse({ keyword: " " }).success).toBeFalse();
await expect(new ExternalInformationClient().geocode(session, { keyword: "北京" })).rejects.toMatchObject({ status: 401 });
});
});
+45
View File
@@ -0,0 +1,45 @@
import { afterEach, describe, expect, it } from "bun:test";
import { ProjectDataClient, projectDataQuerySchema } from "../../src/product/project-data-client.js";
import { bindSessionCredential, releaseSessionCredential } from "../../src/product/credential-refresh.js";
import { historyChart } from "../../src/product/history-chart.js";
const originalFetch = globalThis.fetch;
afterEach(() => { globalThis.fetch = originalFetch; releaseSessionCredential("query-session"); });
describe("controlled project queries", () => {
it("dispatches SCADA history to the existing fields endpoint using monitored_value", async () => {
bindSessionCredential("query-session", { authorization: "Bearer TEST", projectId: "trusted-project" });
globalThis.fetch = (async (url) => {
const parsed = new URL(String(url));
expect(parsed.pathname).toBe("/api/v1/timeseries/scada-readings/fields");
expect(parsed.searchParams.get("field")).toBe("monitored_value");
expect(parsed.searchParams.get("device_ids")).toBe("D1,D2");
return Response.json([{ device_id: "D1", value: 20 }]);
}) as typeof fetch;
await new ProjectDataClient("http://server.test").query("query-session", { source: "scada_history", device_ids: ["D1", "D2"], field: "monitored_value", start: "2026-09-01T00:00:00Z", end: "2026-09-01T01:00:00Z" });
});
it("uses fixed routes and the bound project credential, including encoded asset IDs", async () => {
bindSessionCredential("query-session", { authorization: "Bearer TEST", projectId: "trusted-project" });
let requested: URL | undefined;
globalThis.fetch = (async (url, init) => {
requested = new URL(String(url));
expect(new Headers(init?.headers).get("X-Project-Id")).toBe("trusted-project");
return Response.json({ pressure: 20 });
}) as typeof fetch;
expect(await new ProjectDataClient("http://server.test").query("query-session", { source: "element_properties", element_type: "junction", element_id: "J&network=other" })).toEqual({ pressure: 20 });
expect(requested?.pathname).toBe("/api/v1/junctions/properties");
expect(requested?.searchParams.get("junction")).toBe("J&network=other");
expect(requested?.searchParams.has("network")).toBeFalse();
});
it("rejects arbitrary routes, project overrides and excessive time windows before dispatch", () => {
expect(projectDataQuerySchema.safeParse({ source: "scada_devices", project_id: "other" }).success).toBeFalse();
expect(projectDataQuerySchema.safeParse({ source: "http", url: "http://other" }).success).toBeFalse();
expect(projectDataQuerySchema.safeParse({ source: "scada_history", device_ids: ["D1"], field: "monitored_value", start: "2026-09-01T00:00:00Z", end: "2026-09-03T00:00:00Z" }).success).toBeFalse();
});
it("keeps returned units, drops null samples and never manufactures missing history", () => {
expect(() => historyChart({ series: [] }, "J1")).toThrow("NO_HISTORY_DATA");
const chart = historyChart({ series: [{ element_id: "J1", metric: "pressure", source: "scada_raw", source_unit: "m", points: [{ time: "t1", value: null }, { time: "t2", value: 20 }] }] }, "J1");
expect(chart.title).toContain("(m)");
expect(chart.x_data).toEqual(["t2"]);
expect(chart.series[0].data).toEqual([20]);
});
});