@nanobpm/urban 0.83.0 → 0.85.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.
Files changed (32) hide show
  1. package/dist/runtime/core/host.d.ts +87 -1
  2. package/dist/runtime/core/host.js +4 -0
  3. package/dist/runtime/core/modules/api.d.ts +5 -0
  4. package/dist/runtime/core/modules/api.js +2 -2
  5. package/dist/runtime/core/modules/mcp.d.ts +57 -0
  6. package/dist/runtime/core/modules/mcp.js +709 -0
  7. package/dist/runtime/core/modules/surfaces.js +11 -0
  8. package/dist/runtime/engine/nanosdk.d.ts +41 -1
  9. package/dist/runtime/engine/nanosdk.js +118 -0
  10. package/dist/runtime/index.d.ts +1 -1
  11. package/package.json +2 -1
  12. package/src/runtime/core/host.ts +89 -0
  13. package/src/runtime/core/modules/actions.test.ts +4 -0
  14. package/src/runtime/core/modules/agent.test.ts +4 -0
  15. package/src/runtime/core/modules/api.test.ts +4 -0
  16. package/src/runtime/core/modules/api.ts +2 -2
  17. package/src/runtime/core/modules/cancel.test.ts +4 -0
  18. package/src/runtime/core/modules/connectors.test.ts +4 -0
  19. package/src/runtime/core/modules/datasource.migrations.test.ts +4 -0
  20. package/src/runtime/core/modules/instance-tracking.test.ts +4 -0
  21. package/src/runtime/core/modules/mcp.test.ts +687 -0
  22. package/src/runtime/core/modules/mcp.ts +814 -0
  23. package/src/runtime/core/modules/pages.test.ts +4 -0
  24. package/src/runtime/core/modules/surfaces.test.ts +24 -0
  25. package/src/runtime/core/modules/surfaces.ts +11 -0
  26. package/src/runtime/core/modules/triggers.test.ts +4 -0
  27. package/src/runtime/core/modules/workers.test.ts +4 -0
  28. package/src/runtime/core/runtime.extensions.test.ts +6 -0
  29. package/src/runtime/core/runtime.test.ts +6 -0
  30. package/src/runtime/engine/nanosdk.test.ts +103 -0
  31. package/src/runtime/engine/nanosdk.ts +152 -0
  32. package/src/runtime/index.ts +4 -0
@@ -318,6 +318,51 @@ export interface ElementInstanceWaitStateFilter {
318
318
  elementId?: string;
319
319
  waitStateType?: WaitStateType;
320
320
  }
321
+ /**
322
+ * The engine's own incident-state enum — the lifecycle states an engine actually reports and
323
+ * that are valid as a search selector. An `ACTIVE` incident is an open fault a human/agent can
324
+ * act on (resolve/retry); the other values are terminal or transitional. This is the single
325
+ * source of truth for real engine states; {@link IncidentState} derives from it.
326
+ */
327
+ export type EngineIncidentState = "ACTIVE" | "MIGRATED" | "PENDING" | "RESOLVED";
328
+ /**
329
+ * The lifecycle state of an incident, as {@link EngineClient.searchIncidents} reports it.
330
+ * Extends {@link EngineIncidentState} with the client-side `"UNKNOWN"` sentinel — a fallback
331
+ * used when the engine returns an unrecognized/absent value, so an incident is surfaced rather
332
+ * than dropped for an odd state. Because `"UNKNOWN"` is not a real engine state, it is not a
333
+ * valid {@link IncidentFilter} selector. A caller narrows on this union rather than probing a
334
+ * raw string.
335
+ */
336
+ export type IncidentState = EngineIncidentState | "UNKNOWN";
337
+ /**
338
+ * Selectors for an incident search: which process instance and/or lifecycle state to match.
339
+ * Used by {@link EngineClient.searchIncidents}. An eventually consistent (zero-wait) read; an
340
+ * unset selector matches every incident.
341
+ */
342
+ export interface IncidentFilter {
343
+ processInstanceKey?: string;
344
+ state?: EngineIncidentState;
345
+ }
346
+ /**
347
+ * A single incident (a stuck token — a job out of retries, an unhandled error, a failed
348
+ * gateway evaluation) as {@link EngineClient.searchIncidents} reports it. Carries the
349
+ * `incidentKey` a caller passes to {@link EngineClient.resolveIncident}, the owning
350
+ * `processInstanceKey`, and — when the incident is job-backed — the `jobKey` a caller passes
351
+ * to {@link EngineClient.updateJobRetries} to make the job retriable before resolving.
352
+ * `errorType`/`errorMessage` are the engine's fault description; they are free-form across
353
+ * engines (the live SDK reports a Camunda error-type enum, the in-process engine a coarser
354
+ * `kind`), so treat them as diagnostic text, not a stable taxonomy.
355
+ */
356
+ export interface IncidentSummary {
357
+ readonly incidentKey: string;
358
+ readonly processInstanceKey: string;
359
+ readonly elementId?: string;
360
+ readonly elementInstanceKey?: string;
361
+ readonly jobKey?: string;
362
+ readonly errorType?: string;
363
+ readonly errorMessage?: string;
364
+ readonly state: IncidentState;
365
+ }
321
366
  /**
322
367
  * A deployed form's form-js schema plus its identifying metadata, as
323
368
  * {@link EngineClient.getForm} returns it. `schema` is the parsed form-js document
@@ -447,6 +492,47 @@ export interface EngineClient {
447
492
  * {@link searchElementInstances}.
448
493
  */
