feat(core): session.pending.list API with pending-only session_pending storage (#36126)

This commit is contained in:
Kit Langton 2026-07-09 17:25:40 -04:00 committed by GitHub
parent 99156c10e6
commit a72992e00f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 1048 additions and 711 deletions

View file

@ -1082,6 +1082,14 @@ const scenarios: Scenario[] = [
headers: ctx.headers(),
}))
.json(404, object, "status"),
http.protected
.get("/api/session/{sessionID}/pending", "v2.session.pending.list")
.seeded((ctx) => ctx.session({ title: "Pending list owner" }))
.at((ctx) => ({
path: route("/api/session/{sessionID}/pending", { sessionID: ctx.state.id }),
headers: ctx.headers(),
}))
.json(200, data(array)),
http.protected
.post("/api/session/{sessionID}/revert/stage", "v2.session.revert.stage")
.at((ctx) => ({

View file

@ -26,7 +26,7 @@ import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/se
import { Session } from "@/session/session"
import { MessageID, PartID, SessionID, type SessionID as SessionIDType } from "../../src/session/schema"
import { Database } from "@opencode-ai/core/database/database"
import { SessionInputTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
import { SessionPendingTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
import { SessionMessage } from "@opencode-ai/core/session/message"
import { Agent } from "@opencode-ai/schema/agent"
import { ModelV2 } from "@opencode-ai/core/model"
@ -566,18 +566,18 @@ describe("session HttpApi", () => {
request(`/api/session/${session.id}/prompt`, {
method: "POST",
headers: { ...headers, "content-type": "application/json" },
body: JSON.stringify({ id: "msg_http_prompt", prompt: { text: "hello" }, resume: false }),
body: JSON.stringify({ id: "msg_http_prompt", text: "hello", resume: false }),
})
const first = yield* recordPrompt()
const retried = yield* recordPrompt()
type PromptBody = { id: string; prompt: { text: string }; delivery: string; promotedSeq?: number }
type PromptBody = { id: string; data: { text: string }; delivery: string }
const firstBody = yield* json<{ data: PromptBody }>(first)
const retriedBody = yield* json<{ data: PromptBody }>(retried)
expect(first.status).toBe(200)
expect(retried.status).toBe(200)
expect(retriedBody).toEqual(firstBody)
expect(firstBody).toMatchObject({
data: { id: "msg_http_prompt", prompt: { text: "hello" }, delivery: "steer" },
data: { id: "msg_http_prompt", data: { text: "hello" }, delivery: "steer" },
})
const messages = yield* requestJson<{ data: PromptBody[] }>(`/api/session/${session.id}/message`, {
@ -587,8 +587,8 @@ describe("session HttpApi", () => {
const admitted = yield* Database.Service.use(({ db }) =>
db
.select()
.from(SessionInputTable)
.where(eq(SessionInputTable.id, SessionMessage.ID.make("msg_http_prompt")))
.from(SessionPendingTable)
.where(eq(SessionPendingTable.id, SessionMessage.ID.make("msg_http_prompt")))
.get()
.pipe(Effect.orDie),
)
@ -596,12 +596,11 @@ describe("session HttpApi", () => {
id: "msg_http_prompt",
session_id: session.id,
delivery: "steer",
promoted_seq: null,
})
const conflict = yield* request(`/api/session/${session.id}/prompt`, {
method: "POST",
headers: { ...headers, "content-type": "application/json" },
body: JSON.stringify({ id: "msg_http_prompt", prompt: { text: "goodbye" } }),
body: JSON.stringify({ id: "msg_http_prompt", text: "goodbye" }),
})
expect(conflict.status).toBe(409)
expect(yield* responseJson(conflict)).toEqual({
@ -614,7 +613,7 @@ describe("session HttpApi", () => {
const wake = yield* request(`/api/session/${session.id}/prompt`, {
method: "POST",
headers: { ...headers, "content-type": "application/json" },
body: JSON.stringify({ id: wakeID, prompt: { text: "hello again" } }),
body: JSON.stringify({ id: wakeID, text: "hello again" }),
})
expect(wake.status).toBe(200)
const message = yield* pollWithTimeout(