@mcpmesh/sdk 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/jobs.d.ts ADDED
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Public `mesh.jobs` namespace — convenience helpers for the MeshJob
3
+ * event-injection primitive (Phase 1, MeshJob substrate; event-channel
4
+ * extension landed in v2.2).
5
+ *
6
+ * The primary surfaces are:
7
+ *
8
+ * - {@link postEvent} — fire-and-forget helper to push an event into a
9
+ * running job by id, without holding a `JobProxy` reference. Intended
10
+ * for MCP tool bodies that receive a `jobId` in their request payload
11
+ * (e.g. a "submit_user_input" tool exposed by an orchestrator agent).
12
+ *
13
+ * - {@link JobNotFoundError} / {@link JobTerminalError} — typed `Error`
14
+ * subclasses translated from the Rust core's `JobError` variants. The
15
+ * napi binding surfaces all `JobError` variants as a plain `Error`
16
+ * today (see `src/runtime/core/src/jobs_napi.rs::job_error_to_napi`),
17
+ * so we re-classify on the TypeScript side via stable
18
+ * error-message substrings ("job is terminal" / "job not found").
19
+ *
20
+ * The `JobController` / `JobProxy` napi-rs classes from `@mcpmesh/core`
21
+ * already expose `recvEvent` / `sendEvent` methods directly — application
22
+ * code calls them via the `MeshJob`-typed parameter the framework
23
+ * injects. This module just adds the helper + error classes around that
24
+ * surface, mirroring Python's `mesh.jobs.post_event` API one-for-one.
25
+ */
26
+ import { JobProxy } from "@mcpmesh/core";
27
+ /**
28
+ * Event posted into a running job's event log. Matches the OpenAPI
29
+ * `JobEvent` schema field-for-field (and the Rust `JobEvent` /
30
+ * Python `job_event_to_pydict` shape). Returned by `JobController.recvEvent`.
31
+ */
32
+ export interface JobEvent {
33
+ /** Server-assigned job UUID this event belongs to. */
34
+ job_id: string;
35
+ /** Per-job monotonic sequence number assigned by the registry. */
36
+ seq: number;
37
+ /** User-supplied event type tag (e.g. "signal", "user_input"). */
38
+ type: string;
39
+ /** Arbitrary JSON-shaped payload carried with the event (or `null`). */
40
+ payload: unknown;
41
+ /** W3C trace context propagated by the poster (or `null`). */
42
+ trace_context: unknown;
43
+ /** Identifier of the agent that posted the event (or `null`). */
44
+ posted_by: string | null;
45
+ /** Unix epoch seconds at which the registry created the row. */
46
+ created_at: number;
47
+ }
48
+ /**
49
+ * Receipt returned by `JobProxy.sendEvent` / `postEvent`. Matches the
50
+ * OpenAPI `JobEventPostResponse` schema field-for-field.
51
+ */
52
+ export interface JobEventReceipt {
53
+ /** Server-assigned job UUID the event was posted into. */
54
+ job_id: string;
55
+ /** Per-job monotonic sequence number assigned by the registry. */
56
+ seq: number;
57
+ /** Unix epoch seconds at which the registry created the row. */
58
+ created_at: number;
59
+ }
60
+ /**
61
+ * The targeted job does not exist (or has been swept) in the registry.
62
+ *
63
+ * Translated from the Rust `JobError::Other(BackendError::NotFound)`
64
+ * path (`GET/POST /jobs/{id}/events` → HTTP 404).
65
+ */
66
+ export declare class JobNotFoundError extends Error {
67
+ readonly name = "JobNotFoundError";
68
+ constructor(message: string);
69
+ }
70
+ /**
71
+ * The targeted job is in a terminal state (completed / failed /
72
+ * cancelled) and no longer accepts events.
73
+ *
74
+ * Translated from the Rust `JobError::JobTerminal` variant — the
75
+ * registry returns HTTP 409 once the job row is terminal and the Rust
76
+ * layer maps that to `JobTerminal`.
77
+ */
78
+ export declare class JobTerminalError extends Error {
79
+ readonly name = "JobTerminalError";
80
+ constructor(message: string);
81
+ }
82
+ /**
83
+ * Re-classify a generic `Error` raised by the napi layer into one of the
84
+ * typed subclasses, if the message matches. Returns the original error
85
+ * (or a typed clone) — callers should `throw` the returned value.
86
+ *
87
+ * Exported so tests can exercise the substring contract without a real
88
+ * napi failure path.
89
+ */
90
+ export declare function translateJobError(err: unknown): unknown;
91
+ /**
92
+ * Return a process-cached `JobProxy` for the given
93
+ * `(registryUrl, jobId)` pair, constructing one on first miss.
94
+ *
95
+ * Cache is a bounded LRU: hits bump the entry to the most-recent end of
96
+ * the insertion-order map, misses on a full cache evict the
97
+ * least-recent entry before inserting. Exported only for tests; not
98
+ * part of the public API.
99
+ */
100
+ export declare function _getOrCreateProxy(registryUrl: string, jobId: string): JobProxy;
101
+ /**
102
+ * Clear the JobProxy cache. Exposed for tests — not part of the public
103
+ * API. Equivalent to dropping all entries; the underlying napi handles
104
+ * are released when JS GC runs.
105
+ */
106
+ export declare function _clearProxyCache(): void;
107
+ /**
108
+ * Post an event to a running job by ID.
109
+ *
110
+ * Convenience helper for tool bodies that hold a `jobId` (e.g. from a
111
+ * request body, a token lookup, or a stashed reference) but do NOT
112
+ * have a `JobProxy` reference in scope. Constructs (or reuses, via the
113
+ * LRU cache) a `JobProxy` bound to the current agent's registry URL
114
+ * and forwards the call.
115
+ *
116
+ * Mirrors Python's `mesh.jobs.post_event` API one-for-one.
117
+ *
118
+ * @param jobId - Target job's server-assigned id.
119
+ * @param eventType - Event type tag (e.g. `"extend_deadline"`,
120
+ * `"user_input"`, or any user-defined string). The running handler
121
+ * can filter via `await job.recvEvent(["..."])`.
122
+ * @param payload - Optional JSON-serializable payload carried with the
123
+ * event. `undefined`/`null` is normalized to an empty object before
124
+ * forwarding — the Rust layer accepts either.
125
+ * @returns Receipt `{ job_id, seq, created_at }`. `seq` is the
126
+ * server-assigned sequence number useful for stitching follow-up
127
+ * `recvEvent` calls.
128
+ *
129
+ * @throws {@link JobNotFoundError} If the registry doesn't know the
130
+ * job (sweep already removed it, or wrong id).
131
+ * @throws {@link JobTerminalError} If the job has already reached a
132
+ * terminal state — no more events accepted.
133
+ * @throws Error For transport errors (registry unreachable, 5xx after
134
+ * retries, malformed payload, etc.) — the underlying error message
135
+ * is preserved.
136
+ *
137
+ * @example
138
+ * Inside an MCP tool body that holds a job id:
139
+ * ```ts
140
+ * agent.addTool({
141
+ * name: "submit_user_input",
142
+ * capability: "submit_user_input",
143
+ * parameters: z.object({ jobId: z.string(), text: z.string() }),
144
+ * execute: async ({ jobId, text }) => {
145
+ * const receipt = await mesh.jobs.postEvent(
146
+ * jobId,
147
+ * "user_input",
148
+ * { text },
149
+ * );
150
+ * return { posted_seq: receipt.seq };
151
+ * },
152
+ * });
153
+ * ```
154
+ */
155
+ export declare function postEvent(jobId: string, eventType: string, payload?: unknown): Promise<JobEventReceipt>;
156
+ /**
157
+ * Options for {@link subscribeEvents}. All fields are optional —
158
+ * defaults match Python's `mesh.jobs.subscribe_events` keyword args.
159
+ */
160
+ export interface SubscribeEventsOptions {
161
+ /**
162
+ * Optional event-type filter applied server-side. Only events whose
163
+ * `type` matches one of these is yielded. Omit for all types.
164
+ */
165
+ types?: string[];
166
+ /**
167
+ * Initial cursor (default `0` ≡ from the beginning of the event log).
168
+ * Pass a higher value to skip historical events. Accepts both
169
+ * `number` and `bigint` for callers stitching from an `i64`-sourced
170
+ * cursor.
171
+ */
172
+ after?: number | bigint;
173
+ /**
174
+ * Long-poll wait budget per registry call (in seconds). Default `30`.
175
+ * Capped at `60` by the registry. Pass `null` to skip the long-poll
176
+ * entirely (single immediate read; rarely needed — tight-poll callers
177
+ * should pass `0` instead).
178
+ */
179
+ longPollSecs?: number | null;
180
+ }
181
+ /**
182
+ * Subscribe to events posted to a running job by ID.
183
+ *
184
+ * Long-lived async iterator. Each call manages its own cursor —
185
+ * multiple subscribers can observe the same job's events independently
186
+ * without affecting the producer's `recvEvent` consumption (the
187
+ * producer's cursor is per-controller; this observer's cursor is
188
+ * per-call).
189
+ *
190
+ * The iterator runs indefinitely until the caller breaks out of the
191
+ * `for await` loop or the underlying registry returns
192
+ * {@link JobNotFoundError}. There is no automatic terminal-state
193
+ * detection — use a synthetic event type (e.g. `{ type: "ended" }`)
194
+ * posted by your application to signal iteration end.
195
+ *
196
+ * Mirrors Python's `mesh.jobs.subscribe_events` one-for-one.
197
+ *
198
+ * @param jobId - Target job's server-assigned id.
199
+ * @param options - Optional filter / cursor / long-poll knobs.
200
+ * @yields Event objects: `{ seq, type, payload, trace_context,
201
+ * posted_by, created_at, job_id }`.
202
+ *
203
+ * @throws {@link JobNotFoundError} If the job has been reaped from the
204
+ * registry (404 on the `GET /jobs/{id}/events` endpoint).
205
+ * @throws Error For transport errors (registry unreachable, 5xx after
206
+ * retries, malformed payload, etc.) — the underlying error message
207
+ * is preserved.
208
+ *
209
+ * @example
210
+ * Mirror events from a running job into a downstream system:
211
+ * ```ts
212
+ * for await (const event of mesh.jobs.subscribeEvents(jobId, {
213
+ * types: ["progress", "result"],
214
+ * })) {
215
+ * await downstream.publish(event);
216
+ * if (event.type === "result") break; // caller-defined termination
217
+ * }
218
+ * ```
219
+ */
220
+ export declare function subscribeEvents(jobId: string, options?: SubscribeEventsOptions): AsyncGenerator<JobEvent, void, unknown>;
221
+ //# sourceMappingURL=jobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../src/jobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAC;IACvB,iEAAiE;IACjE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;CACpB;AAoBD;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,sBAAsB;gBACvB,OAAO,EAAE,MAAM;CAG5B;AAED;;;;;;;GAOG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,sBAAsB;gBACvB,OAAO,EAAE,MAAM;CAG5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAwBvD;AAmCD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAuB9E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AA6BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,eAAe,CAAC,CAc1B;AAMD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAuB,eAAe,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,sBAA2B,GACnC,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAmEzC"}
package/dist/jobs.js ADDED
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Public `mesh.jobs` namespace — convenience helpers for the MeshJob
3
+ * event-injection primitive (Phase 1, MeshJob substrate; event-channel
4
+ * extension landed in v2.2).
5
+ *
6
+ * The primary surfaces are:
7
+ *
8
+ * - {@link postEvent} — fire-and-forget helper to push an event into a
9
+ * running job by id, without holding a `JobProxy` reference. Intended
10
+ * for MCP tool bodies that receive a `jobId` in their request payload
11
+ * (e.g. a "submit_user_input" tool exposed by an orchestrator agent).
12
+ *
13
+ * - {@link JobNotFoundError} / {@link JobTerminalError} — typed `Error`
14
+ * subclasses translated from the Rust core's `JobError` variants. The
15
+ * napi binding surfaces all `JobError` variants as a plain `Error`
16
+ * today (see `src/runtime/core/src/jobs_napi.rs::job_error_to_napi`),
17
+ * so we re-classify on the TypeScript side via stable
18
+ * error-message substrings ("job is terminal" / "job not found").
19
+ *
20
+ * The `JobController` / `JobProxy` napi-rs classes from `@mcpmesh/core`
21
+ * already expose `recvEvent` / `sendEvent` methods directly — application
22
+ * code calls them via the `MeshJob`-typed parameter the framework
23
+ * injects. This module just adds the helper + error classes around that
24
+ * surface, mirroring Python's `mesh.jobs.post_event` API one-for-one.
25
+ */
26
+ import { JobProxy } from "@mcpmesh/core";
27
+ // ---------------------------------------------------------------------------
28
+ // Typed error classes
29
+ // ---------------------------------------------------------------------------
30
+ //
31
+ // The Rust core's `JobError::Other(BackendError::NotFound)` and
32
+ // `JobError::JobTerminal` variants currently surface as plain `Error` from
33
+ // the napi layer (see `src/runtime/core/src/jobs_napi.rs::job_error_to_napi`).
34
+ // Until the napi binding switches to a custom exception type, we
35
+ // re-classify on the TypeScript side via stable substrings emitted by the
36
+ // NAPI wrapper's explicit error remap in
37
+ // `src/runtime/core/src/jobs_napi.rs` (`job_error_to_napi` at lines
38
+ // 85-102, specifically the `JobTerminal` arm at 97-99). The wrapper
39
+ // deliberately remaps `JobError::Display` to a stable SDK-facing format
40
+ // — do NOT collapse this remap thinking it just passes core's Display
41
+ // through; the substring contract here depends on it. Both classes
42
+ // derive from `Error` so existing `catch (Error)` handlers continue to
43
+ // catch them.
44
+ /**
45
+ * The targeted job does not exist (or has been swept) in the registry.
46
+ *
47
+ * Translated from the Rust `JobError::Other(BackendError::NotFound)`
48
+ * path (`GET/POST /jobs/{id}/events` → HTTP 404).
49
+ */
50
+ export class JobNotFoundError extends Error {
51
+ name = "JobNotFoundError";
52
+ constructor(message) {
53
+ super(message);
54
+ }
55
+ }
56
+ /**
57
+ * The targeted job is in a terminal state (completed / failed /
58
+ * cancelled) and no longer accepts events.
59
+ *
60
+ * Translated from the Rust `JobError::JobTerminal` variant — the
61
+ * registry returns HTTP 409 once the job row is terminal and the Rust
62
+ * layer maps that to `JobTerminal`.
63
+ */
64
+ export class JobTerminalError extends Error {
65
+ name = "JobTerminalError";
66
+ constructor(message) {
67
+ super(message);
68
+ }
69
+ }
70
+ /**
71
+ * Re-classify a generic `Error` raised by the napi layer into one of the
72
+ * typed subclasses, if the message matches. Returns the original error
73
+ * (or a typed clone) — callers should `throw` the returned value.
74
+ *
75
+ * Exported so tests can exercise the substring contract without a real
76
+ * napi failure path.
77
+ */
78
+ export function translateJobError(err) {
79
+ if (!(err instanceof Error) ||
80
+ err instanceof JobNotFoundError ||
81
+ err instanceof JobTerminalError) {
82
+ return err;
83
+ }
84
+ const msg = err.message ?? "";
85
+ const msgLower = msg.toLowerCase();
86
+ // Order matters: "job is terminal" is the JobTerminal variant's Display
87
+ // prefix (see jobs_napi.rs::job_error_to_napi); "job not found" is
88
+ // BackendError::NotFound's Display prefix.
89
+ if (msgLower.includes("job is terminal")) {
90
+ const typed = new JobTerminalError(msg);
91
+ typed.cause = err;
92
+ return typed;
93
+ }
94
+ if (msgLower.includes("job not found")) {
95
+ const typed = new JobNotFoundError(msg);
96
+ typed.cause = err;
97
+ return typed;
98
+ }
99
+ return err;
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // JobProxy cache (mirror of Python's `_get_or_create_proxy`)
103
+ // ---------------------------------------------------------------------------
104
+ //
105
+ // `postEvent` used to construct a fresh `JobProxy` on every call. Each
106
+ // proxy wraps a Rust `reqwest::Client` with its own connection pool, so a
107
+ // steady-state sender that fires off `postEvent` in a hot loop would
108
+ // force a fresh TCP/TLS handshake against the registry on every call.
109
+ // Cache by `(registryUrl, jobId)` for the process lifetime; the cache
110
+ // key is invalidated naturally when a different registry URL or job id is
111
+ // used.
112
+ //
113
+ // Bounded LRU eviction: long-lived senders that post events to many
114
+ // distinct jobs (e.g. a router fanning out across thousands of jobs) would
115
+ // otherwise grow the cache without bound. `Map` preserves insertion
116
+ // order; deleting + re-inserting on hit and shifting off the first key on
117
+ // overflow gives us O(1) LRU semantics. The Rust `JobProxy` does not
118
+ // expose an explicit `close()` over napi — eviction just drops the Map
119
+ // entry and lets JS GC release the wrapped `reqwest::Client` connection
120
+ // pool when no JS references remain.
121
+ const _PROXY_CACHE_DEFAULT_MAX = 256;
122
+ function proxyCacheMax() {
123
+ const raw = process.env.MCP_MESH_JOBPROXY_CACHE_MAX;
124
+ if (!raw)
125
+ return _PROXY_CACHE_DEFAULT_MAX;
126
+ const value = parseInt(raw, 10);
127
+ if (!Number.isFinite(value) || value <= 0)
128
+ return _PROXY_CACHE_DEFAULT_MAX;
129
+ return value;
130
+ }
131
+ const _proxyCache = new Map();
132
+ /**
133
+ * Return a process-cached `JobProxy` for the given
134
+ * `(registryUrl, jobId)` pair, constructing one on first miss.
135
+ *
136
+ * Cache is a bounded LRU: hits bump the entry to the most-recent end of
137
+ * the insertion-order map, misses on a full cache evict the
138
+ * least-recent entry before inserting. Exported only for tests; not
139
+ * part of the public API.
140
+ */
141
+ export function _getOrCreateProxy(registryUrl, jobId) {
142
+ const key = `${registryUrl} ${jobId}`;
143
+ const existing = _proxyCache.get(key);
144
+ if (existing !== undefined) {
145
+ // Bump to most-recent end of insertion order: delete + re-set.
146
+ _proxyCache.delete(key);
147
+ _proxyCache.set(key, existing);
148
+ return existing;
149
+ }
150
+ const proxy = new JobProxy(jobId, registryUrl);
151
+ const maxSize = proxyCacheMax();
152
+ while (_proxyCache.size >= maxSize) {
153
+ // Evict LRU = first entry in insertion order. The Map iterator yields
154
+ // keys in insertion order; pull the oldest and delete it. Dropping
155
+ // the entry releases our reference to the JobProxy; JS GC reclaims
156
+ // the wrapped reqwest client (and its connection pool) when no other
157
+ // refs exist.
158
+ const oldest = _proxyCache.keys().next().value;
159
+ if (oldest === undefined)
160
+ break;
161
+ _proxyCache.delete(oldest);
162
+ }
163
+ _proxyCache.set(key, proxy);
164
+ return proxy;
165
+ }
166
+ /**
167
+ * Clear the JobProxy cache. Exposed for tests — not part of the public
168
+ * API. Equivalent to dropping all entries; the underlying napi handles
169
+ * are released when JS GC runs.
170
+ */
171
+ export function _clearProxyCache() {
172
+ _proxyCache.clear();
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // postEvent convenience helper
176
+ // ---------------------------------------------------------------------------
177
+ /**
178
+ * Discover the registry base URL the running agent is bound to.
179
+ *
180
+ * Mirrors Python's `mesh.jobs._resolve_registry_url`: the canonical
181
+ * source is the `MCP_MESH_REGISTRY_URL` environment variable. The
182
+ * configuration pipeline writes this on agent startup and every
183
+ * job-substrate code path reads it.
184
+ *
185
+ * @throws Error if the variable isn't set — the caller can't post an
186
+ * event without knowing which registry to target.
187
+ */
188
+ function resolveRegistryUrl() {
189
+ const url = process.env.MCP_MESH_REGISTRY_URL;
190
+ if (!url) {
191
+ throw new Error("mesh.jobs.postEvent: MCP_MESH_REGISTRY_URL is not set; " +
192
+ "cannot resolve registry base URL. Ensure the calling " +
193
+ "process is running inside a mesh agent.");
194
+ }
195
+ return url;
196
+ }
197
+ /**
198
+ * Post an event to a running job by ID.
199
+ *
200
+ * Convenience helper for tool bodies that hold a `jobId` (e.g. from a
201
+ * request body, a token lookup, or a stashed reference) but do NOT
202
+ * have a `JobProxy` reference in scope. Constructs (or reuses, via the
203
+ * LRU cache) a `JobProxy` bound to the current agent's registry URL
204
+ * and forwards the call.
205
+ *
206
+ * Mirrors Python's `mesh.jobs.post_event` API one-for-one.
207
+ *
208
+ * @param jobId - Target job's server-assigned id.
209
+ * @param eventType - Event type tag (e.g. `"extend_deadline"`,
210
+ * `"user_input"`, or any user-defined string). The running handler
211
+ * can filter via `await job.recvEvent(["..."])`.
212
+ * @param payload - Optional JSON-serializable payload carried with the
213
+ * event. `undefined`/`null` is normalized to an empty object before
214
+ * forwarding — the Rust layer accepts either.
215
+ * @returns Receipt `{ job_id, seq, created_at }`. `seq` is the
216
+ * server-assigned sequence number useful for stitching follow-up
217
+ * `recvEvent` calls.
218
+ *
219
+ * @throws {@link JobNotFoundError} If the registry doesn't know the
220
+ * job (sweep already removed it, or wrong id).
221
+ * @throws {@link JobTerminalError} If the job has already reached a
222
+ * terminal state — no more events accepted.
223
+ * @throws Error For transport errors (registry unreachable, 5xx after
224
+ * retries, malformed payload, etc.) — the underlying error message
225
+ * is preserved.
226
+ *
227
+ * @example
228
+ * Inside an MCP tool body that holds a job id:
229
+ * ```ts
230
+ * agent.addTool({
231
+ * name: "submit_user_input",
232
+ * capability: "submit_user_input",
233
+ * parameters: z.object({ jobId: z.string(), text: z.string() }),
234
+ * execute: async ({ jobId, text }) => {
235
+ * const receipt = await mesh.jobs.postEvent(
236
+ * jobId,
237
+ * "user_input",
238
+ * { text },
239
+ * );
240
+ * return { posted_seq: receipt.seq };
241
+ * },
242
+ * });
243
+ * ```
244
+ */
245
+ export async function postEvent(jobId, eventType, payload) {
246
+ const registryUrl = resolveRegistryUrl();
247
+ const proxy = _getOrCreateProxy(registryUrl, jobId);
248
+ const safePayload = payload !== undefined && payload !== null ? payload : {};
249
+ try {
250
+ const receipt = (await proxy.sendEvent(eventType, safePayload));
251
+ return receipt;
252
+ }
253
+ catch (err) {
254
+ const translated = translateJobError(err);
255
+ if (translated !== err) {
256
+ throw translated;
257
+ }
258
+ throw err;
259
+ }
260
+ }
261
+ /**
262
+ * Subscribe to events posted to a running job by ID.
263
+ *
264
+ * Long-lived async iterator. Each call manages its own cursor —
265
+ * multiple subscribers can observe the same job's events independently
266
+ * without affecting the producer's `recvEvent` consumption (the
267
+ * producer's cursor is per-controller; this observer's cursor is
268
+ * per-call).
269
+ *
270
+ * The iterator runs indefinitely until the caller breaks out of the
271
+ * `for await` loop or the underlying registry returns
272
+ * {@link JobNotFoundError}. There is no automatic terminal-state
273
+ * detection — use a synthetic event type (e.g. `{ type: "ended" }`)
274
+ * posted by your application to signal iteration end.
275
+ *
276
+ * Mirrors Python's `mesh.jobs.subscribe_events` one-for-one.
277
+ *
278
+ * @param jobId - Target job's server-assigned id.
279
+ * @param options - Optional filter / cursor / long-poll knobs.
280
+ * @yields Event objects: `{ seq, type, payload, trace_context,
281
+ * posted_by, created_at, job_id }`.
282
+ *
283
+ * @throws {@link JobNotFoundError} If the job has been reaped from the
284
+ * registry (404 on the `GET /jobs/{id}/events` endpoint).
285
+ * @throws Error For transport errors (registry unreachable, 5xx after
286
+ * retries, malformed payload, etc.) — the underlying error message
287
+ * is preserved.
288
+ *
289
+ * @example
290
+ * Mirror events from a running job into a downstream system:
291
+ * ```ts
292
+ * for await (const event of mesh.jobs.subscribeEvents(jobId, {
293
+ * types: ["progress", "result"],
294
+ * })) {
295
+ * await downstream.publish(event);
296
+ * if (event.type === "result") break; // caller-defined termination
297
+ * }
298
+ * ```
299
+ */
300
+ export async function* subscribeEvents(jobId, options = {}) {
301
+ const { types, after = 0, longPollSecs = 30 } = options;
302
+ const registryUrl = resolveRegistryUrl();
303
+ const proxy = _getOrCreateProxy(registryUrl, jobId);
304
+ // Cursor as `number` because the napi binding types `after` as
305
+ // `number` (i64 → JS Number). Accept `bigint` in the input options
306
+ // for callers stitching from another i64 source, but coerce down to
307
+ // `number` for the binding boundary — guarding the overflow case so
308
+ // an out-of-range i64 cursor surfaces as an explicit RangeError
309
+ // rather than silently truncating.
310
+ let cursor;
311
+ if (typeof after === "bigint") {
312
+ if (after > BigInt(Number.MAX_SAFE_INTEGER)) {
313
+ throw new RangeError(`subscribeEvents: 'after' cursor exceeds Number.MAX_SAFE_INTEGER (${Number.MAX_SAFE_INTEGER}); per-job seq overflowed JS Number range. Got: ${after}`);
314
+ }
315
+ cursor = Number(after);
316
+ }
317
+ else {
318
+ cursor = after ?? 0;
319
+ }
320
+ // The binding type signature is `number | null | undefined`; the
321
+ // Python sibling accepts `None` for "single immediate read", and we
322
+ // forward that through verbatim.
323
+ const wait = longPollSecs;
324
+ // eslint-disable-next-line no-constant-condition
325
+ while (true) {
326
+ let result;
327
+ try {
328
+ result = (await proxy.listEvents(cursor, types, wait));
329
+ }
330
+ catch (err) {
331
+ const translated = translateJobError(err);
332
+ if (translated !== err) {
333
+ throw translated;
334
+ }
335
+ throw err;
336
+ }
337
+ const { events, nextAfter } = result;
338
+ for (const event of events) {
339
+ const seq = event.seq;
340
+ // Reject booleans explicitly: `typeof true === "boolean"` but
341
+ // JS often widens bool↔number; the registry contract is integer
342
+ // seqs, so a bool here is a wire-level malformed payload.
343
+ if (typeof seq === "boolean") {
344
+ throw new Error(`subscribeEvents: registry returned event with boolean 'seq': ${JSON.stringify(event)}`);
345
+ }
346
+ if (typeof seq !== "number" && typeof seq !== "bigint") {
347
+ throw new Error(`subscribeEvents: registry returned event without integer 'seq': ${JSON.stringify(event)}`);
348
+ }
349
+ const seqNum = typeof seq === "bigint" ? Number(seq) : seq;
350
+ if (seqNum > cursor)
351
+ cursor = seqNum;
352
+ // listEvents returns ascending-seq; cursor advance before yield
353
+ // ensures correctness across consumer cancellation.
354
+ yield event;
355
+ }
356
+ // Empty pages (or pages filtered by `types` server-side) still
357
+ // advance the cursor via the registry-supplied watermark, so
358
+ // subsequent polls don't re-scan the same filtered range.
359
+ if (nextAfter > cursor)
360
+ cursor = nextAfter;
361
+ }
362
+ }
363
+ //# sourceMappingURL=jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../src/jobs.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -97,6 +97,18 @@ export interface MeshJob {
97
97
  updateProgress?(progress: number, message?: string): Promise<void>;
98
98
  complete?(result: unknown): Promise<void>;
99
99
  fail?(error: string): Promise<void>;
100
+ /**
101
+ * Wait for the next event posted into this job's event log.
102
+ *
103
+ * Returns the event object on arrival, `null` on timeout. Cursor is
104
+ * per-controller-instance (shared across `clone`s); a fresh
105
+ * controller for the same `jobId` replays from seq=0.
106
+ *
107
+ * Mirrors Python's `MeshJob.recv_event` (event-channel extension that
108
+ * shipped with v2.2 via PR #1041). Throws on `JobNotFound` /
109
+ * transport-layer failures; `timeoutSecs` rejects NaN/Infinity/negative.
110
+ */
111
+ recvEvent?(types?: string[], timeoutSecs?: number): Promise<import("./jobs.js").JobEvent | null>;
100
112
  submit?(payload?: Record<string, unknown>, options?: {
101
113
  maxDuration?: number;
102
114
  maxRetries?: number;
@@ -106,6 +118,15 @@ export interface MeshJob {
106
118
  wait?(timeoutSecs?: number): Promise<unknown>;
107
119
  status?(): Promise<Record<string, unknown>>;
108
120
  cancel?(reason?: string): Promise<void>;
121
+ /**
122
+ * Post an event into this job's event log. The running handler
123
+ * (inside the `task: true` job) will see it on its next `recvEvent`
124
+ * call — or wake immediately if it's currently long-polling.
125
+ *
126
+ * Mirrors Python's `MeshJob.send_event`. Throws `JobNotFoundError` /
127
+ * `JobTerminalError` for the corresponding registry error codes.
128
+ */
129
+ sendEvent?(eventType: string, payload?: unknown): Promise<import("./jobs.js").JobEventReceipt>;
109
130
  }
110
131
  /**
111
132
  * Dependency specification for mesh tool DI.