Navigation: packages/AGENTS.md → node-sdk
Read packages/AGENTS.md first — the Node Authoring — Bug Patterns to Avoid section there is the contract every node must satisfy (output slots, media refs, bounds, defaults). This overlay covers the
BaseNodeinternals that enforce it.
The framework injects internals into a node before process() runs — resolved
_secrets (API keys), _control_context, __node_id, __node_name. These must
never mix with user-supplied dynamic inputs:
- Route every reserved
_-prefixed key to the private_internalPropsmap (inassign/setDynamic/getDynamic), notdynamicProps. If they land indynamicPropsthey leak three ways:serialize()persists API keys into graph state;Object.fromEntries(this.dynamicProps)pulls secrets into prompt-template variables; and per-node arg builders forward them to provider requests. - Gate on the
_prefix, not a hardcoded skip-list. The old guard (["__node_id", "__node_name", "_secrets"]) omitted_control_contextand needed a per-packageSKIP_KEYSworkaround. Prefix-gating covers any new internal automatically. - Regression-test the boundary: assert
serialize()excludes internals and that prompt-variable iteration (dynamicProps) doesn't see_secrets/_control_context.
These are defined here (in BaseNode/the registry) and enforced for every node —
see packages/AGENTS.md § Output contract:
- Every key returned from
process()/genProcess()must be a declaredmetadataOutputTypesslot, or it's unreachable in the editor. - A
chunk(streaming) output requires anasync *genProcessgenerator withoutputCorrelationset toiteration(chunks) /single(aggregate). yieldstructured results so the kernel routes them to dynamic output handles; a valuereturned from a generator is discarded byyield*.
This package's behavioural core (validation, registry, metadata bridge, pack
trust model) is gated by mutation testing — npm run test:mutation breaks
below 80%. When you change BaseNode, the registry, validation, or the metadata
loaders, add a test that pins the exact new behaviour, then re-run the gate. Two
config settings are load-bearing and explained in
MUTATION_TESTING.md: inPlace: true (the source aliases
in vitest.config.ts break Stryker's sandbox) and ignoreStatic: true (the
top-level await in metadata.ts makes module-load constants slow static
mutants). src/docs/** and src/python-package-scan.ts are excluded — see the doc.