Merge branch 'codex/simp-agent-entry-state' into codex/simp-unify-agent-session-id

# Conflicts:
#	docs/architecture.md
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/coding-agent/tests/code-mode.e2e.ts
#	examples/coding-agent/tests/coding-task.e2e.ts
#	examples/coding-agent/tests/compaction.e2e.ts
#	examples/coding-agent/tests/full-loop.e2e.ts
#	examples/coding-agent/tests/todo-write.e2e.ts
#	examples/cordis-agent/tests/cordis-tools.e2e.ts
#	packages/bash/tool-bash/tests/integration.spec.ts
#	packages/bash/tool-bash/tests/tools.spec.ts
#	packages/compact/compact-basic/tests/compact-loop-repro.spec.ts
#	packages/context/time-context/tests/time-context.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/cordis/tool-cordis/tests/integration.spec.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/src/agent.ts
#	packages/core/agent-loop/src/index.ts
#	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-order.spec.ts
#	packages/core/agent-loop/tests/turn-stop.spec.ts
#	packages/core/agent/src/types.ts
#	packages/examples/agent-spine-demo/README.md
#	packages/examples/agent-spine-demo/tests/agent-core.spec.ts
#	packages/examples/stdio-demo/README.md
#	packages/examples/stdio-demo/src/index.ts
#	packages/examples/stdio-demo/tests/stdio-agent.spec.ts
#	packages/fs/tool-fs/tests/fs-tools.e2e.ts
#	packages/guard/repeat-tool-guard/tests/repeat-tool-guard.spec.ts
#	packages/hooks/hooks-claude/tests/bridge.spec.ts
#	packages/hooks/hooks-claude/tests/coverage.spec.ts
#	packages/hooks/hooks-codex/tests/bridge.spec.ts
#	packages/hooks/hooks-codex/tests/coverage.spec.ts
#	packages/subagent/subagent-fork/tests/multi-subagent.spec.ts
#	packages/subagent/subagent-fork/tests/subagent-fork.spec.ts
#	packages/subagent/subagent-inprocess/tests/structured.spec.ts
#	packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts
#	packages/subagent/subagent-spawn/tests/spawn.e2e.ts
#	packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts
#	packages/todo/tool-todo/tests/integration.spec.ts
#	packages/ui/acp/tests/dispose.spec.ts
#	packages/ui/acp/tests/edges.spec.ts
#	packages/workflow/workflow-workerthread/tests/integration.spec.ts
This commit is contained in:
Tianyi Cui
2026-07-18 12:21:15 +08:00
657 changed files with 36658 additions and 7514 deletions

View File

@@ -1,12 +1,8 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import LlmService from '@deepseek-ai/dsh-llm'
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 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'
import SubagentService from '@deepseek-ai/dsh-subagent'
import * as spawn from '@deepseek-ai/dsh-subagent-spawn'
@@ -27,18 +23,14 @@ type Script = ConstructorParameters<typeof MockAdapter>[0]
async function setup(script: Script) {
const ctx = new Context()
const adapter = new MockAdapter(script)
await ctx.plugin(LlmService)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(Invariants)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
await ctx.plugin(spawn, { providerName: 'spawn' })
await ctx.plugin(WorkerWorkflowEngine, {})
ctx.llm.registerAdapter(['mock'], adapter)
const parent = ctx.agentLoop.create(SessionId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent, adapter }
}

View File

@@ -33,7 +33,7 @@ describe('validateMeta', () => {
description: 'migrate call sites',
whenToUse: 'large mechanical sweeps',
phases: [
{ title: 'Discover' },
{ title: 'Discover', provider: 'openai' },
{ title: 'Transform', detail: 'one agent per file', model: 'deepseek-v4-pro' },
],
})
@@ -42,7 +42,7 @@ describe('validateMeta', () => {
description: 'migrate call sites',
whenToUse: 'large mechanical sweeps',
phases: [
{ title: 'Discover' },
{ title: 'Discover', provider: 'openai' },
{ title: 'Transform', detail: 'one agent per file', model: 'deepseek-v4-pro' },
],
})
@@ -73,6 +73,7 @@ describe('validateMeta', () => {
expectInvalid({ name: 'x', description: 'd', phases: [{ title: '' }] }, 'meta.phases[0].title must be a non-empty string')
expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', order: 1 }] }, 'meta.phases[0].order is not a recognized field')
expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', detail: 9 }] }, 'meta.phases[0].detail must be a string')
expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', provider: 9 }] }, 'meta.phases[0].provider must be a string')
expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', model: 9 }] }, 'meta.phases[0].model must be a string')
})

