docs: rebalance prose cleanup and add trimming skill

This commit is contained in:
Tianyi Cui
2026-07-13 23:27:00 +08:00
parent fcdc318dda
commit 148046b9c8
392 changed files with 2801 additions and 1754 deletions

View File

@@ -1,6 +1,8 @@
/**
* Host side of one workflow run. Owns the worker, child RPC, first-outcome
* settlement, cancellation grace, lifecycle pairing, and quiescent cleanup.
* Host side of one workflow run. The first worker result, unexpected death, or
* cancellation-grace expiry owns settlement and closes message admission.
* Pending starts share one abort signal; published children share idempotent
* cleanup, and quiescence waits for both while synthesizing any missing end events.
* @module @deepseek-ai/dsh-workflow-workerthread/host
*/

View File

@@ -1,5 +1,8 @@
/**
* The `node:worker_threads` workflow engine: the {@link WorkflowService} implementation.
* Worker-thread workflow engine. Each run executes its model-written script in
* an escapable vm context on a fresh worker and bridges `agent()` calls to host
* subagents. The thread prevents synchronous script work from blocking the host
* and permits forced termination, but it is containment rather than a security boundary.
* @module @deepseek-ai/dsh-workflow-workerthread
*/

View File

@@ -1,6 +1,8 @@
/**
* Meta validation: check the caller-provided {@link WorkflowMeta} DATA against the shape
* contract and reject everything else loud, every violation named.
* contract and reject everything else loud, every violation named. Meta arrives as schema-checked
* JSON data, never evaluated script text; evaluating it on the host could run getters outside the
* worker timeout that exists to isolate model-written code.
* @module @deepseek-ai/dsh-workflow-workerthread/meta
*/

View File

@@ -1,7 +1,9 @@
/**
* The host⇄worker wire protocol: one string-valued enum of message tags per direction, a
* payload map giving each tag its parameters (the single source of truth), and the message
* unions derived from them.
* unions derived from them. Payloads are plain JSON by construction for structured clone. Both
* directions are closed engine protocols whose receivers use `assertNever`; generic typed senders
* make tag/payload mismatches compile-time errors rather than silently skipped messages.
* @module @deepseek-ai/dsh-workflow-workerthread/protocol
*/

View File

