ts: MeshJob event injection parity — recvEvent/sendEvent/postEvent - #1043
Conversation
Closes #1042. TypeScript vertical for the consumer-pushed event primitive that shipped Python-first in #1041 (closes #1032). NAPI bindings (src/runtime/core/src/jobs_napi.rs): - JsJobController::recv_event(types, timeoutSecs) -> Promise<JobEvent|null> - JsJobProxy::send_event(eventType, payload) -> Promise<JobEventReceipt> - parse_timeout_secs helper guards NaN/Inf/negative; applied at both recv_event and existing wait() call sites - JobError::JobTerminal mapped to TS-visible named error TS SDK (src/runtime/typescript/src/): - recvEvent / sendEvent surfaced on MeshJob interface in types.ts - New mesh.jobs namespace in jobs.ts with postEvent(jobId, type, payload) convenience helper; LRU-bounded JobProxy cache (default 256, env override MCP_MESH_JOBPROXY_CACHE_MAX) mirrors Python implementation. JS Map insertion-order semantics make O(1) LRU straightforward — no extra bookkeeping needed. - JobNotFoundError / JobTerminalError typed error classes - JobEvent / JobEventReceipt type definitions - Exports wired in index.ts (mesh.jobs namespace + types + errors) Unit tests (src/runtime/typescript/src/__tests__/jobs.spec.ts): - +16 tests covering recv/send/postEvent, LRU eviction, typed- error mapping, NaN/Inf timeout guard, MCP_MESH_REGISTRY_URL discovery, cache keyed by (registryUrl, jobId) - Suite: 734 → 750 passing Integration tests (tests/integration/suites/uc22_meshjob_ts/): - tc24_event_injection_happy_path_ts — payload round-trip - tc25_event_type_filter_ts — registry-side WHERE filter - tc26_cancel_posts_synthetic_event_ts — cancel-grace verified in TypeScript path - Reuses long-task-provider-ts / long-task-consumer-ts fixtures (extended with 3 new tools each) - Suite: 20 baseline + 3 new = 23/23 pass API parity matrix (TS ↔ Python): recvEvent(types?, timeoutSecs?) ↔ recv_event(types, timeout_secs) sendEvent(eventType, payload) ↔ send_event(event_type, payload) mesh.jobs.postEvent(...) ↔ mesh.jobs.post_event(...) JobNotFoundError ↔ JobNotFoundError JobTerminalError ↔ JobTerminalError MCP_MESH_JOBPROXY_CACHE_MAX (256) ↔ MCP_MESH_JOBPROXY_CACHE_MAX (256) Cancel synthetic event grace is server-side (MCP_MESH_CANCEL_EVENT_GRACE_MS, default 200ms, cap 10s) — no client change needed for TypeScript parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
W1: add explicit `delete process.env.MCP_MESH_REGISTRY_URL` to a
nested beforeEach in describe("postEvent") so each test starts from
a known env-var state. The previous arrangement worked by accident
of test ordering — fragile to test insertion or reordering.
W2: update source-of-truth comments on both TS and Python sides to
point at the actual contract source (NAPI/pyo3 wrapper remap
functions in jobs_napi.rs::job_error_to_napi and
jobs_py.rs::job_error_to_py) rather than the misleading reference
to core's JobError::Display. The wrapper remap deliberately
overrides Display with a stable SDK-facing format ("job is
terminal: ...") — a future maintainer thinking the remap is
redundant could silently break the substring contract on both
runtimes. Cite the actual line numbers (85-102 napi, 66-81 pyo3)
plus the specific JobTerminal arm.
I4: rename `getOrCreateProxy` → `_getOrCreateProxy` to match the
existing `_clearProxyCache` underscore convention for test-only
exports. 14 reference sites updated (1 export, 1 internal call, 12
in jobs.spec.ts). index.ts does NOT re-export — no public API
impact. dist/ stale references will regenerate on next build.
Tests: 750 passed, unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR implements TypeScript SDK parity for job event injection by extending N-API bindings with recv_event/send_event methods, defining MeshJob interface extensions and mesh.jobs namespace, providing comprehensive unit and integration test coverage, and demonstrating real-world event-driven patterns in test fixtures. ChangesJob Event Injection for TypeScript SDK
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/runtime/core/src/jobs_napi.rs`:
- Around line 65-72: The parse_timeout_secs function currently uses
Duration::from_secs_f64 which can panic on very large finite inputs; change it
to use Duration::try_from_secs_f64 and convert the Result to napi::Result with
an appropriate Error::from_reason message (e.g., "timeoutSecs too large" plus
the value) so overflows return an Err instead of panicking. Also find the other
raw call to Duration::from_secs_f64(secs) in this file (the unprotected usage
noted) and replace it with Duration::try_from_secs_f64(secs).map_err(|e|
Error::from_reason(format!("timeoutSecs overflow: {}", e))) so both places
handle overflow safely and return napi errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4d858348-73e4-4978-a38e-6812c4e2ba21
📒 Files selected for processing (11)
src/runtime/core/src/jobs_napi.rssrc/runtime/python/mesh/jobs.pysrc/runtime/typescript/src/__tests__/jobs.spec.tssrc/runtime/typescript/src/index.tssrc/runtime/typescript/src/jobs.tssrc/runtime/typescript/src/types.tstests/integration/suites/uc22_meshjob_ts/fixtures/long-task-consumer-ts/src/index.tstests/integration/suites/uc22_meshjob_ts/fixtures/long-task-provider-ts/src/index.tstests/integration/suites/uc22_meshjob_ts/tc24_event_injection_happy_path_ts/test.yamltests/integration/suites/uc22_meshjob_ts/tc25_event_type_filter_ts/test.yamltests/integration/suites/uc22_meshjob_ts/tc26_cancel_posts_synthetic_event_ts/test.yaml
…rror Inline review on PR #1043 caught that parse_timeout_secs guarded NaN/Inf/negative but still called the panic-prone Duration::from_secs_f64. Very large finite inputs (e.g. f64::MAX seconds) would panic inside the napi async runtime rather than returning a clean JS Error rejection. Both raw call sites in jobs_napi.rs fixed: - parse_timeout_secs (line 65) -> try_from_secs_f64 + Error::from_reason "timeoutSecs out of range: {e} (got {s})" - with_job_async_napi deadline_secs path (line 659) -> same pattern, error message includes job_id for diagnosis Parity fix mirrored on jobs_py.rs: - parse_timeout_secs (line 45) -> try_from_secs_f64 + PyValueError - with_job_async_py (line 646) -> try_from_secs_f64 + inline NaN/Inf guard (the Python side lacked validate_deadline_secs equivalent) Tests: +3 in jobs_napi.rs covers (a) valid passthrough, (b) NaN/Inf/ negative rejection, (c) f64::MAX overflow rejection. 415 -> 418 Rust lib tests pass on the typescript feature. Python-side parity tests omitted because jobs_py.rs has no test mod (pyo3 extension- module feature deliberately omits Python symbols at static-link time); explanatory comment added pointing at the napi-side test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…1045) ## Summary Closes #1044. Java vertical for the consumer-pushed event-injection primitive shipped Python-first in #1041 and TypeScript in #1043. **Completes the cross-runtime fan-out for issue #1032.** Follows the original MeshJob shipping cadence: Python (#861/#878) → TypeScript (#885) → Java (#891). This PR is the Java step. ## What's in the PR ### FFI bindings (`src/runtime/core/src/jobs_ffi.rs`) - `mesh_job_controller_recv_event(handle, types_json, timeout_secs, out_event_json)` → `i32` (0 ok, -1 invalid, -2 NotFound, -3 backend) - `mesh_job_proxy_send_event(handle, event_type, payload_json, out_receipt_json)` → `i32` (0 ok, -1 invalid, -2 NotFound, -3 **Terminal**, -4 backend) — distinct codes for `JobNotFound` vs `JobTerminal` so the Java SDK can map to distinct typed exceptions - `parse_ffi_timeout_secs` helper: rejects NaN/Inf, uses `Duration::try_from_secs_f64` for overflow safety, negative-sentinel `< 0` convention for "no timeout" (since C ABI can't pass `Option<f64>`) - +6 Rust FFI tests (jobs_ffi: 16 → 22) ### Java SDK (`src/runtime/java/mcp-mesh-sdk/`) - `JobController.recvEvent(List<String> types, Duration timeout)` → `Map<String, Object>` or null - `JobProxy.sendEvent(String eventType, Map<String, Object> payload)` → receipt Map - New `MeshJobs.postEvent(jobId, eventType, payload)` static helper with LRU-bounded `JobProxy` cache (default 256, env override `MCP_MESH_JOBPROXY_CACHE_MAX`) - Uses `LinkedHashMap(accessOrder=true)` with explicit `synchronized` blocks — access-order mode mutates internal state on `get()` so thread-safety requires external sync - `removeEldestEntry` closes evicted proxies so FFI handles release promptly - `JobNotFoundException` + `JobTerminalException` typed exceptions extending the existing `MeshException` (which extends `RuntimeException` — unchecked, matches Python's `RuntimeError`-subclassing) - mcp-mesh-core `MeshCore` JNR-FFI interface gets 2 new method declarations ### Tests - **Rust FFI**: +6 unit tests - **Java unit**: +20 tests (`MeshJobsTest` 14, `JobEventApiTest` 6); sdk module 42 → 62; full Java module 124 → 125 - **Integration suite `uc23_meshjob_java`**: 3 new test cases mirroring Python tc24/25/26 and TS tc24/25/26_ts: - `tc24_event_injection_happy_path_java` — payload round-trip - `tc25_event_type_filter_java` — registry-side WHERE filter - `tc26_cancel_posts_synthetic_event_java` — cancel-grace verified in Java path - Reuses existing `long-task-provider-java` / `long-task-consumer-java` fixtures (extended with 3 new tools each) - Baseline 23 + new 3 = **26/26 pass** ### API parity matrix completed (3 runtimes) | API | Java | TypeScript | Python | |---|---|---|---| | Producer wait for event | `job.recvEvent(types, duration)` | `job.recvEvent(types, timeoutSecs)` | `job.recv_event(types, timeout_secs)` | | Consumer post to specific proxy | `proxy.sendEvent(type, payload)` | `proxy.sendEvent(type, payload)` | `proxy.send_event(type, payload)` | | Convenience helper | `MeshJobs.postEvent(...)` | `mesh.jobs.postEvent(...)` | `mesh.jobs.post_event(...)` | | Typed errors | `JobNotFoundException` / `JobTerminalException` | `JobNotFoundError` / `JobTerminalError` | `JobNotFoundError` / `JobTerminalError` | | Proxy cache cap | `MCP_MESH_JOBPROXY_CACHE_MAX` (256) | same | same | | Cancel synthetic event grace | Server-side `MCP_MESH_CANCEL_EVENT_GRACE_MS` (200ms default, 10s cap) | same | same | ## Review Notes Independent review returned **0 BLOCKERs, 0 WARNINGs, 4 INFOs.** All 4 addressed in commit `99da6a504`: - **I1**: `JobNotFoundException` javadoc had `JobError::Other(BackendError::NotFound)` — actual variant is `JobError::Backend(BackendError::NotFound)`. Typo fixed. - **I2**: Added a 7-line policy comment above `parse_ffi_timeout_secs` documenting why two timeout policies coexist (strict for new surfaces; permissive for back-compat on `mesh_job_proxy_wait`). - **I3**: Added a parallel 6-line lock-rationale comment on `JobController.recvEvent`'s synchronized block, modeled on `JobProxy.await()`'s existing doc — explains the use-after-free fence (concurrent `close()` must not free the FFI handle mid-poll). - **I4**: Added direct LRU eviction test `getOrCreateProxy_evictsLruAndClosesProxyAtCap` exercising the `removeEldestEntry → JobProxy.close() → mesh_job_proxy_free` path with the default cap (256 + 1 overflow). 257 FFI allocations in 24ms — acceptable cost for a regression guard on the resource-cleanup guarantee. ## What's still open for #1032 epic After this PR merges: - ✅ Python (#1041) - ✅ TypeScript (#1043) - ✅ Java (#1044, this PR) - ⏳ `Stream[T]` subscription mode (the paired feature originally cited in #1032 — depends on reading #645's current Stream[T] producer model) ## Test plan - [ ] CI green across Rust, Java, integration suite - [ ] Manual: cross-runtime sanity (Python `mesh.jobs.post_event(jobId, ...)` reaches Java `await job.recvEvent(["..."], ...)` — and vice versa) - [ ] Manual: a Java producer with `await job.recvEvent(["cancelled"])` exits gracefully when consumer (any runtime) calls `proxy.cancel("reason")` (200ms grace observed) Closes #1044 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit # Release Notes ## New Features - Added job event communication: send events via `sendEvent()` and receive events via `recvEvent()` with optional timeout support - Event type filtering to selectively consume specific event types while ignoring others - New `MeshJobs` utility class for posting events without maintaining proxy instances - New typed exceptions for better error handling: `JobNotFoundException` and `JobTerminalException` ## Tests - Added integration tests validating event injection, type filtering, and event-based cancellation workflows <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1045?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary - Adds **Stream[T] subscription mode** for MeshJob (Python vertical of #1032 follow-up): `mesh.jobs.subscribe_events(job_id, types=, after=, long_poll_secs=) -> AsyncIterator[dict]`. - Counterpart to the point-to-point `recv_event`/`send_event` surface (#1041). Subscription is non-destructive — multiple subscribers can observe the same job's event log independently, each with its own per-call cursor. - Three layers: Rust core `JobProxy::list_events` (thin pass-through to `backend.list_job_events`), pyo3 binding `PyJobProxy.list_events` with `parse_timeout_secs` guard, Python SDK `mesh.jobs.subscribe_events` async generator. - Integration test tc27 extends existing fixtures with `run_until_done` (producer) and `commission_subscribe_observer` (consumer) tools — same job is observed by both `recv_event` (consumer) and `subscribe_events` (third-party observer), proving cursor independence. ## Review Notes Independent review caught 1 blocker + 2 substantive warnings (all addressed in the amended commit): 1. **Public `long_poll_secs` surface**: now `Optional[float] = 30.0` (was `float = 30.0`). `None` reaches the binding's "single immediate read" path; previously unreachable from Python. Unit test added. 2. **Cursor advance + `seq` validation**: cursor now advances **before** yield, and `event["seq"]` is type-validated with a clear `RuntimeError` instead of a half-yielded `KeyError`. Unit test added. 3. **YAML assertion robustness**: replaced the whitespace-sensitive `"posted_seqs":[1,2,3]` substring with structured per-seq checks tolerant to encoder spacing changes. Other review WARNINGs (`async def`+`yield` annotation footgun in docstring, subscriber-vs-poster scheduling race in the fixture) and the 5 INFOs are deferred — none affect correctness. Closes #1046 Sibling verticals (TS, Java) of this same subscription mode will follow as separate PRs to keep the diffs reviewable, mirroring the #1041 → #1043 → #1045 cadence. ## Test plan - [x] Python unit tests pass (`test_meshjob_events.py`: 27 tests, +5 new in this PR — 2 from fixes, 3 from initial implementation) - [x] Rust unit tests pass (`jobs.rs`: +6 new for `JobProxy::list_events`) - [x] uc21_meshjob integration suite: 21/21 pass (20 baseline + 1 new tc27 in 193.8s) - [x] `parse_timeout_secs` guard reused from #1041 (NaN/Inf/negative reject; `try_from_secs_f64` overflow) - [x] LRU `JobProxy` cache reused from #1041 (no duplicate cache) - [x] Server-side `types` filter forwarded all the way to `backend.list_job_events` (no client-side post-filter) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Job event listing: Query events with optional type filtering and long-polling support for efficient batch retrieval without affecting internal state * Job event subscription: Subscribe to event streams with automatic cursor management, enabling asynchronous observation of job activity across multiple batches * **Tests** * Integration tests validating concurrent event publishing and concurrent subscription via observer pattern <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1047?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary - Adds **Stream subscription mode** for MeshJob (TypeScript vertical of #1032 follow-up): `mesh.jobs.subscribeEvents(jobId, { types, after, longPollSecs }) -> AsyncGenerator<JobEvent>`. - Mirrors the Python vertical (#1047). Counterpart to the point-to-point `recvEvent`/`postEvent` surface shipped in #1043. Multiple subscribers can observe the same job's events independently via per-call cursors. - Three layers: napi-rs binding `JsJobProxy::list_events` returning `ListEventsResult { events, next_after }` (napi-rs v3 doesn't auto-serialize bare tuples), TS SDK `subscribeEvents` async generator reusing the existing `_getOrCreateProxy` LRU cache, integration test tc27 in `uc22_meshjob_ts` extending the long-task fixtures with `run_until_done` (producer) and `commission_subscribe_observer` (consumer). - Side benefit: scrubs a stray null byte from `jobs.ts:187` (`${registryUrl}\x00${jobId}` → `${registryUrl} ${jobId}`) inherited from an earlier PR that made git classify the file as binary in diffs. This also explains the `Bin -> Bin` line in the file stat — `git diff -a` shows the actual textual diff. ## Review Notes Independent review caught 4 substantive WARNINGs (all addressed in the amended commit): 1. **`bigint` → `number` cursor truncation**: TSDoc accepted `bigint` for i64-sourced cursors, but `Number(after)` silently truncated values above `2^53`. Now guarded with an explicit `RangeError` when `after > BigInt(Number.MAX_SAFE_INTEGER)`. 2. **Missing "no `seq` key" malformed-payload test**: Python sibling had this test; TS now has parity (`rejects event without seq key`). 3. **Subscriber drain comment misleading**: `.catch(() => {})` only suppresses the unhandled rejection — it does NOT cancel the underlying long-poll (JS has no cancellation primitive without `AbortController` plumbed through napi). Comment rewritten to be honest. Plumbing `AbortController` through napi is a known limitation left out of scope. 4. **LRU-reuse test asserted caching only**: now strengthened to assert both iters observe `seq=1` independently — the actual headline contract. Other findings (3 INFOs) skipped as cosmetic or already-intentional patterns. Closes #1048 Sibling Java vertical of subscription mode will follow as a separate PR, mirroring the #1043 → #1045 cadence. ## Test plan - [x] TS unit tests pass (`jobs.spec.ts`: 23 tests, +7 new in this PR — 6 from initial implementation + 1 from review fix) - [x] napi binding rebuild + `src-tests` 12/12 pass - [x] uc22_meshjob_ts integration suite: 24/24 pass (23 baseline + 1 new tc27 in 54.4s) - [x] `parse_timeout_secs` guard reused at napi boundary (NaN/Inf/negative reject) - [x] LRU `JobProxy` cache reused from #1043 (no duplicate cache) - [x] Server-side `types` filter forwarded all the way to the registry (no client-side post-filter) - [x] `nextAfter` watermark advances cursor on empty pages (parity with Python #1047) - [x] Bool-`seq` and missing-`seq` rejection (parity with Python `RuntimeError` shape) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added `subscribeEvents()` function to the jobs namespace for real-time event subscription with optional event type filtering and long-polling support * Events support cursor-based pagination for reliable delivery across subscription sessions * **Tests** * Added comprehensive integration tests validating concurrent event subscription alongside event consumption patterns <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1049?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…1053) ## Summary Documentation-only PR covering the six commits shipped since v2.1.0 (MeshJob event-injection trilogy: #1041 / #1043 / #1045 for `recv_event` / `send_event` / `post_event`; #1047 / #1049 / #1051 for `subscribe_events`). - `docs/concepts/jobs.md` — adds two new sections after the existing v2.0 baseline material: **Event injection** (producer/consumer model + recv loop + post pattern + type filtering + synthetic cancel event with grace window + typed errors) and **Stream subscription** (per-call cursor + `next_after` watermark + no-automatic-terminal-detection + multiple-concurrent-subscribers semantics). Both sections carry cross-runtime Python/TS/Java tabbed examples respecting the three distinct iterator shapes (Python async generator, TS async generator, Java blocking `Closeable` iterator). - `docs/concepts/stateful-agents.md` — replaces the stale "Coming soon: mesh-managed event channel" block (which referenced #1032 as roadmap) with an accurate cross-link to the new sections. - `docs/concepts/index.md` — grid card teaser mentions event injection. - `docs/environment-variables.md` — documents `MCP_MESH_JOBPROXY_CACHE_MAX` (default 256) and `MCP_MESH_CANCEL_EVENT_GRACE_MS` (default 200, capped 10000). - `src/core/cli/man/content/` — same env-var content mirrored to `environment.md`; new **Event injection** + **Stream subscription** sections added to `jobs.md`, `jobs_typescript.md`, `jobs_java.md` (each variant carries idiomatic native-runtime code). - `RELEASE_NOTES.md` — `## v2.2.0 (UNRELEASED)` stub at the top, grouping the six commits by feature rather than PR number, mirroring the v2.1.0 entry's voice and structure. `meshctl man` content is **hand-maintained markdown**, not generated from `docs/` — both surfaces were updated to keep them in sync. ## Review Notes Independent review caught 3 substantive WARNINGs (all addressed) and surfaced one pre-existing adjacent inaccuracy (also folded in): 1. **Java `Map.of` foot-gun** — the cancel-event handling example used `Map.of("reason", payloadMap.get("reason"))`, which NPEs if the synthetic cancel event has no `reason` field (the API allows null reasons). Replaced with a null-tolerant pattern using `Objects.toString(payload.get("reason"), "")`. 2. **Misstated Java cancel mechanism** — `jobs_java.md` claimed handlers observe cancel via `InterruptedException`. Per `JobController.java:239-249`, Java's `Thread#sleep` cannot be interrupted by the Tokio cancel token firing — handlers must poll `isCancelled()` between work units OR park on `recvEvent(["cancelled", ...])`. Rewrote the section accurately. 3. **TS double-bang + `as any` cast** — the canonical TS doc example used `job!.recvEvent!(...)` and `(event.payload as any)?.reason`. Replaced with idiomatic early-return narrowing + `Record<string, unknown>` payload typing so the example doesn't teach non-idiomatic TS. 4. **Adjacent pre-existing inaccuracy** — `jobs_java.md:308-310` (outside the diff) carried the same wrong `Thread.sleep` interrupt claim in the Cancellation section. Folded a one-paragraph fix in since the same surface area was already being edited. All API signatures, env-var defaults (256, 200ms, 10000ms cap), tab indentation, sequence-diagram syntax, anchor links, and framing verified accurate against source files. Closes #1052 A follow-up PR (tracked separately) will add a runnable example agent under `examples/` demonstrating the same APIs. ## Test plan - [x] `mkdocs build` clean (no new errors/warnings introduced; pre-existing orphan-page warnings unrelated) - [x] `mkdocs serve` renders new sections cleanly (grid cards, tabbed code, mermaid `sequenceDiagram` blocks) - [x] `meshctl man environment --raw | grep -E 'MCP_MESH_(JOBPROXY|CANCEL_EVENT)'` matches both env vars - [x] `meshctl man jobs [--typescript|--java] --raw` matches new Event injection + Stream subscription sections per runtime - [x] `meshctl man jobs --java --raw | grep InterruptedException` returns only an unrelated `throws` clause in a code sample — the wrong cancel-mechanism claim is gone - [x] `meshctl man jobs --typescript --raw | grep -E '!\.|\bas any\b'` zero matches — non-idiomatic TS scrubbed - [x] `go build ./...` clean (man content compiles via `//go:embed`) - [x] No-deprecated-in-docs rule observed; public-artifact framing observed (no naming of downstream consumers, no behavioral framing) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * MeshJob event injection: post and receive job events across all supported runtimes with typed error handling and configurable cancel-event grace window. * MeshJob stream subscription: non-destructive event observation with independent per-call cursors and watermark filtering. * **Documentation** * Added comprehensive event injection and stream subscription guidance across Python, TypeScript, and Java documentation. * Documented new environment variables for event channel tuning. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1053?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary Release v2.2.0 — the MeshJob event-injection wave. Rolls up six feature PRs (cross-runtime point-to-point event injection + stream subscription) plus three docs/examples PRs into a stable release: - **MeshJob event injection** — `recv_event` / `send_event` / `post_event` across Python (#1041), TypeScript (#1043), Java (#1045). - **MeshJob stream subscription** — `subscribe_events` async generator (Python #1047, TS #1049) and `EventSubscription` Closeable iterator (Java #1051). - **Docs + examples** — concepts + env vars + meshctl man + release notes (#1053), signature-drift follow-up (#1056), runnable event-injection example agents across all three runtimes (#1058). See `RELEASE_NOTES.md` for the per-feature narrative grouping all six feature PRs. ## Mechanical changes in this PR - `scripts/bump_version.py 2.1.0 2.2.0` — 417 files updated across 36 categories (Cargo manifests, pyproject.toml, package.json, helm charts, scaffold templates, man content, test config, release workflow versions). - `helm dependency update helm/mcp-mesh-core` — Chart.lock regenerated. - `cargo generate-lockfile` (src/runtime/core) — Cargo.lock refreshed. - `RELEASE_NOTES.md` — `## v2.2.0 (UNRELEASED)` → `## v2.2.0 (2026-05-19)`, top-of-file Full Changelog link bumped to `v2.2.0...HEAD`, new `v2.1.0...v2.2.0` link added above the v2.2.0 heading. Net diff: 398 files changed, +613 / -611. Mostly one-line version bumps. Closes #1059 ## Test plan - [x] Dry-run `bump_version.py` matched expected scope (537 files / 36 categories) - [x] No stray `2.1.0` mcp-mesh-internal references left after bump (sanity grep — only transitive npm-dep matches remain) - [x] `helm dependency update` + `cargo generate-lockfile` reminders followed - [x] All upstream feature PRs (#1041 → #1058) merged and validated end-to-end before this release was cut - [ ] **After merge**: tag `v2.2.0` pushed to origin (HOLD for user inspection of merged main) - [ ] **After tag**: publish workflow fired (HOLD for explicit user go-ahead) Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary TypeScript parity for the Python `mesh.jobs.cancel/status/wait` facades shipped in #1077. Three new module-level async functions mirror the existing `postEvent` / `subscribeEvents` DDDI-clean pattern — callers with only a `jobId` no longer need to construct `new JobProxy(jobId, registryUrl)` and pass `MCP_MESH_REGISTRY_URL` explicitly. ```ts export async function cancel(jobId: string, reason?: string): Promise<void> export async function status(jobId: string): Promise<JobStatus> export async function wait(jobId: string, timeoutSecs?: number): Promise<unknown> ``` Each: `resolveRegistryUrl()` → `_getOrCreateProxy()` (LRU-cached) → napi dispatch → `translateJobError()` for typed exception re-classification. Underlying napi binding (`JsJobProxy.cancel/status/wait`) was already exposed — **no Rust changes**. `JobStatus` interface added alongside `JobEvent` / `JobEventReceipt`, exported from `mesh.jobs`. Fields mirror `job_to_json` in `jobs_napi.rs`: required fields typed `T`, `Option<T>` fields emitted as `T | null` (every key always present; only null-checks needed, no key-presence checks). `MeshJobsNamespace` extended with `cancel`/`status`/`wait` alphabetically; public exports added. ## Bonus generalization `resolveRegistryUrl`'s error message prefix generalized from `"mesh.jobs.postEvent:"` → `"mesh.jobs:"`. Now accurate for all four facades — mirrors the same generalization the Python BLOCKER fix made in #1077. ## Out of scope - **Java parity** — separate PR per polyglot cadence (mirrors the #1041 → #1043 → #1045 trilogy pattern) - **`TimeoutError` typed class** — `wait()` currently rejects with plain `Error` whose message starts with `"timeout:"`. Substring contract pinned by test. Follow-up if needed. - **Caller authorization** — design discussion at #1076 ## Review Notes Independent review: 0 BLOCKER, 1 WARNING (addressed in amended commit), 5 INFOs (skipped as cosmetic). - **WARNING fixed**: `JobStatus` interface used `field?: T` for nullable fields, but `job_to_json` always emits every field with `Value::Null` fallback. Tightened to `field: T | null` where appropriate, and `field: T` (no optional, no null) for fields the Rust binding emits unconditionally. Added one test pinning the every-key-present contract via `Object.keys` comparison. INFOs skipped: `Promise<unknown>` vs `Any` (the stricter shape is correct), repeated try/catch boilerplate matches `postEvent`'s style, empty `toHaveBeenCalledWith()` matcher is intentional for nullary calls, JSDoc null-vs-undefined nuance, doc table layout. ## Test plan - [x] TS unit tests: 39 pass (was 23, +16 new — 15 facade tests + 1 contract test) - [x] uc22_meshjob_ts integration: 24/24 pass (222.8s) - [x] `npm run build` clean (no TS compile errors after type tightening) - [x] Vitest hoisting workaround confined to test file (`import { cancel as cancelFacade }`); SDK exports stay canonical — `import { cancel } from "@mcpmesh/sdk"` works unaliased for end users - [x] Cross-runtime scope: no edits to Python / Java / Rust / `jobs_java.md` / Python `jobs.md` variant - [x] Public-artifact framing: no naming downstream consumers; structural language throughout Closes #1078 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added job lifecycle management capabilities: cancel jobs, retrieve job status, and wait for job completion. * Enhanced job operation documentation with TypeScript implementation examples and per-runtime behavior specifications. * **Tests** * Expanded test coverage for new job control operations and error handling scenarios. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1079?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Closes #1042. TypeScript vertical for the consumer-pushed event-injection primitive that shipped Python-first in #1041 (closes #1032).
Follows the original MeshJob cross-runtime shipping cadence: Python (#861/#878) → TypeScript (#885) → Java (#891). This PR is the TS step; Java parity will follow as a separate PR.
What's in the PR
NAPI bindings (
src/runtime/core/src/jobs_napi.rs)JsJobController::recv_event(types?, timeoutSecs?) -> Promise<JobEvent | null>JsJobProxy::send_event(eventType, payload) -> Promise<JobEventReceipt>parse_timeout_secshelper guards NaN/Infinity/negative inputs — applied at bothrecv_eventAND existingwait()call sitesJobError::JobTerminalmapped to a TS-visible named error via thejob_error_to_napiremapTypeScript SDK (
src/runtime/typescript/src/)recvEvent/sendEventsurfaced on theMeshJobinterface intypes.tsmesh.jobsnamespace injobs.tswithpostEvent(jobId, eventType, payload)convenience helperJobProxycache (default 256, env overrideMCP_MESH_JOBPROXY_CACHE_MAX) — mirrors Python implementation. JSMapinsertion-order semantics make O(1) LRU straightforward — no extra bookkeeping needed.JobNotFoundError/JobTerminalErrortyped error classesJobEvent/JobEventReceipttype definitionsindex.ts(mesh.jobs.postEvent, error classes, types)Tests
jobs.spec.tscoveringrecvEvent/sendEvent/postEvent, LRU eviction (cap=2, populate 3, assert oldest evicted), typed-error mapping, NaN/Inf timeout guards, MCP_MESH_REGISTRY_URL discovery, cache keyed by(registryUrl, jobId). 734 → 750 totaluc22_meshjob_ts: 3 new test cases mirror Python tc24/25/26:tc24_event_injection_happy_path_ts— payload round-triptc25_event_type_filter_ts— registry-side WHERE filtertc26_cancel_posts_synthetic_event_ts— cancel-grace verified in TS pathlong-task-provider-ts/long-task-consumer-tsfixtures (no new fixture agents)API parity matrix (TS ↔ Python)
await job.recvEvent(types, timeoutSecs)await job.recv_event(types, timeout_secs)await proxy.sendEvent(type, payload)await proxy.send_event(type, payload)mesh.jobs.postEvent(...)mesh.jobs.post_event(...)JobNotFoundError,JobTerminalErrorJobNotFoundError,JobTerminalErrorMCP_MESH_JOBPROXY_CACHE_MAX(256)MCP_MESH_JOBPROXY_CACHE_MAX(256)Erroron NaN/Inf/negValueErroron NaN/Inf/negCancel synthetic event grace is server-side (
MCP_MESH_CANCEL_EVENT_GRACE_MS, default 200ms, cap 10s) — no client change needed for TS parity.Review Notes
Independent review found 0 BLOCKERs, 2 WARNINGs, 4 INFOs. All addressed in commit
cb65611a5:delete process.env.MCP_MESH_REGISTRY_URLtodescribe("postEvent")beforeEachso each test starts from a known stateJobError::Displaywhich is wrapper-overriddengetOrCreateProxy→_getOrCreateProxyto match the existing_clearProxyCacheunderscore convention for test-only exportsTest plan
mesh.jobs.postEvent(jobId, "extend", {by: 5})reaches a Python producer'sawait job.recv_event(["extend"])(cross-runtime sanity check)await job.recvEvent(["cancelled"])exits gracefully when consumer callsproxy.cancel("reason")(200ms grace observed)Closes #1042
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
postEventmethod to send events to running jobsrecvEventmethod to wait for and receive job events with optional type filteringTests