Selaa lähdekoodia

Merge remote-tracking branch 'origin/master' into fix/subagent-depth-budget

Tianyi Cui 1 kuukausi sitten
vanhempi
sitoutus
02a048ffd9

+ 7 - 2
packages/core/tools/src/index.ts

@@ -958,14 +958,19 @@ export class ToolRegistry extends Service {
     // Freeze the remaining mutable signal slot before observers receive the
     // shared WeakMap-keyable execution object.
     Object.freeze(exec)
+    const { name: toolName, callId } = exec
+    const reportFailure = (error: unknown): void => {
+      this.ctx.logger.warn(`tool "${toolName}" (${callId}): tools/result observer failed: ${errorMessage(error)}`)
+    }
     const callbacks = this.ctx.events.dispatch('emit', [
       scopeTarget(this, exec.agent), 'tools/result', exec, result,
     ])
     for (const callback of callbacks) {
       try {
-        callback(exec, result)
+        const returned: unknown = callback(exec, result)
+        void Promise.resolve(returned).catch(reportFailure)
       } catch (error: unknown) {
-        this.ctx.logger.warn(`tool "${exec.name}" (${exec.callId}): tools/result observer failed: ${errorMessage(error)}`)
+        reportFailure(error)
       }
     }
   }

+ 7 - 2
packages/core/tools/tests/scoped.spec.ts

@@ -582,13 +582,18 @@ describe('scoped execution dispatch', () => {
     ctx.on('tools/result', () => {
       throw { toString: () => { throw new Error('coercion trap') } }
     })
+    ctx.on('tools/result', () => Promise.reject(new Error('async observer failure')) as never)
     ctx.on('tools/result', (_exec, result) => { seen.push(result.isError) })
 
     const result = await ctx.tools.execute({ callId: CallId('final'), name: 't', arguments: {}, agent: key })
+    await Promise.resolve()
     expect(result).toMatchObject({ isError: true, content: [{ type: 'text', text: 'outer failure' }] })
     expect(seen).toEqual([true, true])
     expect(dispatchModes).toEqual(['emit'])
-    expect(warn).toHaveBeenCalledOnce()
-    expect(String(warn.mock.calls[0]?.[0])).toContain('<unprintable thrown value>')
+    expect(warn).toHaveBeenCalledTimes(2)
+    expect(warn.mock.calls.map(call => String(call[0]))).toEqual(expect.arrayContaining([
+      expect.stringContaining('<unprintable thrown value>'),
+      expect.stringContaining('async observer failure'),
+    ]))
   })
 })

+ 1 - 1
packages/examples/agent-spine-demo/src/index.ts

@@ -140,7 +140,7 @@ export function apply(ctx: Context, config: Config): void {
   const nestedDshHome = config.skills?.local?.dshHome
   if (config.dshHome !== undefined && nestedDshHome !== undefined
     && resolveDshHome(config.dshHome) !== resolveDshHome(nestedDshHome)) {
-    throw new Error('agent-core: dshHome and skills.local.dshHome must resolve to the same directory')
+    throw new Error('agent-spine-demo: dshHome and skills.local.dshHome must resolve to the same directory')
   }
   const dshHome = resolveDshHome(config.dshHome ?? nestedDshHome)
 

+ 1 - 1
packages/examples/agent-spine-demo/tests/agent-core.spec.ts

@@ -280,7 +280,7 @@ describe('dsh-agent-spine-demo bundle', () => {
         workspaceContext: false,
         skills: { local: { dshHome: '/nested-dsh-home' } },
       })
-    }).toThrow(/must resolve to the same directory/)
+    }).toThrow('agent-spine-demo: dshHome and skills.local.dshHome must resolve to the same directory')
   })
 
   it('places workspace instructions before the skill catalog in the session prefix', async () => {