@@ -1,6 +1,10 @@
/**
* The engine's value boundary: copy script-realm values into plain JSON data — loud about
* everything JSON cannot carry — and render thrown script values to failure text.
* Materializes values leaving the script vm into plain JSON before they cross the worker
* boundary, and renders thrown script values without rejecting the run. The walk rejects
* lossy JSON shapes but trusts model-written workflow scripts: getters and proxy traps may
* run, and the vm is not a security boundary. The worker provides host-loop isolation and
* forced termination, not hostile-value containment. See
* docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md for the isolation rationale.
* @module @deepseek-ai/dsh-workflow-workerthread/realm
*/
@@ -48,11 +52,16 @@ function hasPlainPrototype(value: object): boolean {
}
/**
* Copy `value` (typically from the vm realm) into plain host JSON data.
* Copy `value` (typically from the vm realm) into plain host JSON data. Root `undefined` is
* returned unchanged; nested `undefined` and values JSON cannot represent losslessly fail
* with the offending path. Property accessors run normally, and a throwing read is wrapped
* with its rendered failure.
*
* @param value - the realm value to materialize.
* @param root - the path label for the root value (error messages).
* @returns the host-realm copy (plain objects/arrays/scalars only).
* @throws {@link MaterializeError} for unsupported values, cycles, sparse arrays, exotic
* prototypes, or property reads that throw.
*/
export function materializeFromRealm(value: unknown, root = 'value'): unknown {
if (value === undefined) return undefined

View File

@@ -1,7 +1,14 @@
/**
* Worker-side workflow runtime: vm hooks, child RPC, limits, value
* materialization, cancellation, and result shaping. Host termination enforces
* the cancellation deadline.
* Per-run worker-side vm hooks, child RPC, concurrency/caps, cancellation, and result shaping; it
* never touches Cordis. Script values leaving the realm are materialized as plain JSON before
* messaging. Values entering the trusted model-written realm are passed directly; `args` alone is
* cloned so script mutation cannot alter initialization data. See `./realm.ts` for the trust model.
*
* Fatal workflow errors—bad hook arguments, unsupported schemas/options, caps, start failures, and
* cancellation—propagate through combinators. Only child failures and ordinary stage errors become
* per-item nulls. Every returned promise has a rejection consumer so dropped script promises cannot
* kill the worker. A cancelled script that never settles emits nothing; the host force-settles the
* run within grace and terminates the thread.
* @module @deepseek-ai/dsh-workflow-workerthread/runtime
*/

View File

@@ -1,7 +1,13 @@
/**
* The worker-side half of the engine: {@link runWorkerSession} wires one MessagePort to one
* {@link WorkflowExecution} — hook progress and child starts go out as messages, run control
* and child lifecycle come back in — and posts the run's terminal result exactly once.
* and child lifecycle come back in — and posts the run's terminal result exactly once. Keeping it
* separate from `worker.ts` lets unit tests drive the session over a MessageChannel, because main
* process coverage cannot observe code inside a real Worker.
*
* The session announces ready and waits for `go`, so cancellation racing startup can prevent even
* the script's synchronous prefix. A cancel in place of `go` releases the gate into a cancelled
* drive without executing the body.
* @module @deepseek-ai/dsh-workflow-workerthread/session
*/
@@ -127,8 +133,9 @@ export function requireParentPort(port: MessagePort | null): MessagePort {
/**
* Run one workflow script to settlement against `port`, posting the terminal result message
* exactly once; resolves after that post (stray children may still be winding down through the
* port — the host owns their teardown and ultimately terminates the thread).
*
* port — the host owns their teardown and ultimately terminates the thread). It never rejects:
* constructor failure becomes an error result. Host pre-parse makes syntax failure here a likely
* Node-version skew, but the session still reports it instead of dying silently.
* @param port - the channel to the host (the real `parentPort`, or one side
* of an in-process `MessageChannel` in tests).
* @param init - the run payload the host provided as `workerData`.

View File

@@ -1,6 +1,7 @@
/**
* Non-protocol wire vocabulary for the worker-thread engine: the `workerData` init payload and
* the child-port interfaces the worker-side runtime consumes.
* the child-port interfaces the worker-side runtime consumes. Host/worker messages are defined in
* `./protocol.ts`; transported child requests and results are plain JSON for structured clone.
* @module @deepseek-ai/dsh-workflow-workerthread/types
*/

View File

@@ -1,5 +1,7 @@
/**
* The worker-thread entry the engine spawns: bootstrap ./session.ts on the real `parentPort`.
* Single-statement worker entry that boots `runWorkerSession` on real `parentPort`. Logic remains in
* the session module for in-process MessageChannel coverage; importing this entry on the main thread
* exercises `requireParentPort`'s failure path.
* @module @deepseek-ai/dsh-workflow-workerthread/worker
*/

View File

@@ -437,10 +437,7 @@ describe('runWorkerSession over an in-process MessageChannel', () => {
void runWorkerSession(host.port, init("return await agent('p')"))
await vi.waitFor(() => { expect(host.ofType(WorkerToHostType.ChildStart).length).toBe(1) })
const callId = host.ofType(WorkerToHostType.ChildStart)[0]!.callId
// Cancel FIRST, then the (stale) started reply: the worker processes them
// in order, so the agent() continuation resumes already-cancelled — the
// window the real host cannot produce (it refuses starts once cancelled)
// but a teardown race can.
// Simulate a teardown race by delivering cancellation before a stale start reply.
host.send({ type: HostToWorkerType.Cancel, reason: 'raced the start' })
host.send({ type: HostToWorkerType.ChildStarted, callId, childId: 'child-0' })
const result = await host.result()
@@ -448,7 +445,7 @@ describe('runWorkerSession over an in-process MessageChannel', () => {
await vi.waitFor(() => {
expect(host.ofType(WorkerToHostType.ChildDispose).map(m => m.callId)).toContain(callId)
})
// The child never became an agent-start: it was wound down pre-lifecycle.
// The unpublished child is disposed without a lifecycle announcement.
expect(host.ofType(WorkerToHostType.AgentStart)).toEqual([])
host.close()
})

View File

@@ -19,7 +19,11 @@ function fakeParent(): Agent {
// Allow cold worker startup on contended CI runners.
vi.setConfig({ testTimeout: 30_000 })
/** Retry an assertion until it passes or the timeout elapses. */
/**
* Wait up to 10 seconds for CPU-bound worker startup or cross-thread delivery on contended CI.
* Host reactions after an observed event use explicit tight overrides, so this generous startup
* allowance cannot hide multi-second reap regressions.
*/
function waitFor(assertion: () => void, timeout = 10_000): Promise<void> {
return vi.waitFor(assertion, { timeout, interval: 50 })
}