fixup! refactor(interaction): move Approval and Question into UI owners

This commit is contained in:
imccyu
2026-08-23 16:28:18 +08:00
parent f13fb4daeb
commit 55dd6320d2
6 changed files with 32 additions and 26 deletions
@@ -36,7 +36,7 @@ async function answerApproval(
owner: ClientContext,
request: ClientApprovalRequest,
next: ClientApprovalNext,
attend: (pending: PendingApproval) => () => void,
registerPendingInteraction: (pending: PendingApproval) => () => void,
): Promise<ClientApprovalOutcome> {
const sessionId = ctx.sessions.scopeOf(owner)
if (sessionId === undefined) return next()
@@ -48,7 +48,7 @@ async function answerApproval(
...(request.reason === undefined ? {} : { reason: request.reason }),
...(request.signal === undefined ? {} : { signal: request.signal }),
})
const remove = attend(pending)
const remove = registerPendingInteraction(pending)
try {
return await pending.result
} finally {
@@ -62,7 +62,9 @@ async function answerApproval(
*/
export function apply(ctx: ClientContext): void {
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-approval: dictionaries')
const attend = ctx.uiSession.attend<PendingApproval>(() => 0)
const registerPendingInteraction = ctx.uiSession.registerPendingInteraction<PendingApproval>(
() => 0,
)
ctx.slots.inject('conversation.composer', () => ctx.slots.register({
name: 'conversation.composer',
priority: 1,
@@ -74,6 +76,6 @@ export function apply(ctx: ClientContext): void {
},
}, ApprovalPanel))
ctx.remote.$on('approval/request', function (request, next) {
return answerApproval(ctx, this, request, next, attend)
return answerApproval(ctx, this, request, next, registerPendingInteraction)
})
}
@@ -28,7 +28,7 @@ interface PluginBench {
readonly ctx: Context
readonly listener: ApprovalListener
readonly pending: { getSnapshot(): readonly PendingApproval[] }
readonly attend: ReturnType<typeof vi.fn>
readonly registerPendingInteraction: ReturnType<typeof vi.fn>
readonly disposeSlot: ReturnType<typeof vi.fn>
readonly disposeLocale: ReturnType<typeof vi.fn>
readonly register: ReturnType<typeof vi.fn>
@@ -53,7 +53,7 @@ function setupPlugin(): PluginBench {
const disposeSlot = vi.fn()
const disposeLocale = vi.fn()
let pending: readonly PendingApproval[] = []
const attend = vi.fn((_precedence: (value: PendingApproval) => number) => (
const registerPendingInteraction = vi.fn((_precedence: (value: PendingApproval) => number) => (
value: PendingApproval,
) => {
pending = [...pending, value]
@@ -77,7 +77,7 @@ function setupPlugin(): PluginBench {
},
} as never)
ctx.provide('sessions', { scopeOf } as never)
ctx.provide('uiSession', { attend } as never)
ctx.provide('uiSession', { registerPendingInteraction } as never)
ctx.provide('slots', { inject: injectSlot, register } as never)
ctx.provide('locale', {
register: vi.fn(() => disposeLocale),
@@ -89,7 +89,7 @@ function setupPlugin(): PluginBench {
ctx,
listener,
pending: { getSnapshot: () => pending },
attend,
registerPendingInteraction,
disposeSlot,
disposeLocale,
register,
@@ -282,7 +282,7 @@ export class UiSession extends Service {
* @param precedence - deterministic cross-domain precedence; larger values win.
* @returns a function that publishes one exact interaction until its disposer runs.
*/
attend<T extends SessionPendingInteractionBase>(
registerPendingInteraction<T extends SessionPendingInteractionBase>(
precedence: (interaction: T) => number,
): (interaction: T) => () => void {
const domain = new PendingInteractionDomain(precedence, () => {
@@ -297,7 +297,7 @@ export class UiSession extends Service {
if (index !== -1) this.pendingDomains.splice(index, 1)
this.publishPendingInteractions()
}
}, 'uiSession.attend()')
}, 'uiSession.registerPendingInteraction()')
return interaction => domain.publish(interaction)
}
@@ -380,8 +380,10 @@ describe('UiSession pending interactions', () => {
const id = sessionId('s1')
const listener = vi.fn()
const off = service.pendingInteractions.subscribe(listener)
const attendApproval = service.attend<SessionPendingInteractionBase>(() => 0)
const attendQuestion = service.attend<SessionPendingInteractionBase>(
const registerApproval = service.registerPendingInteraction<SessionPendingInteractionBase>(
() => 0,
)
const registerQuestion = service.registerPendingInteraction<SessionPendingInteractionBase>(
interaction => interaction.kind === 'plan-review' ? 2 : 1,
)
listener.mockClear()
@@ -390,13 +392,13 @@ describe('UiSession pending interactions', () => {
const duplicate = { key: 'approval:2', kind: 'approval', sessionId: id }
const question = { key: 'question:1', kind: 'question', sessionId: id }
const plan = { key: 'question:2', kind: 'plan-review', sessionId: id }
const removeApproval = attendApproval(approval)
const removeApproval = registerApproval(approval)
expect(service.pendingInteractions.getSnapshot().get(id)).toBe(approval)
const removeDuplicate = attendApproval(duplicate)
const removeDuplicate = registerApproval(duplicate)
expect(service.pendingInteractions.getSnapshot().get(id)).toBe(duplicate)
const removeQuestion = attendQuestion(question)
const removeQuestion = registerQuestion(question)
expect(service.pendingInteractions.getSnapshot().get(id)).toBe(question)
const removePlan = attendQuestion(plan)
const removePlan = registerQuestion(plan)
expect(service.pendingInteractions.getSnapshot().get(id)).toBe(plan)
removeQuestion()
@@ -414,10 +416,12 @@ describe('UiSession pending interactions', () => {
const ctx = new Context()
const bench = createSessionsBench(ctx)
const service = createUiSession(ctx, bench)
const attend = service.attend<SessionPendingInteractionBase>(() => 1)
const registerPendingInteraction = service.registerPendingInteraction<SessionPendingInteractionBase>(
() => 1,
)
const interaction = { key: 'question:1', kind: 'question', sessionId: sessionId('s1') }
const remove = attend(interaction)
expect(() => { attend(interaction) })
const remove = registerPendingInteraction(interaction)
expect(() => { registerPendingInteraction(interaction) })
.toThrow("ui-session: duplicate pending interaction key 'question:1'")
const failure = new Error('pending subscriber failed')
@@ -55,12 +55,12 @@ async function answerQuestion(
owner: ClientContext,
request: ClientQuestionRequest,
next: ClientQuestionNext,
attend: (pending: PendingQuestion) => () => void,
registerPendingInteraction: (pending: PendingQuestion) => () => void,
): Promise<ClientQuestionAnswer> {
const sessionId = ctx.sessions.scopeOf(owner)
if (sessionId === undefined) return next()
const pending = new PendingQuestion(sessionId, request.questions, request.signal)
const remove = attend(pending)
const remove = registerPendingInteraction(pending)
try {
return await pending.result
} finally {
@@ -76,7 +76,7 @@ async function answerQuestion(
*/
export function apply(ctx: ClientContext): void {
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-user-questions: dictionaries')
const attend = ctx.uiSession.attend<PendingQuestion>(
const registerPendingInteraction = ctx.uiSession.registerPendingInteraction<PendingQuestion>(
pending => pending.kind === 'plan-review' ? 2 : 1,
)
ctx.slots.inject('conversation.composer', () => ctx.slots.register(
@@ -89,6 +89,6 @@ export function apply(ctx: ClientContext): void {
QuestionComposer,
))
ctx.remote.$on('user-questions/request', function (request, next) {
return answerQuestion(ctx, this, request, next, attend)
return answerQuestion(ctx, this, request, next, registerPendingInteraction)
})
}
@@ -50,13 +50,13 @@ async function bench(declare = true) {
)[SESSION_SCOPE])
ctx.provide('sessions', { scopeOf } as never)
let pending: readonly PendingQuestion[] = []
const attend = vi.fn((_precedence: (value: PendingQuestion) => number) => (
const registerPendingInteraction = vi.fn((_precedence: (value: PendingQuestion) => number) => (
value: PendingQuestion,
) => {
pending = [...pending, value]
return () => { pending = pending.filter(candidate => candidate !== value) }
})
ctx.provide('uiSession', { attend } as never)
ctx.provide('uiSession', { registerPendingInteraction } as never)
let listener: QuestionListener | undefined
const on = vi.fn((event: string, value: QuestionListener) => {
expect(event).toBe('user-questions/request')
@@ -81,7 +81,7 @@ async function bench(declare = true) {
agent,
scopeOf,
pending: { getSnapshot: () => pending },
attend,
registerPendingInteraction,
on,
fiber,
invoke,