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.
49 lines
3.6 KiB
TypeScript
49 lines
3.6 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import type { V2Event, FormInfo } from "@opencode/client";
|
|
import { formAnswers, Opencode2EventNormalizer } from "../../src/runtime/opencode2-events.js";
|
|
import { normalizeQuestionToolPayload } from "../../src/routes/chatStreamEvents.js";
|
|
|
|
const event = (type: string, data: unknown) => ({ type, data, created: 123 }) as V2Event;
|
|
|
|
describe("OpenCode 2 event contract", () => {
|
|
it("ends the product stream on native execution completion", () => {
|
|
const normalizer = new Opencode2EventNormalizer();
|
|
expect(normalizer.normalize(event("session.execution.succeeded", { sessionID: "s" }))).toMatchObject([
|
|
{ type: "session.status", properties: { sessionID: "s", status: { type: "idle" } } },
|
|
{ type: "session.idle", properties: { sessionID: "s" } },
|
|
]);
|
|
});
|
|
it("preserves tool identity, structured output and todo updates", () => {
|
|
const normalizer = new Opencode2EventNormalizer();
|
|
const data = { sessionID: "s", assistantMessageID: "m", id: "call-1" };
|
|
normalizer.normalize(event("session.tool.input.started", { ...data, name: "todowrite" }));
|
|
normalizer.normalize(event("session.tool.called", { ...data, input: { todos: [{ content: "check", status: "pending" }] } }));
|
|
const result = normalizer.normalize(event("session.tool.success", { ...data, content: [{ type: "text", text: "[]" }] }));
|
|
expect(result[0]).toMatchObject({ properties: { part: { callID: "call-1", tool: "todowrite", state: { status: "completed", output: "[]" } } } });
|
|
expect(result[1]).toMatchObject({ type: "todo.updated", properties: { sessionID: "s", todos: [{ content: "check" }] } });
|
|
});
|
|
it("keeps permissions tied to the native session and call", () => {
|
|
const normalizer = new Opencode2EventNormalizer();
|
|
expect(normalizer.normalize(event("permission.asked", { id: "p", sessionID: "s", action: "shell", resources: ["echo ok"], source: { type: "tool", id: "call-2", messageID: "m" } }))[0]).toMatchObject({
|
|
type: "permission.v2.asked", properties: { sessionID: "s", action: "shell", tool: { callID: "call-2", messageID: "m" } },
|
|
});
|
|
});
|
|
it("waits for the native form ID instead of exposing a tool call ID as a question request", () => {
|
|
const normalizer = new Opencode2EventNormalizer();
|
|
const data = { sessionID: "s", assistantMessageID: "m", id: "call-q" };
|
|
normalizer.normalize(event("session.tool.input.started", { ...data, name: "question" }));
|
|
const events = normalizer.normalize(event("session.tool.called", { ...data, input: { questions: [{ header: "验证", question: "是否通过?", options: [] }] } }));
|
|
const part = (events[0] as any).properties.part;
|
|
expect(normalizeQuestionToolPayload(part, part.state.input, "client")).toBeUndefined();
|
|
const forms = normalizer.normalize(event("form.created", { form: { id: "frm_test", sessionID: "s", title: "验证", fields: [{ key: "answer", type: "string", title: "是否通过?", options: [{ value: "ok", label: "通过" }] }] } }));
|
|
expect(forms[0]).toMatchObject({ type: "question.v2.asked", properties: { id: "frm_test", questions: [{ question: "是否通过?" }] } });
|
|
});
|
|
it("maps displayed choices back to form values and preserves multiple selections", () => {
|
|
const form = { id: "form-1", sessionID: "s", title: "选项", fields: [
|
|
{ key: "mode", type: "string", options: [{ label: "快速", value: "fast" }] },
|
|
{ key: "areas", type: "multiselect", options: [{ label: "东区", value: "east" }], custom: true },
|
|
] } as FormInfo;
|
|
expect(formAnswers(form, [["快速"], ["东区", "自选区域"]])).toEqual({ mode: "fast", areas: ["east", "自选区域"] });
|
|
});
|
|
});
|