deepseek-harness/packages/workflow/workflow-workerthread/tests/integration.spec.ts

90 lines
4 KiB
TypeScript
Raw Normal View History

workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
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
2026-07-18 12:21:15 +08:00
import { SessionId } from '@deepseek-ai/dsh-session'
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
import * as Invariants from '@deepseek-ai/dsh-invariants'
import SubagentService from '@deepseek-ai/dsh-subagent'
import * as spawn from '@deepseek-ai/dsh-subagent-spawn'
import { STRUCTURED_OUTPUT_TOOL } from '@deepseek-ai/dsh-subagent-inprocess'
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
workflow: swap the engine's internals to node:worker_threads In-place port of dsh-workflow-vm from the in-process node:vm execution to one worker thread per run (the workflow-workerthread engine of PR #215, adopted as THE engine): the script's vm context moves inside the worker, agent() bridges to ctx.subagents over the message port (host.ts/protocol.ts/session.ts/worker.ts are new; runtime.ts loses the abandon channel — the host's grace timer force-settles and TERMINATES instead), start() pre-parses the body host-side to keep the seam's synchronous SCRIPT_PARSE throw, and a ready→go handshake keeps a run cancelled before start from ever executing the body. start() no longer blocks the host, termination is real, and the value boundary is serialization by construction. The package keeps its name until the follow-up rename commit; scripts see the identical hook surface, and the seam-contract tests hardened ahead of this swap pass unchanged. The run and child-RPC surfaces are class-shaped rather than literal bundles: WorkerRun IMPLEMENTS the seam's WorkflowRun (id/meta are its own clone, separate from event payloads') and start() returns the instance directly — interface parity with the seam is compiler-checked; worker-side, ChildRpcBridge (implements ChildPort; callId allocation + pending book-keeping settled by onChild* entry points) and RpcChildHandle (every member an RPC keyed by its callId) carry names in stacks. ChildPort's method is startAgent — it names what it starts, matching the script-side agent() hook and the agentsStarted / workflow/agent-* vocabulary; the Child* type names deliberately stay (the worker side is cordis- and subagent-free; these are reduced JSON projections, not the seam's types). Review findings from the reference PR are folded in rather than re-introduced: - cancel() drives BOTH child-cancel channels host-side: the request signal aborts AND each registered child's explicit cancel() is called — a worker wedged in a synchronous spin cannot relay its own ChildCancel RPCs (regression: cancel-only provider + wedged worker). - All host warn paths render through the total renderThrown; a child dispose() rejecting a value whose coercion throws still acks ChildDisposed instead of wedging the script's finally (regression). - built-worker.e2e.ts is wired into builtBinSmokeGate and the AGENTS.md CI sequence — the built lib/worker.js resolution contract now runs in an automated gate. - workflow/end payload pinned on the worker-death path (with the cancelled and grace-force-settle pins riding the ported spec). - Real-Worker scripted timing budgets widened (50-300ms → 150-1000ms) for starved CI hosts. Workspace plumbing: the "./worker" subpath export sanctions the second runtime bundle (check-workspace-constraints), tsdown builds two single-entry passes, tsx becomes a devDependency for the unbuilt worker spawn.
2026-07-09 18:39:31 +08:00
import WorkerWorkflowEngine from '../src/index.ts'
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
type Script = ConstructorParameters<typeof MockAdapter>[0]
/**
workflow: swap the engine's internals to node:worker_threads In-place port of dsh-workflow-vm from the in-process node:vm execution to one worker thread per run (the workflow-workerthread engine of PR #215, adopted as THE engine): the script's vm context moves inside the worker, agent() bridges to ctx.subagents over the message port (host.ts/protocol.ts/session.ts/worker.ts are new; runtime.ts loses the abandon channel — the host's grace timer force-settles and TERMINATES instead), start() pre-parses the body host-side to keep the seam's synchronous SCRIPT_PARSE throw, and a ready→go handshake keeps a run cancelled before start from ever executing the body. start() no longer blocks the host, termination is real, and the value boundary is serialization by construction. The package keeps its name until the follow-up rename commit; scripts see the identical hook surface, and the seam-contract tests hardened ahead of this swap pass unchanged. The run and child-RPC surfaces are class-shaped rather than literal bundles: WorkerRun IMPLEMENTS the seam's WorkflowRun (id/meta are its own clone, separate from event payloads') and start() returns the instance directly — interface parity with the seam is compiler-checked; worker-side, ChildRpcBridge (implements ChildPort; callId allocation + pending book-keeping settled by onChild* entry points) and RpcChildHandle (every member an RPC keyed by its callId) carry names in stacks. ChildPort's method is startAgent — it names what it starts, matching the script-side agent() hook and the agentsStarted / workflow/agent-* vocabulary; the Child* type names deliberately stay (the worker side is cordis- and subagent-free; these are reduced JSON projections, not the seam's types). Review findings from the reference PR are folded in rather than re-introduced: - cancel() drives BOTH child-cancel channels host-side: the request signal aborts AND each registered child's explicit cancel() is called — a worker wedged in a synchronous spin cannot relay its own ChildCancel RPCs (regression: cancel-only provider + wedged worker). - All host warn paths render through the total renderThrown; a child dispose() rejecting a value whose coercion throws still acks ChildDisposed instead of wedging the script's finally (regression). - built-worker.e2e.ts is wired into builtBinSmokeGate and the AGENTS.md CI sequence — the built lib/worker.js resolution contract now runs in an automated gate. - workflow/end payload pinned on the worker-death path (with the cancelled and grace-force-settle pins riding the ported spec). - Real-Worker scripted timing budgets widened (50-300ms → 150-1000ms) for starved CI hosts. Workspace plumbing: the "./worker" subpath export sanctions the second runtime bundle (check-workspace-constraints), tsdown builds two single-entry passes, tsx becomes a devDependency for the unbuilt worker spawn.
2026-07-09 18:39:31 +08:00
* The whole in-process stack, keyless, with the script in a REAL worker
* thread: the engine drives the REAL spawn backend (with its
* structured runtime) on a real agent loop; the scripted mock MODEL is the
* only mocked boundary. This is the guard the unit suites structurally
* cannot give — the MessageChannel suite fakes the host, and the host suite
* stubs the subagent seam.
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
*/
async function setup(script: Script) {
const ctx = new Context()
const adapter = new MockAdapter(script)
await mountAgentLoopTestDependencies(ctx)
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
await ctx.plugin(Invariants)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
await ctx.plugin(spawn, { providerName: 'spawn' })
workflow: swap the engine's internals to node:worker_threads In-place port of dsh-workflow-vm from the in-process node:vm execution to one worker thread per run (the workflow-workerthread engine of PR #215, adopted as THE engine): the script's vm context moves inside the worker, agent() bridges to ctx.subagents over the message port (host.ts/protocol.ts/session.ts/worker.ts are new; runtime.ts loses the abandon channel — the host's grace timer force-settles and TERMINATES instead), start() pre-parses the body host-side to keep the seam's synchronous SCRIPT_PARSE throw, and a ready→go handshake keeps a run cancelled before start from ever executing the body. start() no longer blocks the host, termination is real, and the value boundary is serialization by construction. The package keeps its name until the follow-up rename commit; scripts see the identical hook surface, and the seam-contract tests hardened ahead of this swap pass unchanged. The run and child-RPC surfaces are class-shaped rather than literal bundles: WorkerRun IMPLEMENTS the seam's WorkflowRun (id/meta are its own clone, separate from event payloads') and start() returns the instance directly — interface parity with the seam is compiler-checked; worker-side, ChildRpcBridge (implements ChildPort; callId allocation + pending book-keeping settled by onChild* entry points) and RpcChildHandle (every member an RPC keyed by its callId) carry names in stacks. ChildPort's method is startAgent — it names what it starts, matching the script-side agent() hook and the agentsStarted / workflow/agent-* vocabulary; the Child* type names deliberately stay (the worker side is cordis- and subagent-free; these are reduced JSON projections, not the seam's types). Review findings from the reference PR are folded in rather than re-introduced: - cancel() drives BOTH child-cancel channels host-side: the request signal aborts AND each registered child's explicit cancel() is called — a worker wedged in a synchronous spin cannot relay its own ChildCancel RPCs (regression: cancel-only provider + wedged worker). - All host warn paths render through the total renderThrown; a child dispose() rejecting a value whose coercion throws still acks ChildDisposed instead of wedging the script's finally (regression). - built-worker.e2e.ts is wired into builtBinSmokeGate and the AGENTS.md CI sequence — the built lib/worker.js resolution contract now runs in an automated gate. - workflow/end payload pinned on the worker-death path (with the cancelled and grace-force-settle pins riding the ported spec). - Real-Worker scripted timing budgets widened (50-300ms → 150-1000ms) for starved CI hosts. Workspace plumbing: the "./worker" subpath export sanctions the second runtime bundle (check-workspace-constraints), tsdown builds two single-entry passes, tsx becomes a devDependency for the unbuilt worker spawn.
2026-07-09 18:39:31 +08:00
await ctx.plugin(WorkerWorkflowEngine, {})
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
ctx.llm.registerAdapter(['mock'], adapter)
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
2026-07-18 12:21:15 +08:00
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
return { ctx, parent, adapter }
}
describe('dsh-workflow-workerthread over the real in-process stack', () => {
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
it('runs a two-stage workflow: a plain child, then a schema child through the structured runtime', async () => {
const { ctx, parent } = await setup([
textResponse('the file list is a.ts'),
toolCallResponse('c1', STRUCTURED_OUTPUT_TOOL, { verdict: 'real', confidence: 0.9 }),
])
const childIds: string[] = []
ctx.on('workflow/agent-start', (_info, agent) => {
// The workflow bridge must await asynchronous provider start: an observer
// sees the real spawn child already published, never a reserved id.
expect(ctx.agents.get(agent.childId)).toBeDefined()
childIds.push(agent.childId)
})
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
const run = ctx.workflows.start({
2026-07-09 20:09:10 +08:00
meta: { name: 'integration', description: 'plain + structured children' },
script: `phase('Read')
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
const prose = await agent('read the repo')
phase('Judge')
const judged = await agent('judge: ' + prose, {
schema: { type: 'object', properties: { verdict: { type: 'string', enum: ['real', 'bogus'] }, confidence: { type: 'number' } }, required: ['verdict'] },
})
return { prose, verdict: judged.verdict, confidence: judged.confidence }`,
parent,
})
const result = await run.result
expect(result.stopReason).toBe('completed')
expect(result.value).toEqual({ prose: 'the file list is a.ts', verdict: 'real', confidence: 0.9 })
expect(result.agentsStarted).toBe(2)
await run.dispose()
// 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(SessionId(childId))).toBeUndefined()
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
}
})
it('a child that fails against its schema (nudges exhausted) reaches the script as null', async () => {
const { ctx, parent } = await setup([
textResponse('prose only'),
textResponse('still prose after the nudge'),
])
const run = ctx.workflows.start({
2026-07-09 20:09:10 +08:00
meta: { name: 'null-path', description: 'schema failure maps to null' },
script: `const judged = await agent('judge it', { schema: { type: 'object', properties: { v: { type: 'string' } } } })
workflow: dynamic workflows — script-driven multi-agent orchestration A new capability family at packages/workflow/ in the bash seam shape, modeled on Claude Code's dynamic workflows: the model writes a JavaScript orchestration script (export const meta = {...} + plain-JS body), a runtime executes it, and the script — not the conversation — holds the loop, the branching, and the intermediate results. - dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary (WorkflowRun whose result NEVER rejects) + observe-only workflow/* events carrying data snapshots (id + meta, never the live run), per-listener contained like subagent/*. - dsh-workflow-vm: in-process node:vm engine. Meta extraction via a string/comment-aware scanner (template interpolation rejected; literal evaluated alone in an empty timed context; statement blanked line- preservingly so stacks keep script line numbers). Hooks: agent(prompt, {label, phase, schema, model}) over ctx.subagents, parallel(), pipeline() (no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline: hook misuse (unknown/deferred options, bad arguments, unsupported schemas, tripped caps, seam start failures, cancellation) throws fatal WorkflowErrors the combinators RE-THROW — never dissolved into the per-item null reserved for child failures. Realm boundary: inbound values materialized by descriptor walks that never invoke accessors (defineProperty copies, __proto__-safe); outbound values rebuilt in-realm via the context's own JSON.parse. Determinism bans (Date.now/Math.random/argless new Date) kept so future resume support cannot break scripts. Caps and timeouts are validated Config. Every hook promise carries a no-op rejection consumer (app-boot exits on unhandled rejections). - dsh-tool-workflow: the model-facing workflow tool, synchronous like dsh-tool-subagent (start → await → try/finally dispose; abort bridged; non-completed → isError). Generic render card titled by a textual meta.name sniff. The tool description carries the authoring contract. Wired into examples/{coding-agent,acp-agent} with explicit-ask-only guidance. Coverage at every tier: unit (meta scanner, materializer incl. counting-getter and __proto__ regressions, combinator semantics, concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon), integration over the real spawn stack, with-key e2e (real two-phase run + the tool through the registry pipeline), and a recorded ACP snapshot scenario (workflow-run, 1 child session). RFC: docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's layout line.
2026-07-05 13:29:35 +08:00
return { got: judged === null ? 'null' : 'value' }`,
parent,
})
const result = await run.result
expect(result.stopReason).toBe('completed')
expect(result.value).toEqual({ got: 'null' })
await run.dispose()
})
})