refactor: streamline query requests and session storage
Generic Container CI/CD / test-build-publish (push) Successful in 2m4s
Agent CI/CD / build-test-publish-and-deploy (push) Successful in 2m4s

This commit is contained in:
2026-11-09 18:49:32 +08:00
parent 6279a4b555
commit 5ee93329a4
25 changed files with 111 additions and 100 deletions
+30 -3
View File
@@ -5,16 +5,43 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { getLocalAgentContext } from "../../src/context/localContext.js";
import { registerChatInteractionRoutes } from "../../src/routes/chatInteractionRoutes.js";
import type { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
import { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
import { SessionMetadataStore } from "../../src/sessions/metadataStore.js";
import { SessionUiStateStore } from "../../src/sessions/uiStateStore.js";
import { SessionWorkspaceStore } from "../../src/sessions/workspace-store.js";
it("reads a native form once when checking and answering a persisted question", async () => {
const root = await mkdtemp(join(tmpdir(), "question-single-read-"));
const workspaces = new SessionWorkspaceStore(join(root, "workspaces"));
const sessions = new SessionMetadataStore(join(root, "metadata"), workspaces);
const states = new SessionUiStateStore(workspaces);
const context = { ...getLocalAgentContext(), sessionId: "session" };
await sessions.ensure(context);
await states.write(context, { sessionId: context.sessionId, messages: [{ role: "assistant", questions: [{ requestId: "form", status: "pending" }] }] });
const calls: string[] = [];
const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), {
ensureClient: async () => ({ session: { form: {
get: async () => { calls.push("get"); return { fields: [{ key: "answer", type: "text" }], state: { status: "pending" } }; },
reply: async (input: { answer: unknown }) => { calls.push("reply"); expect(input.answer).toEqual({ answer: "A" }); },
} } }),
});
const routes = new Map<string, (req: unknown, res: unknown) => Promise<void>>();
const router = { get() {}, post(path: string, handler: (req: unknown, res: unknown) => Promise<void>) { routes.set(path, handler); } };
registerChatInteractionRoutes(router as unknown as express.Router, { activeRuns: new Map(), runtime, sessionMetadataStore: sessions, sessionUiStateStore: states });
const req = { params: { request_id: "form" }, body: { session_id: "session", answers: [["A"]] }, header() { return undefined; } };
const response = { code: 200, setHeader() {}, status(code: number) { this.code = code; return this; }, json() { return this; } };
try {
await routes.get("/question/:request_id/reply")!(req, response);
expect(response.code).toBe(202);
expect(calls).toEqual(["get", "reply"]);
} finally { await rm(root, { recursive: true, force: true }); }
});
it("reconciles persisted legacy forms and replies after the active run was lost", async () => {
const root = await mkdtemp(join(tmpdir(), "legacy-question-routes-"));
const workspaces = new SessionWorkspaceStore(join(root, "workspaces"));
const sessions = new SessionMetadataStore(join(root, "metadata"), workspaces);
const states = new SessionUiStateStore(join(root, "unused"), workspaces);
const states = new SessionUiStateStore(workspaces);
const context = { ...getLocalAgentContext(), sessionId: "session" };
await sessions.ensure(context);
await states.write(context, { sessionId: "session", messages: [{ id: "old", role: "assistant", questions: [{ requestId: "form", status: "pending", tool: { callID: "call" } }] }, { id: "new", role: "assistant", text: "later message", parts: [{ type: "data-question_request", data: { request_id: "form2", session_id: "session", questions: [], tool: { callID: "call2" } } }, { type: "data-question_request", data: { request_id: "form3", session_id: "session", questions: [] } }, { type: "data-question_request", data: { request_id: "form4", session_id: "session", questions: [] } }] }] });
@@ -22,7 +49,7 @@ it("reconciles persisted legacy forms and replies after the active run was lost"
let unavailable = false;
let reads = 0;
let replies = 0;
const runtime = { getQuestionStatus: async () => { reads++; if (unavailable) throw new Error("private-native-path"); return status; }, replyQuestion: async () => { replies++; status = "answered"; }, rejectQuestion: async () => { status = "cancelled"; } } as unknown as OpencodeRuntimeAdapter;
const runtime = { getQuestion: async () => { reads++; if (unavailable) throw new Error("private-native-path"); return status === "missing" ? null : { state: { status } }; }, replyQuestion: async () => { replies++; status = "answered"; }, rejectQuestion: async () => { status = "cancelled"; } } as unknown as OpencodeRuntimeAdapter;
const app = express(); app.use(express.json());
registerChatInteractionRoutes(app, { activeRuns: new Map(), runtime, sessionMetadataStore: sessions, sessionUiStateStore: states });
const server = app.listen(0, "127.0.0.1");