workflow, subagent: fix Codex code-review round-1 blockers
Six verified A-findings from the code-stage review, each with a regression test: - parallel()/pipeline() resolved to HOST arrays inside the vm realm, exposing host Array.prototype to scripts; combinator results are now realm-built (in-realm Array.from bound at context setup). - materializeFromRealm ran proxy traps (ownKeys/getOwnPropertyDescriptor/ getPrototypeOf) during the descriptor walk — realm code on the host stack, outside the vm timeout, escaping as raw errors; proxies (root, nested, and in the prototype position) are now rejected trap-free via util.types.isProxy before any inspection. - an already-aborted signal or an immediate cancel() no longer reports 'completed' for a hook-free script: drive() checks cancellation before running the body and again when the script settles. - dispose() now waits (bounded by disposeGraceMs) for stray agent() children to FINISH disposing, not just for the script to settle: every agent() call is tracked and quiesce() drains the in-flight set. - workflow/* event payloads were live mutable aliases shared across emissions; emitWorkflowEvent now hands each listener its own structural clone. - the structured-output turn-continuation veto is now prepend: true, so an earlier-registered force-continue listener cannot short-circuit it. Docs updated in the same change (READMEs, core-data-structures/workflow.md, the dynamic-workflows RFC, regenerated cordis catalogs).
This commit is contained in:
@@ -66,6 +66,28 @@ describe('dsh-workflow (interface)', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('gives each listener its OWN payload snapshot: mutation corrupts neither peers nor the caller', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(StubEngine)
|
||||
const seen: string[] = []
|
||||
ctx.on('workflow/agent-start', (info, agent) => {
|
||||
agent.label = 'HACKED'
|
||||
info.meta.name = 'HACKED'
|
||||
seen.push('mutator')
|
||||
})
|
||||
ctx.on('workflow/agent-start', (info, agent) => {
|
||||
seen.push(`${info.meta.name}/${agent.label}`)
|
||||
})
|
||||
const engine = ctx.workflows as StubEngine
|
||||
const info: WorkflowRunInfo = { id: WorkflowRunId('run-2'), meta: { name: 'w', description: 'd' } }
|
||||
const payload = { seq: 1, label: 'original', childId: 'c' }
|
||||
engine.emit('workflow/agent-start', info, payload)
|
||||
expect(seen).toEqual(['mutator', 'w/original'])
|
||||
// The caller's own objects are pristine too — no listener ever saw them.
|
||||
expect(info.meta.name).toBe('w')
|
||||
expect(payload.label).toBe('original')
|
||||
})
|
||||
|
||||
it('contains a throwing listener PER LISTENER: later listeners still run, nothing propagates', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(StubEngine)
|
||||
|
||||
Reference in New Issue
Block a user