Merge origin/master into codex/invariant-service-seam
# Conflicts: # docs/module-graph.md
This commit is contained in:
85 files changed
+2563
-729
No files matched your search
@@ -27,16 +27,30 @@ import WorkerWorkflowEngine from '@deepseek-ai/dsh-workflow-workerthread'
|
||||
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(WorkerWorkflowEngine, {})
|
||||
let selectedStarts = 0
|
||||
ctx.subagents.registerProvider({
|
||||
name: 'built-selected',
|
||||
capabilities: { outputSchema: true, depthLimit: false, toolFilter: false, persona: false },
|
||||
inheritsParentContext: false,
|
||||
async start() {
|
||||
selectedStarts += 1
|
||||
return {
|
||||
id: 'built-child',
|
||||
result: Promise.resolve({ output: [], structured: { answer: 42 }, stopReason: 'completed' }),
|
||||
dispose: () => Promise.resolve(),
|
||||
}
|
||||
},
|
||||
})
|
||||
await ctx.plugin(WorkerWorkflowEngine, { provider: 'must-not-be-used' })
|
||||
const run = ctx.workflows.start({
|
||||
script: 'return 6 * 7',
|
||||
script: "const value = await agent('answer', { schema: { type: 'object', properties: { answer: { type: 'number' } }, required: ['answer'] } }); return value.answer",
|
||||
meta: { name: 'built-smoke', description: 'built worker smoke' },
|
||||
// A zero-agent script never touches the provider.
|
||||
subagentProvider: 'built-selected',
|
||||
parent: { id: 'built-smoke-parent', options: {} },
|
||||
})
|
||||
const result = await run.result
|
||||
await run.dispose()
|
||||
if (result.stopReason !== 'completed' || result.value !== 42) {
|
||||
if (result.stopReason !== 'completed' || result.value !== 42 || selectedStarts !== 1) {
|
||||
console.error('unexpected result: ' + JSON.stringify(result))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -392,6 +392,7 @@ describe('runWorkerSession over an in-process MessageChannel', () => {
|
||||
const result = await host.result()
|
||||
expect(result.stopReason).toBe('error')
|
||||
expect(result.error).toContain('total agent cap (2)')
|
||||
expect(result.error).toContain('applicable maxTotalAgents limit')
|
||||
expect(result.agentsStarted).toBe(2)
|
||||
host.close()
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import { expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import type { SubagentProvider } from '@deepseek-ai/dsh-subagent'
|
||||
import WorkerWorkflowEngine from '../src/index.ts'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
|
||||
@@ -18,6 +19,13 @@ vi.setConfig({ testTimeout: 30_000 })
|
||||
it('runs the default config through the source worker', async () => {
|
||||
const ctx = new Context()
|
||||
const subagents = await ctx.plugin(SubagentService)
|
||||
const provider: SubagentProvider = {
|
||||
name: 'spawn',
|
||||
capabilities: { outputSchema: true, depthLimit: true, toolFilter: true, persona: true },
|
||||
inheritsParentContext: false,
|
||||
start: () => Promise.reject(new Error('source-worker compat script must not start a child')),
|
||||
}
|
||||
ctx.subagents.registerProvider(provider)
|
||||
const engine = await ctx.plugin(WorkerWorkflowEngine, {})
|
||||
const parent = { id: SessionId('workflow-compat-parent'), options: {} } as unknown as Agent
|
||||
try {
|
||||
|
||||
@@ -6,7 +6,7 @@ import Loader from '@cordisjs/plugin-loader'
|
||||
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'
|
||||
import type { WorkflowMeta, WorkflowResult, WorkflowResultInfo, WorkflowRunInfo } from '@deepseek-ai/dsh-workflow'
|
||||
import type { WorkflowMeta, WorkflowResult, WorkflowResultInfo, WorkflowRun, WorkflowRunInfo } from '@deepseek-ai/dsh-workflow'
|
||||
import * as workerEngineModule from '../src/index.ts'
|
||||
import WorkerWorkflowEngine, { type Config } from '../src/index.ts'
|
||||
import { HostToWorkerType, WorkerToHostType } from '../src/protocol.ts'
|
||||
@@ -232,6 +232,97 @@ describe('dsh-workflow-workerthread', () => {
|
||||
expect(provider.runs[0]!.request.agentOptions).toEqual({ provider: 'openai' })
|
||||
})
|
||||
|
||||
it('a start-request provider override selects every child without changing the engine default', async () => {
|
||||
const { ctx, parent, provider } = await setup()
|
||||
const selected = new StubProvider('selected', () => text('selected reply'))
|
||||
ctx.subagents.registerProvider(selected)
|
||||
|
||||
const overridden = ctx.workflows.start({
|
||||
...scripted("return await agent('route this run')"),
|
||||
parent,
|
||||
subagentProvider: 'selected',
|
||||
})
|
||||
expect((await overridden.result).value).toBe('selected reply')
|
||||
await overridden.dispose()
|
||||
expect(selected.runs).toHaveLength(1)
|
||||
expect(provider.runs).toHaveLength(0)
|
||||
|
||||
const ordinary = await run(ctx, parent, scripted("return await agent('use the default')"))
|
||||
expect(ordinary.value).toBe('stub reply')
|
||||
expect(provider.runs).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('rejects invalid start-request provider routes before publishing a run', async () => {
|
||||
const { ctx, parent } = await setup()
|
||||
let starts = 0
|
||||
ctx.on('workflow/start', () => { starts += 1 })
|
||||
const messages: string[] = []
|
||||
for (const subagentProvider of ['', 'missing']) {
|
||||
let run: WorkflowRun | undefined
|
||||
let thrown: unknown
|
||||
try {
|
||||
run = ctx.workflows.start({
|
||||
...scripted("return 'must not start'"),
|
||||
parent,
|
||||
subagentProvider,
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
thrown = error
|
||||
}
|
||||
await run?.dispose()
|
||||
messages.push(thrown instanceof Error ? thrown.message : '')
|
||||
}
|
||||
|
||||
expect(messages).toEqual([
|
||||
'workflow subagentProvider must be a non-empty normalized string',
|
||||
'no subagent provider registered for "missing"',
|
||||
])
|
||||
expect(starts).toBe(0)
|
||||
})
|
||||
|
||||
it('rejects invalid per-run total-agent caps before publishing a run', async () => {
|
||||
const { ctx, parent } = await setup({ config: { maxTotalAgents: 2 } })
|
||||
let starts = 0
|
||||
ctx.on('workflow/start', () => { starts += 1 })
|
||||
const errors: unknown[] = []
|
||||
for (const maxTotalAgents of [0, 1.5, Number.NaN, 3]) {
|
||||
try {
|
||||
const handle = ctx.workflows.start({
|
||||
...scripted("return 'must not start'"),
|
||||
parent,
|
||||
maxTotalAgents,
|
||||
})
|
||||
await handle.dispose()
|
||||
} catch (error: unknown) {
|
||||
errors.push(error)
|
||||
}
|
||||
}
|
||||
|
||||
expect(errors.slice(0, 3)).toEqual(Array(3).fill(expect.objectContaining({
|
||||
code: 'INVALID_ARGUMENT',
|
||||
message: 'workflow maxTotalAgents must be a positive safe integer',
|
||||
})))
|
||||
expect(errors[3]).toMatchObject({
|
||||
code: 'INVALID_ARGUMENT',
|
||||
message: 'workflow maxTotalAgents 3 exceeds the engine ceiling 2',
|
||||
})
|
||||
expect(starts).toBe(0)
|
||||
})
|
||||
|
||||
it('enforces a per-run total-agent cap below the engine ceiling', async () => {
|
||||
const { ctx, parent } = await setup({ config: { maxTotalAgents: 2 } })
|
||||
const handle = ctx.workflows.start({
|
||||
...scripted("await agent('first'); await agent('second'); return 'unreachable'"),
|
||||
parent,
|
||||
maxTotalAgents: 1,
|
||||
})
|
||||
const result = await handle.result
|
||||
expect(result.stopReason).toBe('error')
|
||||
expect(result.agentsStarted).toBe(1)
|
||||
expect(result.error).toContain('total agent cap (1)')
|
||||
await handle.dispose()
|
||||
})
|
||||
|
||||
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' })])"))
|
||||
@@ -239,11 +330,18 @@ describe('dsh-workflow-workerthread', () => {
|
||||
expect(result.error).toContain('"isolation" is deferred')
|
||||
})
|
||||
|
||||
it('a provider start failure crosses back as a fatal AGENT_START error', async () => {
|
||||
it('rejects an unregistered configured provider before publishing a run', async () => {
|
||||
const { ctx, parent } = await setup({ config: { provider: 'nonexistent' } })
|
||||
const result = await run(ctx, parent, scripted("return await pipeline([1], () => agent('p'))"))
|
||||
expect(result.stopReason).toBe('error')
|
||||
expect(result.error).toContain('agent() could not start a child')
|
||||
let thrown: unknown
|
||||
try {
|
||||
ctx.workflows.start({ ...scripted("return 'must not start'"), parent })
|
||||
} catch (error: unknown) {
|
||||
thrown = error
|
||||
}
|
||||
expect(thrown).toMatchObject({
|
||||
code: 'AGENT_START',
|
||||
message: 'no subagent provider registered for "nonexistent"',
|
||||
})
|
||||
})
|
||||
|
||||
it('waits for async provider start before announcing a result that settled early', async () => {
|
||||
|
||||
Reference in New Issue
Block a user