449
494
  getElementInstance(elementInstanceKey: string): Promise<ElementInstanceSummary | null>;
495
+ /**
496
+ * Search incidents by owning process instance and/or lifecycle state
497
+ * (`POST /v2/incidents/search`) — the open faults blocking one or more instances (a job out
498
+ * of retries, an unhandled error, a failed gateway evaluation). An eventually consistent
499
+ * (zero-wait) read; each {@link IncidentSummary} carries the `incidentKey` to pass to
500
+ * {@link resolveIncident} and, for a job-backed incident, the `jobKey` to pass to
501
+ * {@link updateJobRetries}. The debugging counterpart to the read accessors above: where
502
+ * they answer "how far has this instance progressed", this answers "why is it stuck".
503
+ */
504
+ searchIncidents(filter?: IncidentFilter): Promise<IncidentSummary[]>;
505
+ /**
506
+ * Resolve an open incident by key (`POST /v2/incidents/{incidentKey}/resolution`), unblocking
507
+ * the parked token: a job incident returns the job to the activatable pool (so it must have
508
+ * retries left first — see {@link updateJobRetries}), a gateway incident re-evaluates, an
509
+ * uncaught-error incident re-creates the service-task job. A mutating operation.
510
+ */
511
+ resolveIncident(input: {
512
+ incidentKey: string;
513
+ }): Promise<void>;
514
+ /**
515
+ * Update the remaining retries of a job (`PATCH /v2/jobs/{jobKey}`) — the "retry a failed
516
+ * job" operation. Setting a failed job's retries back above zero makes it activatable again;
517
+ * paired with {@link resolveIncident}, this is how a stuck `jobNoRetries` incident is cleared
518
+ * (bump retries, then resolve). A mutating operation.
519
+ */
520
+ updateJobRetries(input: {
521
+ jobKey: string;
522
+ retries: number;
523
+ }): Promise<void>;
524
+ /**
525
+ * Set (merge) variables into a scope (`PUT /v2/element-instances/{scopeKey}/variables`).
526
+ * `scopeKey` is a process-instance key or an element-instance key; when `local` is `true` the
527
+ * variables are merged strictly into that local scope, otherwise (the default) they propagate
528
+ * to the outermost scope. A mutating operation used to repair state before resolving an
529
+ * incident or to steer an instance during debugging.
530
+ */
531
+ setVariables(input: {
532
+ scopeKey: string;
533
+ variables: Record<string, unknown>;
534
+ local?: boolean;
535
+ }): Promise<void>;
450
536
  /** Register a push worker for a job type. Draining is handled by the adapter. */
