Merge remote-tracking branch 'origin/master' into worktree/agent-execution-context-rfc
# Conflicts: # docs/architecture.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/module-graph.md # examples/coding-agent/tests/code-mode.e2e.ts # packages/context/workspace-context/tests/workspace-context.spec.ts # packages/core/README.md # packages/core/agent-loop/README.md # packages/core/agent-loop/tests/agent.spec.ts # packages/core/agent-loop/tests/cancel.spec.ts # packages/core/agent-loop/tests/config-session-id.spec.ts # packages/core/agent-loop/tests/contract-regressions.spec.ts # packages/core/agent-loop/tests/coverage-edges.spec.ts # packages/core/agent-loop/tests/interception.spec.ts # packages/core/agent-loop/tests/loop.spec.ts # packages/core/agent-loop/tests/properties.spec.ts # packages/core/agent-loop/tests/request-cache.e2e.ts # packages/core/agent-loop/tests/request-reconstruction.spec.ts # packages/core/agent-loop/tests/resume.spec.ts # packages/core/agent-loop/tests/scope-lifecycle.spec.ts # packages/core/agent-loop/tests/tool-calls.spec.ts # packages/core/agent-loop/tests/tool-order.spec.ts # packages/core/agent-loop/tests/turn-stop.spec.ts # packages/workflow/workflow-workerthread/tests/workflow-workerthread.e2e.ts # website/zh-CN/api/harness/agent-loop.md
This commit is contained in:
@@ -4,7 +4,6 @@ import Loader from '@cordisjs/plugin-loader'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import type { ToolExecutionResult } from '@deepseek-ai/dsh-tools'
|
||||
import { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { WorkflowRunId, WorkflowService } from '@deepseek-ai/dsh-workflow'
|
||||
import type { WorkflowResult, WorkflowRun, WorkflowStartRequest } from '@deepseek-ai/dsh-workflow'
|
||||
@@ -12,6 +11,7 @@ import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import WorkerWorkflowEngine from '@deepseek-ai/dsh-workflow-workerthread'
|
||||
import * as toolWorkflow from '../src/index.ts'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
|
||||
/** A controllable engine standing in behind ctx.workflows (the tool's only seam). */
|
||||
class StubEngine extends WorkflowService {
|
||||
@@ -51,7 +51,7 @@ async function setup(config?: { toolName?: string; maxResultChars?: number }) {
|
||||
await ctx.plugin(StubEngine)
|
||||
await ctx.plugin(toolWorkflow, config ?? {})
|
||||
const engine = ctx.workflows as StubEngine
|
||||
const parent = { id: AgentId('caller'), options: {} } as unknown as Agent
|
||||
const parent = { id: SessionId('caller'), options: {} } as unknown as Agent
|
||||
return { ctx, engine, parent }
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ describe('dsh-tool-workflow', () => {
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(WorkerWorkflowEngine, { disposeGraceMs: 30 })
|
||||
await ctx.plugin(toolWorkflow, {})
|
||||
const parent = { id: AgentId('caller'), options: {} } as unknown as Agent
|
||||
const parent = { id: SessionId('caller'), options: {} } as unknown as Agent
|
||||
const controller = new AbortController()
|
||||
const pending = execute(ctx, {
|
||||
script: 'await new Promise(() => {})\nreturn 1',
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
*/
|
||||
|
||||
import * as vm from 'node:vm'
|
||||
import { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { assertSupportedOutputSchema, OutputSchemaError } from '@deepseek-ai/dsh-tools'
|
||||
import type { StructuredOutputSchema } from '@deepseek-ai/dsh-tools'
|
||||
import { isFatalWorkflowError, WorkflowError } from '@deepseek-ai/dsh-workflow'
|
||||
@@ -295,7 +295,7 @@ export class WorkflowExecution {
|
||||
await run.dispose()
|
||||
throw this.cancelledError()
|
||||
}
|
||||
const info: WorkflowAgentInfo = { seq, label, ...phase !== undefined ? { phase } : {}, childId: AgentId(run.id) }
|
||||
const info: WorkflowAgentInfo = { seq, label, ...phase !== undefined ? { phase } : {}, childId: SessionId(run.id) }
|
||||
this.observer.agentStart(info)
|
||||
try {
|
||||
let result
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
@@ -30,7 +30,7 @@ async function setup(script: Script) {
|
||||
await ctx.plugin(spawn, { providerName: 'spawn' })
|
||||
await ctx.plugin(WorkerWorkflowEngine, {})
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
|
||||
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
|
||||
return { ctx, parent, adapter }
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ return { prose, verdict: judged.verdict, confidence: judged.confidence }`,
|
||||
// Both children were disposed to quiescence — no live child agents remain.
|
||||
expect(childIds.length).toBe(2)
|
||||
for (const childId of childIds) {
|
||||
expect(ctx.agents.get(AgentId(childId))).toBeUndefined()
|
||||
expect(ctx.agents.get(SessionId(childId))).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import WorkerWorkflowEngine from '../src/index.ts'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
|
||||
// A fresh thread compiles the source runtime. Leave contention headroom on
|
||||
// shared CI runners without weakening any engine-level timeout assertion.
|
||||
@@ -19,7 +19,7 @@ it('runs the default config through the source worker', async () => {
|
||||
const ctx = new Context()
|
||||
const subagents = await ctx.plugin(SubagentService)
|
||||
const engine = await ctx.plugin(WorkerWorkflowEngine, {})
|
||||
const parent = { id: AgentId('workflow-compat-parent'), options: {} } as unknown as Agent
|
||||
const parent = { id: SessionId('workflow-compat-parent'), options: {} } as unknown as Agent
|
||||
try {
|
||||
const run = ctx.workflows.start({
|
||||
script: 'return 6 * 7',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import LlmService from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
@@ -64,7 +64,6 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('worker workflow engine with-key
|
||||
it('runs a two-phase script in a worker thread over real children, one through the structured runtime', async () => {
|
||||
ctx = await harness()
|
||||
const parentHandle = await ctx.agents.create({
|
||||
agentId: AgentId('wf-worker-e2e-parent'),
|
||||
sessionId: 'wf-worker-e2e-session' as never,
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek-v4-flash' },
|
||||
})
|
||||
@@ -97,7 +96,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('worker workflow engine with-key
|
||||
expect(childIds.length).toBe(2)
|
||||
// The children were disposed to quiescence after collection.
|
||||
for (const childId of childIds) {
|
||||
expect(ctx.agents.get(AgentId(childId))).toBeUndefined()
|
||||
expect(ctx.agents.get(SessionId(childId))).toBeUndefined()
|
||||
}
|
||||
await parentHandle.dispose()
|
||||
}, 240_000)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { fileURLToPath } from 'node:url'
|
||||
import type { Worker } from 'node:worker_threads'
|
||||
import { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import type { SubagentCapabilities, SubagentProvider, SubagentResult, SubagentRun, SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
|
||||
@@ -11,10 +10,11 @@ import type { WorkflowMeta, WorkflowResult, WorkflowResultInfo, WorkflowRunInfo
|
||||
import * as workerEngineModule from '../src/index.ts'
|
||||
import WorkerWorkflowEngine, { type Config } from '../src/index.ts'
|
||||
import { HostToWorkerType, WorkerToHostType } from '../src/protocol.ts'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
|
||||
/** A minimal parent stand-in: the engine only threads it through to the provider. */
|
||||
function fakeParent(): Agent {
|
||||
return { id: AgentId('workflow-parent'), options: {} } as unknown as Agent
|
||||
return { id: SessionId('workflow-parent'), options: {} } as unknown as Agent
|
||||
}
|
||||
|
||||
// Allow cold worker startup on contended CI runners.
|
||||
@@ -104,7 +104,8 @@ class StubProvider implements SubagentProvider {
|
||||
}
|
||||
if (request.signal.aborted) throw new Error('child start aborted before publication')
|
||||
return {
|
||||
id: AgentId(`stub-child-${index}`),
|
||||
id: SessionId(`stub-child-${index}`),
|
||||
localAgent: undefined,
|
||||
result: terminal.promise,
|
||||
dispose: () => {
|
||||
controlled.disposeCalls += 1
|
||||
@@ -361,7 +362,8 @@ describe('dsh-workflow-workerthread', () => {
|
||||
capabilities: { outputSchema: true, depthLimit: true, toolFilter: true, persona: false },
|
||||
inheritsParentContext: false,
|
||||
start: async () => ({
|
||||
id: AgentId('reject-child'),
|
||||
id: SessionId('reject-child'),
|
||||
localAgent: undefined,
|
||||
result: Promise.reject(new Error('backend exploded')),
|
||||
dispose: () => Promise.resolve(),
|
||||
}),
|
||||
@@ -395,7 +397,8 @@ describe('dsh-workflow-workerthread', () => {
|
||||
stopReason: 'completed',
|
||||
} as unknown as SubagentResult
|
||||
const start = vi.spyOn(ctx.subagents, 'start').mockResolvedValue({
|
||||
id: AgentId('raw-invalid-child'),
|
||||
id: SessionId('raw-invalid-child'),
|
||||
localAgent: undefined,
|
||||
result: Promise.resolve(invalid),
|
||||
dispose: () => Promise.resolve(),
|
||||
})
|
||||
@@ -418,7 +421,8 @@ describe('dsh-workflow-workerthread', () => {
|
||||
capabilities: { outputSchema: true, depthLimit: true, toolFilter: true, persona: false },
|
||||
inheritsParentContext: false,
|
||||
start: async () => ({
|
||||
id: AgentId('bad-dispose-child'),
|
||||
id: SessionId('bad-dispose-child'),
|
||||
localAgent: undefined,
|
||||
result: Promise.resolve({ output: [{ type: 'text', text: 'fine' }], stopReason: 'completed' }),
|
||||
cancel: () => { /* settled already */ },
|
||||
dispose: () => { throw new Error('dispose exploded') },
|
||||
@@ -439,7 +443,8 @@ describe('dsh-workflow-workerthread', () => {
|
||||
capabilities: { outputSchema: true, depthLimit: true, toolFilter: true, persona: false },
|
||||
inheritsParentContext: false,
|
||||
start: async () => ({
|
||||
id: AgentId('trap-child'),
|
||||
id: SessionId('trap-child'),
|
||||
localAgent: undefined,
|
||||
result: Promise.resolve({ output: [{ type: 'text', text: 'fine' }], stopReason: 'completed' }),
|
||||
cancel: () => { /* settled already */ },
|
||||
// The rejection VALUE's own coercion throws: a warn built with bare
|
||||
@@ -766,7 +771,8 @@ describe('dsh-workflow-workerthread', () => {
|
||||
settle({ output: [], stopReason: 'aborted' })
|
||||
}, { once: true })
|
||||
return {
|
||||
id: AgentId('signal-only-child'),
|
||||
id: SessionId('signal-only-child'),
|
||||
localAgent: undefined,
|
||||
result,
|
||||
dispose: () => Promise.resolve(),
|
||||
}
|
||||
@@ -1087,7 +1093,8 @@ describe('dsh-workflow-workerthread', () => {
|
||||
expect(request.signal.reason).toBe('workflow worker gone')
|
||||
|
||||
ready.resolve({
|
||||
id: AgentId('late-ready-child'),
|
||||
id: SessionId('late-ready-child'),
|
||||
localAgent: undefined,
|
||||
result: Promise.resolve({ output: [], stopReason: 'aborted' }),
|
||||
dispose: () => {
|
||||
disposeCalls += 1
|
||||
@@ -1123,7 +1130,8 @@ describe('dsh-workflow-workerthread', () => {
|
||||
handle.cancel('reentered from worker-death signal cleanup')
|
||||
}, { once: true })
|
||||
return {
|
||||
id: AgentId('doomed-child'),
|
||||
id: SessionId('doomed-child'),
|
||||
localAgent: undefined,
|
||||
result: new Promise(() => { /* never settles; the reap is the teardown */ }),
|
||||
dispose: () => Promise.reject(new Error('dispose exploded during reap')),
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-brand": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
import type { Agent, AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
|
||||
/** Identifies one workflow run. */
|
||||
export type WorkflowRunId = Branded<'WorkflowRunId'>
|
||||
@@ -141,7 +142,7 @@ export interface WorkflowAgentInfo {
|
||||
/** The phase this agent belongs to (the `phase` option, else the current `phase()` title). */
|
||||
phase?: string
|
||||
/** The child agent's id on the subagent seam. */
|
||||
childId: AgentId
|
||||
childId: SessionId
|
||||
}
|
||||
|
||||
/** How one `agent()` call settled: clean result, child failure (script sees `null`), or run cancellation. */
|
||||
|
||||
Reference in New Issue
Block a user