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

This commit is contained in:
Tianyi Cui
2026-07-20 17:25:21 +08:00
4 changed files with 16 additions and 6 deletions

View File

@@ -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)
}
}
}

View File

@@ -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'),
]))
})
})

View File

@@ -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)

View File

@@ -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 () => {