refactor: streamline query requests and session storage
This commit is contained in:
@@ -31,7 +31,7 @@ it("authenticates state reads and writes against the persisted session owner and
|
||||
const context = getLocalAgentContext(ownerRequest);
|
||||
const sessionId = "state-session";
|
||||
await sessions.ensure({ ...context, sessionId });
|
||||
await new SessionUiStateStore(join(root, "unused"), workspaces).write({ ...context, sessionId }, { sessionId, messages: [{ id: "assistant", role: "assistant", parts: [{ type: "data-answer_ui", data: { version: "answer-ui@1", block_id: "block", fallback_text: "fallback", spec: { root: "evidence", elements: { evidence: { type: "Evidence", props: { title: "Evidence", summary: "Summary", source: "source", evidence_id: null, observed_at: null, kind: "observed" }, children: [] } } } } }] }] });
|
||||
await new SessionUiStateStore(workspaces).write({ ...context, sessionId }, { sessionId, messages: [{ id: "assistant", role: "assistant", parts: [{ type: "data-answer_ui", data: { version: "answer-ui@1", block_id: "block", fallback_text: "fallback", spec: { root: "evidence", elements: { evidence: { type: "Evidence", props: { title: "Evidence", summary: "Summary", source: "source", evidence_id: null, observed_at: null, kind: "observed" }, children: [] } } } } }] }] });
|
||||
const app = express();
|
||||
app.use(express.json({ limit: "2mb" }));
|
||||
app.use(async (req, res, next) => {
|
||||
|
||||
@@ -22,7 +22,7 @@ it("persists owned topics, searches old text across themes and preserves chats w
|
||||
globalThis.fetch = (async (_input, init) => Response.json({ user_id: new Headers(init?.headers).get("authorization") === "Bearer owner" ? "owner" : "other", project_id: new Headers(init?.headers).get("x-project-id"), network: "supply" })) as typeof fetch;
|
||||
const workspaces = new SessionWorkspaceStore(join(root, "workspaces"));
|
||||
const sessions = new SessionMetadataStore(join(root, "metadata"), workspaces);
|
||||
const states = new SessionUiStateStore(join(root, "ui"), workspaces);
|
||||
const states = new SessionUiStateStore(workspaces);
|
||||
for (const id of ["a", "b", "unclassified"]) {
|
||||
await sessions.ensure({ ...context, sessionId: id });
|
||||
await states.write({ ...context, sessionId: id }, { sessionId: id, messages: [
|
||||
|
||||
@@ -7,6 +7,7 @@ import { attachTrustedAgentContext } from "../../src/context/localContext.js";
|
||||
import { registerChatInteractionRoutes } from "../../src/routes/chatInteractionRoutes.js";
|
||||
import { bindSessionCredential, credentialRefreshCoordinator, releaseSessionCredential } from "../../src/product/credential-refresh.js";
|
||||
import { SessionMetadataStore } from "../../src/sessions/metadataStore.js";
|
||||
import { SessionWorkspaceStore } from "../../src/sessions/workspace-store.js";
|
||||
import { SessionUiStateStore } from "../../src/sessions/uiStateStore.js";
|
||||
import { toActorKey, toProjectKey } from "../../src/utils/fileStore.js";
|
||||
import type { ActiveRun } from "../../src/routes/chatUiState.js";
|
||||
@@ -28,7 +29,7 @@ describe("credential refresh HTTP route", () => {
|
||||
const router = express.Router();
|
||||
router.use((req, res, next) => { void attachTrustedAgentContext(req).then(() => next()).catch(() => res.sendStatus(401)); });
|
||||
const activeRuns = new Map<string, ActiveRun>([["credential-route-session", { clientSessionId: "credential-route-session", controller: new AbortController(), messages: [], pendingPermissions: new Map(), pendingQuestions: new Map(), subscribers: new Set(), status: "running" }]]);
|
||||
registerChatInteractionRoutes(router, { activeRuns, runtime: {} as OpencodeRuntimeAdapter, sessionMetadataStore: metadata, sessionUiStateStore: new SessionUiStateStore(join(directory, "ui")) });
|
||||
registerChatInteractionRoutes(router, { activeRuns, runtime: {} as OpencodeRuntimeAdapter, sessionMetadataStore: metadata, sessionUiStateStore: new SessionUiStateStore(new SessionWorkspaceStore(join(directory, "workspaces"))) });
|
||||
app.use(router);
|
||||
const server = app.listen(0, "127.0.0.1");
|
||||
await new Promise<void>((resolve) => server.once("listening", resolve));
|
||||
|
||||
@@ -115,7 +115,7 @@ it("uses authenticated /responses for saved answers and rejects bypass and forei
|
||||
globalThis.fetch = (async (_input, init) => Response.json({ user_id: new Headers(init?.headers).get("Authorization") === "Bearer owner" ? "owner" : "other", project_id: project, network: "fixture" })) as typeof fetch;
|
||||
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 interactions = new InteractionRequestStore(workspaces);
|
||||
const context = { actorKey: toActorKey("product:owner"), projectKey: toProjectKey(`product:${project}`), projectId: project, userId: "owner", sessionId: "owned" };
|
||||
await sessions.ensure(context);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -16,7 +16,7 @@ it.each([false, true])("persists permission replies in the owner's workspace (na
|
||||
const root = await mkdtemp(join(tmpdir(), "permission-persistence-"));
|
||||
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: "permission-session" };
|
||||
await sessions.ensure(context);
|
||||
const messages = [{ id: "answer", role: "assistant", permissions: [] }];
|
||||
|
||||
@@ -17,7 +17,7 @@ it("keeps historical question invalidation across the next live snapshot", async
|
||||
const root = await mkdtemp(join(tmpdir(), "question-persistence-"));
|
||||
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: "history-session" };
|
||||
await sessions.ensure(context);
|
||||
const history: UIMessage[] = [{ id: "old-answer", role: "assistant", parts: [
|
||||
@@ -35,7 +35,7 @@ it("keeps historical question invalidation across the next live snapshot", async
|
||||
stream.apply("token", { content: "正在核查" });
|
||||
await states.write(context, { sessionId: context.sessionId, isTitleManuallyEdited: true, messages: stream.uiMessages() });
|
||||
let nativeReads = 0;
|
||||
const runtime = { getQuestionStatus: async () => { nativeReads++; return "missing"; } } as unknown as OpencodeRuntimeAdapter;
|
||||
const runtime = { getQuestion: async () => { nativeReads++; return null; } } as unknown as OpencodeRuntimeAdapter;
|
||||
const app = express();
|
||||
registerChatInteractionRoutes(app, { activeRuns: new Map([[context.sessionId, run]]), runtime,
|
||||
sessionMetadataStore: sessions, sessionUiStateStore: states });
|
||||
|
||||
@@ -44,7 +44,7 @@ beforeEach(async () => {
|
||||
})) as typeof fetch;
|
||||
const workspaces = new SessionWorkspaceStore(join(root, "workspaces"));
|
||||
const sessions = new SessionMetadataStore(join(root, "metadata"), workspaces);
|
||||
states = new SessionUiStateStore(join(root, "ui"), workspaces);
|
||||
states = new SessionUiStateStore(workspaces);
|
||||
interactions = new InteractionRequestStore(workspaces);
|
||||
await sessions.initialize();
|
||||
const context = { actorKey: toActorKey("product:owner"), projectKey: toProjectKey(`product:${project}`), projectId: project, userId: "owner", sessionId: "owned" };
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("chat session HTTP isolation", () => {
|
||||
|
||||
const workspaces = new SessionWorkspaceStore(join(metadataDir, "workspaces"));
|
||||
const sessionMetadataStore = new SessionMetadataStore(metadataDir, workspaces);
|
||||
const sessionUiStateStore = new SessionUiStateStore(join(metadataDir, "old-ui"), workspaces);
|
||||
const sessionUiStateStore = new SessionUiStateStore(workspaces);
|
||||
await sessionMetadataStore.initialize();
|
||||
const ownerContext = {
|
||||
actorKey: toActorKey("product:user-a"),
|
||||
|
||||
@@ -33,7 +33,7 @@ async function fixture() {
|
||||
const context = { actorKey: toActorKey("product:owner"), projectKey: toProjectKey(`product:${projectId}`), projectId, userId: "owner" };
|
||||
const workspaces = new SessionWorkspaceStore(join(root, "workspaces"));
|
||||
const sessions = new SessionMetadataStore(join(root, "metadata"), workspaces);
|
||||
const states = new SessionUiStateStore(join(root, "states"), workspaces);
|
||||
const states = new SessionUiStateStore(workspaces);
|
||||
const taskNames = new Map([[firstTask, "南桥管道爆管调查"], [secondTask, "城南供水压力复核"]]);
|
||||
const taskReads: string[] = [];
|
||||
let available = true;
|
||||
|
||||
Reference in New Issue
Block a user