View File

@@ -35,7 +35,7 @@ interface FakeHost {
interface FakeHostOptions {
/** Auto-respond to child-start: reply started + settled per child index. Omit a reply to leave the child pending. */
reply?: (request: { prompt: string; schema?: unknown; model?: string }, index: number) => ChildResult | undefined
reply?: (request: { prompt: string; schema?: unknown; provider?: string; model?: string }, index: number) => ChildResult | undefined
/** Reject the start instead (child-start-error) when returning a string. */
refuse?: (index: number) => string | undefined
/** Auto-send `go` on `ready` (default true). */
@@ -143,6 +143,17 @@ describe('runWorkerSession over an in-process MessageChannel', () => {
host.close()
})
it('agent({provider}) forwards a provider without inventing a model', async () => {
const host = fakeHost({ reply: () => text('ok') })
void runWorkerSession(host.port, init("return await agent('route me', { provider: 'openai' })"))
const result = await host.result()
expect(result.value).toBe('ok')
const start = host.ofType(WorkerToHostType.ChildStart)[0]!
expect(start.request.provider).toBe('openai')
expect(start.request.model).toBeUndefined()
host.close()
})
it('a schema child completing WITHOUT a structured value resolves null with a failed outcome', async () => {
const host = fakeHost({ reply: () => text('prose, no structure') })
void runWorkerSession(host.port, init("return await agent('p', { schema: { type: 'object' } })"))
@@ -330,7 +341,7 @@ describe('runWorkerSession over an in-process MessageChannel', () => {
["return await agent('p', { label: 3 })", '"label" must be a string'],
["return await agent('p', { get label() { throw new Error('read failed') } })", 'options must be plain JSON data'],
["return await agent('p', { bogus: true })", '"bogus" is not recognized'],
["return await agent('p', { effort: 'high' })", '"effort" is deferred'],
["return await agent('p', { effort: 'high' })", '"effort" is deferred and not supported by this engine (supported: label, phase, schema, provider, model)'],
["return await agent('p', { schema: { type: 'object', oneOf: [] } })", 'outside the supported subset'],
['return await parallel([() => 1, () => 2, () => 3])', 'over the per-call cap (2)'],
['return await pipeline([1, 2, 3], (x) => x)', 'maxItemsPerCall'],

View File

@@ -36,7 +36,7 @@ async function harness(): Promise<Context> {
await built.plugin(ToolRegistry)
await built.plugin(AgentRegistry)
await built.plugin(AgentLoop, { agents: [] })
await built.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
await built.plugin(LlmDeepSeek)
await built.plugin(SubagentService)
await built.plugin(Spawn, { providerName: 'spawn' })
await built.plugin(WorkerWorkflowEngine, { provider: 'spawn' })
@@ -64,7 +64,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('worker workflow engine with-key
ctx = await harness()
const parentHandle = await ctx.agents.create({
sessionId: 'wf-worker-e2e-session' as never,
agentOptions: { model: 'deepseek-v4-flash' },
agentOptions: { provider: 'deepseek', model: 'deepseek-v4-flash' },
})
const events: string[] = []

View File

@@ -223,6 +223,14 @@ describe('dsh-workflow-workerthread', () => {
expect(provider.runs[0]!.request.parent).toBeDefined()
})
it('agent({provider}) forwards provider-only agentOptions across the thread', async () => {
const { ctx, parent, provider } = await setup()
const result = await run(ctx, parent, scripted("return await agent('route me', { provider: 'openai' })"))
expect(result.value).toBe('stub reply')
expect(provider.runs[0]!.request.agentOptions).toEqual({ provider: 'openai' })
})
it('a fatal hook error inside the worker kills the script and reports the error', async () => {
const { ctx, parent } = await setup()
const result = await run(ctx, parent, scripted("return await parallel([() => agent('x', { isolation: 'worktree' })])"))