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:
Tianyi Cui
2026-07-19 11:44:50 +08:00
295 changed files with 5544 additions and 4182 deletions

View File

@@ -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',

View File

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

View File

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

View File

@@ -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',

View File

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

View File

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

View File

@@ -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": {

View File

@@ -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. */