feat(core): MCP elicitation support (#35064)
This commit is contained in:
parent
e65477ab1d
commit
efcf2c3f5d
18 changed files with 1170 additions and 668 deletions
|
|
@ -30,11 +30,13 @@ export const createDirSyncContext = (
|
|||
const data = new Proxy({} as State, {
|
||||
get(_, property: keyof State) {
|
||||
if (property === "session_working") return serverSync.session.data.session_working.bind(serverSync.session.data)
|
||||
if (property === "question") return { ...serverSync.session.data.question, global: current()[0].question.global ?? [] }
|
||||
if (sessionFields.has(property)) return serverSync.session.data[property as keyof typeof serverSync.session.data]
|
||||
return current()[0][property]
|
||||
},
|
||||
})
|
||||
const set = ((...input: unknown[]) => {
|
||||
if (input[0] === "question" && input[1] === "global") return (current()[1] as (...args: unknown[]) => unknown)(...input)
|
||||
if (typeof input[0] === "string" && sessionFields.has(input[0])) {
|
||||
return (serverSync.session.set as (...args: unknown[]) => unknown)(...input)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,8 +321,9 @@ export async function bootstrapDirectory(input: {
|
|||
retry(() =>
|
||||
input.sdk.v2.form.request.list().then((x) => {
|
||||
const forms: QuestionForm[] = (x.data?.data ?? []).flatMap((form) => (isQuestionForm(form) ? [form] : []))
|
||||
const ids = forms.map((question) => question.sessionID)
|
||||
const grouped = groupBySession(forms)
|
||||
const global = forms.filter((question) => question.sessionID === "global")
|
||||
const grouped = groupBySession(forms.filter((question) => question.sessionID !== "global"))
|
||||
const ids = Object.keys(grouped)
|
||||
const warm = input.session
|
||||
? Promise.all(ids.map((sessionID) => input.session!.resolve(sessionID))).then(() => undefined)
|
||||
: warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk })
|
||||
|
|
@ -330,11 +331,22 @@ export async function bootstrapDirectory(input: {
|
|||
batch(() => {
|
||||
const current = input.session?.data.question ?? input.store.question
|
||||
for (const sessionID of Object.keys(current)) {
|
||||
if (sessionID === "global") continue
|
||||
if (grouped[sessionID]) continue
|
||||
if (input.session?.get(sessionID)?.directory !== input.directory) continue
|
||||
if (input.session) input.session.set("question", sessionID, [])
|
||||
if (!input.session) input.setStore("question", sessionID, [])
|
||||
}
|
||||
if (global.length > 0 || input.store.question.global) {
|
||||
input.setStore(
|
||||
"question",
|
||||
"global",
|
||||
reconcile(
|
||||
global.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||
{ key: "id" },
|
||||
),
|
||||
)
|
||||
}
|
||||
for (const [sessionID, questions] of Object.entries(grouped)) {
|
||||
const value = reconcile(
|
||||
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||
|
|
|
|||
|
|
@ -527,7 +527,8 @@ describe("applyDirectoryEvent", () => {
|
|||
directory: "/tmp",
|
||||
loadLsp() {},
|
||||
})
|
||||
expect(store.question[sessionID]?.find((x) => x.id === "q_2")?.fields[0]?.description).toBe("updated")
|
||||
const form = store.question[sessionID]?.find((x) => x.id === "q_2")
|
||||
expect(form?.mode === "form" ? form.fields[0]?.description : undefined).toBe("updated")
|
||||
|
||||
applyDirectoryEvent({
|
||||
event: { type: "form.cancelled", properties: { sessionID, id: "q_2" } },
|
||||
|
|
@ -540,6 +541,34 @@ describe("applyDirectoryEvent", () => {
|
|||
expect(store.question[sessionID]?.map((x) => x.id)).toEqual(["q_1", "q_3"])
|
||||
})
|
||||
|
||||
test("tracks global form lifecycles when session content is delegated", () => {
|
||||
const [store, setStore] = createStore(baseState())
|
||||
|
||||
applyDirectoryEvent({
|
||||
event: { type: "form.created", properties: { form: questionRequest("q_1", "global") } },
|
||||
store,
|
||||
setStore,
|
||||
push() {},
|
||||
directory: "/tmp",
|
||||
loadLsp() {},
|
||||
sessionContent: false,
|
||||
})
|
||||
|
||||
expect(store.question.global?.map((x) => x.id)).toEqual(["q_1"])
|
||||
|
||||
applyDirectoryEvent({
|
||||
event: { type: "form.cancelled", properties: { sessionID: "global", id: "q_1" } },
|
||||
store,
|
||||
setStore,
|
||||
push() {},
|
||||
directory: "/tmp",
|
||||
loadLsp() {},
|
||||
sessionContent: false,
|
||||
})
|
||||
|
||||
expect(store.question.global).toEqual([])
|
||||
})
|
||||
|
||||
test("updates vcs branch in store and cache", () => {
|
||||
const [store, setStore] = createStore(baseState({ vcs: { branch: "main", default_branch: "main" } }))
|
||||
const [cacheStore, setCacheStore] = createStore({
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export function applyDirectoryEvent(input: {
|
|||
permission?: State["permission"]
|
||||
}) {
|
||||
const event = input.event
|
||||
if (input.sessionContent === false && SESSION_CONTENT_EVENTS.has(event.type)) return
|
||||
if (input.sessionContent === false && SESSION_CONTENT_EVENTS.has(event.type) && !isGlobalQuestionEvent(event)) return
|
||||
const limit = Math.max(input.store.limit, input.retainedLimit ?? 0)
|
||||
switch (event.type) {
|
||||
case "server.instance.disposed": {
|
||||
|
|
@ -413,3 +413,13 @@ export function applyDirectoryEvent(input: {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isGlobalQuestionEvent(event: { type: string; properties?: unknown }) {
|
||||
if (event.type === "form.created") {
|
||||
const form = (event.properties as { form?: unknown } | undefined)?.form
|
||||
return isQuestionForm(form) && form.sessionID === "global"
|
||||
}
|
||||
if (event.type !== "form.replied" && event.type !== "form.cancelled") return false
|
||||
const properties = event.properties as { sessionID?: unknown } | undefined
|
||||
return properties?.sessionID === "global"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
loadReferencesQuery,
|
||||
} from "./global-sync/bootstrap"
|
||||
import { createChildStoreManager } from "./global-sync/child-store"
|
||||
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
||||
import { applyDirectoryEvent, applyGlobalEvent, isGlobalQuestionEvent } from "./global-sync/event-reducer"
|
||||
import { estimateRootSessionTotal, loadRootSessionsWithFallback } from "./global-sync/session-load"
|
||||
import { trimSessions } from "./global-sync/session-trim"
|
||||
import type { ProjectMeta } from "./global-sync/types"
|
||||
|
|
@ -375,7 +375,7 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
|||
const event = e.details
|
||||
const recent = bootingRoot || Date.now() - bootedAt < 1500
|
||||
|
||||
session.apply(event)
|
||||
if (!isGlobalQuestionEvent(event)) session.apply(event)
|
||||
|
||||
if (directory === "global") {
|
||||
applyGlobalEvent({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue