ソースを参照

fix(core): drain cross-realm execution promises

Yichen Jiang 2 ヶ月 前
親
コミット
b1e19d8b69

+ 1 - 1
docs/cordis-catalog/services.md

@@ -21,7 +21,7 @@ run<T>(execution: AgentExecution | undefined, operation: () => T): T
 
 Types: [AgentExecution](../core-data-structures/core.md)
 
-Source: [`packages/core/agent-execution/src/index.ts:17`](../../packages/core/agent-execution/src/index.ts)
+Source: [`packages/core/agent-execution/src/index.ts:18`](../../packages/core/agent-execution/src/index.ts)
 
 ## `ctx.agentLoop` — `AgentLoop`
 

+ 2 - 1
packages/core/agent-execution/src/index.ts

@@ -6,6 +6,7 @@
 
 import type { Context } from 'cordis'
 import { AsyncLocalStorage } from 'node:async_hooks'
+import { isPromise } from 'node:util/types'
 import type { AgentExecution } from './types.ts'
 
 export type { AgentExecution } from './types.ts'
@@ -75,7 +76,7 @@ class DefaultAgentExecutionService implements AgentExecutionService {
       this.releaseRun()
       throw error
     }
-    if (result instanceof Promise) {
+    if (isPromise(result)) {
       void result.then(
         () => { this.releaseRun() },
         () => { this.releaseRun() },

+ 26 - 0
packages/core/agent-execution/tests/agent-execution.spec.ts

@@ -1,5 +1,6 @@
 import { describe, expect, it } from 'vitest'
 import { Context } from 'cordis'
+import { runInNewContext } from 'node:vm'
 import { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
 import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
 import type { AgentExecution, AgentExecutionService } from '@deepseek-ai/dsh-agent-execution'
@@ -133,4 +134,29 @@ describe('AgentExecutionProvider', () => {
     expect(() => service.current()).toThrow('agent execution service is disposed')
     expect(() => service.require()).toThrow('agent execution service is disposed')
   })
+
+  it('drains cross-realm Promise boundaries before disposal', async () => {
+    const { service, dispose } = await harness()
+    const active = execution('cross-realm')
+    const release = Promise.withResolvers<boolean>()
+    const operation = runInNewContext(
+      '(async () => { await release; inspect() })',
+      {
+        release: release.promise,
+        inspect: () => { expect(service.require()).toBe(active) },
+      },
+    ) as () => Promise<void>
+    const pending = service.run(active, operation)
+    expect(pending).not.toBeInstanceOf(Promise)
+
+    let disposed = false
+    const disposal = dispose().then(() => { disposed = true })
+    await Promise.resolve()
+    expect(disposed).toBe(false)
+
+    release.resolve(true)
+    await pending
+    await disposal
+    expect(disposed).toBe(true)
+  })
 })