[AAASM-3505] 🐛 (node-sdk): Lazy-load grpc in op-control so SDK import stays grpc-free#173
Conversation
`op-control.ts` is re-exported from the package entrypoint (`OpControlSubscriber`, AAASM-3500 / PR #172). It imported the `@grpc/grpc-js` `credentials` value and the `PolicyServiceClient` value (from `./proto/generated/policy.js`, which also imports grpc-js) at module top level. So `import '@agent-assembly/sdk'` eagerly loaded grpc-js at module-load time, breaking consumers where grpc-js isn't resolvable from this module's location (a `file:`-linked SDK under pnpm — the agent-assembly integration-tests node fixture): `ERR_MODULE_NOT_FOUND: Cannot find package '@grpc/grpc-js'`. That took down 4 agent-assembly `e2e_sdk_node` E2E tests. Make the grpc-dependent runtime values lazy: - grpc-js becomes a type-only import; the `credentials` namespace and `PolicyServiceClient` are `await import`ed inside a new private `openRealChannel`, run only on the real-connect path. - `OpControlSignal` enum (a runtime value from the grpc-importing policy module) is replaced by inlined numeric wire constants; the enum type is kept type-only. - `resolveOpControlCredentials` takes the grpc `credentials` namespace as an injected param so it stays synchronous and grpc-free. - `connect()` stays synchronous and public-API-unchanged; the real channel opens asynchronously and `close()` is safe before it lands. The `clientFactory` test seam never touches grpc. Refs AAASM-3505. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a packaging regression test for AAASM-3505. Two assertions:
- The built `dist/esm/op-control.js` has no top-level static value
import of `@grpc/grpc-js`, and references it only via `await
import(...)`; the CJS build has no top-level `require("@grpc/grpc-js")`.
- A fresh Node process requires the built CJS `op-control.js`,
constructs a subscriber on the `clientFactory` test-seam path, and
asserts `@grpc/grpc-js` is absent from `require.cache` — proving the
entrypoint stays grpc-free until a real channel is opened.
Mirrors the existing `langchain-lazy-load` packaging guard.
Refs AAASM-3505.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
🔎 Review (Claude Code) — ready for approval (CI failures are acceptance-only)CI: ✅ Functionally green — 14/16 pass. The 2 failures are acceptance-class only and ignorable per policy:
Functional checks (vitest 322 passed, typecheck, lint, ESM+CJS build, CodeQL) are all green. Scope vs AAASM-3505: ✅ Fixes the regression at the root. Verdict: Ready to approve & merge. This un-breaks the agent-assembly |
Exercises the branch the clientFactory seam bypasses (and the lines the lazy-grpc fix added) by mocking only the dynamically-imported PolicyServiceClient while using the real grpc-js credentials: - lazy client build after connect() returns, - loopback target → insecure creds / non-loopback → TLS, reaching the client, - subscription opened with the agent identity triple, - signals on the real stream drive waitForOp + close() propagation, - close()-before-async-open race guard prevents client construction, - channel-open failure surfaces as a dead stream (fail-safe), not an unhandled rejection. Takes op-control.ts to 100% line coverage; closes the codecov/patch gap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
✅ Coverage follow-up — CI now fully green (16/16)Added
|



Target
Task summary:
Fix a regression from AAASM-3500 / PR [AAASM-3500] ✨ (node-sdk): Wire OpControlSubscriber into withAssembly tool path #172:
src/op-control.tsis re-exported from the package entrypoint (OpControlSubscriber), and it imported@grpc/grpc-js(thecredentialsruntime value) andPolicyServiceClient(from./proto/generated/policy.js, which also imports@grpc/grpc-js) at module top level. Soimport '@agent-assembly/sdk'eagerly loaded grpc-js at module-load time. In consumers where grpc-js isn't resolvable fromop-control.js's location — the agent-assembly integration-tests node fixture (afile:-linked SDK under pnpm) — the SDK module failed to load:This took down 4 previously-green agent-assembly E2E tests (
aa-integration-tests::e2e_sdk_node::real_lang{chain,graph}_*), turning the monorepoTestCI red. This PR makes the grpc-dependent imports lazy so merely importing/exportingOpControlSubscriberno longer pulls grpc; grpc loads only when a subscriber opens a real channel.Task tickets:
e2e_sdk_nodeCI breakage and unblocks AAASM-3497 / agent-assembly PR #1179.Key point change:
@grpc/grpc-jsis now a type-only import at module scope. The runtimecredentialsnamespace and thePolicyServiceClientvalue are loaded viaawait import(...)inside a new privateopenRealChannel, which runs only on the real-connect path (noclientFactory).OpControlSignalenum was a runtime value imported from the grpc-importing./proto/generated/policy.js; importing it as a value would have defeated the lazy-load. It's replaced by inlined numeric wire constants (PAUSE=1,RESUME=2,TERMINATE=3); the enum type is kept (type-only) for signatures.resolveOpControlCredentialsnow takes the grpccredentialsnamespace as an injected parameter, so it stays synchronous, pure, and grpc-free; the real-connect path passes the lazily-imported namespace.OpControlSubscriber.connect()stays synchronous and the public API is unchanged. On the real path the channel opens asynchronously;close()is safe to call before the async open lands.clientFactorytest seam never triggers the dynamic import — the mock path is fully grpc-free.Effecting Scope
No public API change; ESM + CJS both still build.
@grpc/grpc-jsstays a regulardependency.Description
src/op-control.ts: grpc-js → type-only import; lazyawait import('@grpc/grpc-js')+ lazyawait import('./proto/generated/policy.js')forPolicyServiceClient, both insideopenRealChannel; inlinedOpControlSignalnumeric constants; injectedcredentialsnamespace intoresolveOpControlCredentials;clientnullable +closedguard for the async open.tests/op-control.test.ts: pass the grpccredentialsnamespace toresolveOpControlCredentials(the one signature change); all existing pause/resume/terminate/op_id behaviors unchanged and green.tests/packaging/op-control-grpc-lazy-load.test.ts(new regression guard): asserts the builtdist/esm/op-control.jshas no top-level static grpc value import (onlyawait import), the CJS build has no top-levelrequire('@grpc/grpc-js'), and a fresh Node process requiring the built CJSop-control.js(and constructing a subscriber on the test-seam path) leaves@grpc/grpc-jsabsent fromrequire.cache.How to verify
pnpm test(322 passed / 2 skipped),pnpm typecheck,pnpm lint,pnpm build(ESM + CJS) — all green.Closes AAASM-3505.
🤖 Generated with Claude Code