451
537
  registerWorker(jobType: string, handler: JobHandler, options?: {
452
538
  workerName?: string;
@@ -470,4 +556,4 @@ export interface EngineClient {
470
556
  * and `getForm` before it) that a purely structural `implements EngineClient` check no
471
557
  * longer catches once the fake is published/compiled against an older `@nanobpm/urban`.
472
558
  */
473
- export declare const ENGINE_CLIENT_METHODS: readonly ["deployResources", "createInstance", "cancelInstance", "publishMessage", "searchUserTasks", "openUserTasks", "getForm", "completeUserTask", "searchProcessInstances", "searchElementInstances", "searchElementInstanceWaitStates", "getElementInstance", "registerWorker", "close"];
559
+ export declare const ENGINE_CLIENT_METHODS: readonly ["deployResources", "createInstance", "cancelInstance", "publishMessage", "searchUserTasks", "openUserTasks", "getForm", "completeUserTask", "searchProcessInstances", "searchElementInstances", "searchElementInstanceWaitStates", "getElementInstance", "searchIncidents", "resolveIncident", "updateJobRetries", "setVariables", "registerWorker", "close"];
@@ -69,6 +69,10 @@ export const ENGINE_CLIENT_METHODS = [
69
69
  "searchElementInstances",
70
70
  "searchElementInstanceWaitStates",
71
71
  "getElementInstance",
72
+ "searchIncidents",
73
+ "resolveIncident",
74
+ "updateJobRetries",
75
+ "setVariables",
72
76
  "registerWorker",
73
77
  "close",
74
78
  ];
@@ -13,6 +13,10 @@ export interface ApiBinding {
13
13
  * for free — the UI reads the SAME OpenAPI document the runtime routes + validates from. */
14
14
  docs?: boolean | string;
15
15
  }
16
+ /** The canonical, non-configurable mount prefix for an app's OpenAPI operations. Operations always
17
+ * live under `/app/api` so page actions, links, and any `callRoute` can name an operation path as a
18
+ * stable framework constant (`/app/api/<op path>`) — there is no per-app `base` knob to drift. */
19
+ export declare const API_BASE = "/app/api";
16
20
  /**
17
21
  * Derive the ABSOLUTE spec + operations-server URLs the Swagger UI page should use, from the docs
18
22
  * page's own address (`pageHref`), the mount-root-relative docs path (`docsBase`, e.g.
@@ -97,6 +101,7 @@ export declare class NotImplemented extends Error {
97
101
  export declare function defineOperation<Req extends OperationContract = DefaultContract, Res = unknown>(_id: string, handler: OperationHandler<Req, Res>): OperationHandler<Req, Res>;
98
102
  /** Resolve an operation delegate from a loaded module: `default` (when a function) else `handler`. */
99
103
  export declare function resolveOperationHandler(mod: Record<string, unknown>): OperationHandler | undefined;
104
+ export declare function readApiBinding(manifest: unknown): ApiBinding | undefined;
100
105
  /** The Swagger UI route for an app's `api` binding, or `undefined` when there is no api surface
101
106
  * or docs are disabled. Lets other surfaces (e.g. the pages shell's "API docs" badge) link to
102
107
  * the docs without re-deriving the path. */
@@ -19,7 +19,7 @@ import { collectOperations, isSafeOperationId, operationsWithoutId, operationsWi
19
19
  /** The canonical, non-configurable mount prefix for an app's OpenAPI operations. Operations always
20
20
  * live under `/app/api` so page actions, links, and any `callRoute` can name an operation path as a
21
21
  * stable framework constant (`/app/api/<op path>`) — there is no per-app `base` knob to drift. */
22
- const API_BASE = "/app/api";
22
+ export const API_BASE = "/app/api";
23
23
  /** Pinned Swagger UI dist served from a CDN, so the docs UI adds zero bundle/runtime deps to an
24
24
  * Urban app (matching the deps-free spirit of the rest of the surface — validators are standalone,
25
25
  * ADR 0058 Consequences). Pinned (not `latest`) for reproducibility. */
@@ -147,7 +147,7 @@ export function resolveOperationHandler(mod) {
147
147
  return mod.handler;
148
148
  return undefined;
149
149
  }
150
- function readApiBinding(manifest) {
150
+ export function readApiBinding(manifest) {
151
151
  const raw = manifest && typeof manifest === "object" ? Reflect.get(manifest, "api") : undefined;
152
152
  if (!raw || typeof raw !== "object")
153
153
  return undefined;
@@ -0,0 +1,57 @@
1
+ import type { AppApi, RuntimeContext } from "../context.ts";
2
+ import type { HttpRequest } from "../host.ts";
3
+ import { type Route } from "../router.ts";
4
+ export interface McpHandle {
5
+ readonly name: string;
6
+ routes: Route[];
7
+ /** Whether the surface actually answers vs. 404s (config-enabled). The route is always mounted
8
+ * for a stable address, so callers deriving an "active surfaces" list must gate on this. */
9
+ readonly enabled: boolean;
10
+ describe(): Record<string, unknown>;
11
+ }
12
+ /** Mint a fresh MCP session id, host-agnostically. Prefers the Web Crypto `randomUUID` (a global,
13
+ * not `node:crypto`) where present, with a time+random fallback so a host without `crypto` never
14
+ * throws — mirroring how core treats Web Crypto as optional (see `mintRootRequestKey`). */
15
+ export declare function newSessionId(): string;
16
+ /** Bound the live-session map to `max` by evicting least-recently-used entries (the `Map`'s leading
17
+ * keys, given `touchSession` re-inserts on use), closing each victim best-effort so its transport /
18
+ * server releases. This is the categorical fix for the unbounded-growth vector: no client can pin
19
+ * more than `max` server/transport pairs, whatever its `initialize` cadence. Exported for a direct
20
+ * unit test of the LRU eviction contract. */
21
+ export declare function evictExcessSessions<S>(sessions: Map<string, S>, max: number, close: (session: S) => Promise<void>): Promise<void>;
22
+ /** The resolved MCP access policy for this app. */
23
+ interface McpConfig {
24
+ /** Whether the MCP surface answers at all. Defaults ON. */
25
+ readonly enabled: boolean;
26
+ /** Whether non-loopback callers are allowed. Defaults OFF (loopback-only). */
27
+ readonly allowRemote: boolean;
28
+ }
29
+ /**
30
+ * Resolve the MCP access policy from the manifest (`mcp: { enabled?, allowRemote? }`, read
31
+ * reflectively until the app schema folds the field in — mirroring how `readApiBinding` reads
32
+ * `api`) and the environment (`URBAN_MCP_ENABLED`, `URBAN_MCP_ALLOW_REMOTE`). The env flags win so
33
+ * an operator can force the surface off (or open it to a non-loopback bind) without editing the
34
+ * manifest. Enabled defaults ON and access defaults to loopback-only ("defaults ON for loopback").
35
+ */
36
+ export declare function readMcpConfig(manifest: unknown, env: (name: string) => string | undefined): McpConfig;
37
+ /** Whether the request targets a loopback host — the best-effort loopback signal available at the
38
+ * host seam (which carries no peer address). A missing Host header is treated as loopback (a direct
39
+ * local client that sent none). */
40
+ export declare function isLoopbackRequest(req: HttpRequest): boolean;
41
+ /** The `required` keys a tool's `inputSchema` declares but the call omitted. Presence is decided
42
+ * solely by {@link isBlankArg} — the single source of truth also used for path args — so a value is
43
+ * "provided" unless it is absent (`undefined`/`null`) or a blank/whitespace-only string. A required
44
+ * non-string input (e.g. a numeric/boolean/object arg) that is present is therefore accepted, not
45
+ * wrongly reported missing. Derived from the schema's own `required` array so declaring a field
46
+ * required is enough to have missing (or whitespace-only) calls rejected rather than silently
47
+ * substituted with an empty string. */
48
+ export declare function missingRequiredArgs(inputSchema: Record<string, unknown>, args: Record<string, unknown>): string[];
49
+ /**
50
+ * Mount the runtime-served MCP surface at `/app/mcp` (ADR 0067). Returns a single `*`-method route
51
+ * (mounted unconditionally, like `/app/agent`); the handler bridges the host request/response seam
52
+ * to the SDK's Web-Standard Streamable-HTTP transport, maintaining one MCP session per
53
+ * `mcp-session-id`. The tool projection reuses the SAME OpenAPI enumeration and dispatch path as
54
+ * `mountApi`, so a tool call is equivalent to the corresponding HTTP call.
55
+ */
56
+ export declare function mountMcp(ctx: RuntimeContext, app: AppApi, apiRoutes: Route[]): McpHandle;
57
+ export {};