Kaynağa Gözat

fix(user-questions): bridge scoped request events

imccyu 1 ay önce
ebeveyn
işleme
9eb3747ffc

+ 1 - 0
packages/core/scope/src/scoped-events.generated.ts

@@ -34,6 +34,7 @@ const scopedSubjectResolvers: Readonly<Record<string, ScopedSubjectResolver | nu
   'tools/post-execute': args => (args[0] as Record<string, unknown>)['agent'],
   'tools/pre-execute': args => (args[0] as Record<string, unknown>)['agent'],
   'tools/result': args => (args[0] as Record<string, unknown>)['agent'],
+  'user-questions/request': args => (args[0] as Record<string, unknown>)['agent'],
 })
 
 /**

+ 12 - 4
packages/interaction/user-questions/src/index.ts

@@ -10,6 +10,7 @@
 import { Context, Service } from '@deepseek-ai/cordis'
 import type { Agent } from '@deepseek-ai/dsh-agent'
 import { HarnessError } from '@deepseek-ai/dsh-llm'
+import { scopeTarget } from '@deepseek-ai/dsh-scope'
 
 declare module '@deepseek-ai/cordis' {
   interface Context {
@@ -144,11 +145,18 @@ export class UserQuestionService extends Service {
           'BAD_INTENT')
       }
     }
-    if (this.provider === undefined) {
-      throw new UserQuestionError('no user-questions provider is registered', 'NO_PROVIDER')
-    }
+    const askProvider = () => this.provider === undefined
+      ? Promise.reject(new UserQuestionError('no user-questions provider is registered', 'NO_PROVIDER'))
+      : this.provider.ask(request)
     try {
-      return await this.provider.ask(request)
+      return await (agent === undefined
+        ? askProvider()
+        : this.ctx.waterfall(
+          scopeTarget(agent, agent),
+          'user-questions/request',
+          { ...request, agent },
+          askProvider,
+        ))
     } catch (error) {
       if (request.signal?.aborted && !(error instanceof UserQuestionError)) {
         throw abortedQuestion(error)

+ 19 - 0
packages/interaction/user-questions/tests/user-questions.spec.ts

@@ -171,6 +171,25 @@ describe('UserQuestionService', () => {
     expect(result).toEqual({ answers: [{ id: 'confirm', selected: ['yes'] }] })
   })
 
+  it('offers an Agent-scoped waterfall before the provider fallback', async () => {
+    const ctx = new Context()
+    await ctx.plugin(AgentRegistry)
+    await ctx.plugin(UserQuestionService)
+    const p = provider('fallback')
+    ctx.userQuestions.registerProvider(p)
+    const agent = stubAgent('root')
+    ctx.agents.enter(agent, undefined)
+    ctx.on('user-questions/request', request => Promise.resolve({
+      answers: request.questions.map(question => ({ id: question.id, selected: ['remote'] })),
+    }))
+
+    await expect(ctx.userQuestions.ask({
+      questions: [{ id: 'confirm', question: 'Proceed?' }],
+      agent,
+    })).resolves.toEqual({ answers: [{ id: 'confirm', selected: ['remote'] }] })
+    expect(p.seen).toEqual([])
+  })
+
   it('rejects a supplied agent when no live registry can attest it', async () => {
     const ctx = new Context()
     await ctx.plugin(UserQuestionService)