@nanobpm/urban 0.88.0 → 0.89.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 (30) hide show
  1. package/dist/runtime/core/host.d.ts +105 -1
  2. package/dist/runtime/core/host.js +3 -0
  3. package/dist/runtime/core/modules/mcp.js +68 -0
  4. package/dist/runtime/core/modules/runtime.gen.js +1 -1
  5. package/dist/runtime/engine/nanosdk.d.ts +30 -1
  6. package/dist/runtime/engine/nanosdk.js +163 -1
  7. package/dist/runtime/index.d.ts +1 -1
  8. package/package.json +1 -1
  9. package/src/runtime/browser/runtime.browser.js +77 -1
  10. package/src/runtime/browser/runtime.browser.test.ts +126 -1
  11. package/src/runtime/core/host.ts +111 -0
  12. package/src/runtime/core/modules/actions.test.ts +3 -0
  13. package/src/runtime/core/modules/agent.test.ts +3 -0
  14. package/src/runtime/core/modules/api.test.ts +3 -0
  15. package/src/runtime/core/modules/cancel.test.ts +3 -0
  16. package/src/runtime/core/modules/connectors.test.ts +3 -0
  17. package/src/runtime/core/modules/datasource.migrations.test.ts +3 -0
  18. package/src/runtime/core/modules/instance-tracking.test.ts +3 -0
  19. package/src/runtime/core/modules/mcp.test.ts +97 -0
  20. package/src/runtime/core/modules/mcp.ts +66 -0
  21. package/src/runtime/core/modules/pages.test.ts +3 -0
  22. package/src/runtime/core/modules/runtime.gen.ts +1 -1
  23. package/src/runtime/core/modules/surfaces.test.ts +6 -0
  24. package/src/runtime/core/modules/triggers.test.ts +3 -0
  25. package/src/runtime/core/modules/workers.test.ts +3 -0
  26. package/src/runtime/core/runtime.extensions.test.ts +9 -0
  27. package/src/runtime/core/runtime.test.ts +9 -0
  28. package/src/runtime/engine/nanosdk.test.ts +12 -0
  29. package/src/runtime/engine/nanosdk.ts +194 -1
  30. package/src/runtime/index.ts +4 -0
@@ -198,6 +198,10 @@ export type UserTaskState = "CREATED" | "COMPLETED" | "CANCELED" | "FAILED";
198
198
  export interface ProcessInstanceSnapshot {
199
199
  readonly processInstanceKey: string;
200
200
  readonly state: ProcessInstanceState;
201
+ /** The key of the deployed process definition this instance runs — the handle a caller feeds to
202
+ * {@link EngineClient.getProcessDefinitionXml} to fetch the deployed BPMN (the instance →
203
+ * `processDefinitionKey` → deployed XML path, no repo checkout). Absent when the engine omits it. */
204
+ readonly processDefinitionKey?: string;
201
205
  }
202
206
  /**
203
207
  * A single open user task as {@link EngineClient.searchUserTasks} reports it. Carries the
@@ -425,6 +429,72 @@ export interface FormSchema {
425
429
  readonly version?: number;
426
430
  readonly schema: Record<string, unknown>;
427
431
  }
432
+ /**
433
+ * Selectors for a variable search: which process instance and/or scope to match, optionally
434
+ * narrowed to a single variable `name`. Used by {@link EngineClient.searchVariables}. An
435
+ * eventually consistent (zero-wait) read; an unset selector matches every variable in scope.
436
+ */
437
+ export interface VariableFilter {
438
+ processInstanceKey?: string;
439
+ scopeKey?: string;
440
+ name?: string;
441
+ }
442
+ /**
443
+ * A single process variable as {@link EngineClient.searchVariables} reports it — the read
444
+ * counterpart to the write-only {@link EngineClient.setVariables}. Mirrors the engine's v2
445
+ * `/variables/search` row: the variable's own `variableKey`, its `name`, its serialized JSON
446
+ * `value` (a string — an object/array value arrives JSON-encoded, so a caller `JSON.parse`s it),
447
+ * the `scopeKey` it is directly defined in (a process-instance key for a process-level variable,
448
+ * an element-instance key for a local one) and the owning `processInstanceKey`. `isTruncated` is
449
+ * `true` when the engine clipped a large `value` for the read model — the caller then knows the
450
+ * `value` is a preview, not the whole payload. This is the only way to read a parked token's
451
+ * business payload (`prKey`, `round`, an escalation `question`/`answer`, `resolution`,
452
+ * `directive`); the ADR 0065 projections carry lifecycle/position, not the payload.
453
+ */
454
+ export interface VariableSummary {
455
+ readonly variableKey: string;
456
+ readonly name: string;
457
+ readonly value: string;
458
+ readonly scopeKey: string;
459
+ readonly processInstanceKey: string;
460
+ readonly isTruncated: boolean;
461
+ }
462
+ /**
463
+ * Selectors for a job search: which process instance, lifecycle `state`, job `type`, `elementId`,
464
+ * and/or leasing `worker` to match. Used by {@link EngineClient.searchJobs}. `state` is kept a
465
+ * transport-agnostic bare string (the engine's job-state enum is broad and forward-looking — e.g.
466
+ * `CREATED`/`COMPLETED`/`FAILED`/`TIMED_OUT`), so a new engine state flows through without having
467
+ * to be enumerated here. An eventually consistent (zero-wait) read; an unset selector matches
468
+ * every job.
469
+ */
470
+ export interface JobFilter {
471
+ processInstanceKey?: string;
472
+ state?: string;
473
+ type?: string;
474
+ elementId?: string;
475
+ worker?: string;
476
+ }
477
+ /**
478
+ * A single job as {@link EngineClient.searchJobs} reports it — mirrors the engine's v2
479
+ * `/jobs/search` row. Carries the `jobKey` a caller passes to
480
+ * {@link EngineClient.updateJobRetries} (the only source of a `jobKey` for a job NOT already
481
+ * behind a `jobNoRetries` incident, which {@link searchIncidents} surfaces), the job `type` and
482
+ * lifecycle `state` (a bare string — see {@link JobFilter}), the owning `processInstanceKey`, and
483
+ * the best-effort diagnostics `worker`/`retries`/`elementId`/`deadline`. The `worker` field is the
484
+ * "is it actually stuck" signal: a `CREATED` job WITH a `worker` set has been leased by an agent,
485
+ * while one with NONE set is merely queued — so `worker` is omitted (absent) when unset. `retries`,
486
+ * `elementId`, and `deadline` are likewise present when the engine reports them, omitted otherwise.
487
+ */
488
+ export interface JobSummary {
489
+ readonly jobKey: string;
490
+ readonly type: string;
491
+ readonly state: string;
492
+ readonly processInstanceKey: string;
493
+ readonly worker?: string;
494
+ readonly retries?: number;
495
+ readonly elementId?: string;
496
+ readonly deadline?: string;
497
+ }
428
498
  /**
429
499
  * EngineClient is the seam onto a Nano engine. The SDK/REST-backed adapter implements
430
500
  * it against a live engine; tests implement it in-memory. Core modules depend only on this.
@@ -560,6 +630,40 @@ export interface EngineClient {
560
630
  * they answer "how far has this instance progressed", this answers "why is it stuck".
561
631
  */
562
632
  searchIncidents(filter?: IncidentFilter): Promise<IncidentSummary[]>;
633
+ /**
634
+ * Search process variables by owning process instance and/or scope, optionally narrowed to a
635
+ * single `name` (`POST /v2/variables/search`). The read counterpart to the write-only
636
+ * {@link setVariables}: it is the only way to see a parked token's business payload (`prKey`,
637
+ * `round`, an escalation `question`/`answer`, `resolution`, `directive`) — the ADR 0065
638
+ * projections carry lifecycle/position, not the payload. An eventually consistent (zero-wait)
639
+ * read; each {@link VariableSummary} carries the variable's serialized JSON `value` (a string)
640
+ * and an `isTruncated` flag set when the engine clipped a large value for the read model.
641
+ */
642
+ searchVariables(filter?: VariableFilter): Promise<VariableSummary[]>;
643
+ /**
644
+ * Search jobs by owning process instance, lifecycle `state`, job `type`, `elementId`, and/or
645
+ * leasing `worker` (`POST /v2/jobs/search`). This is the "is it actually stuck" read: a
646
+ * `CREATED` job WITH a `worker` set has been leased by an agent, while one with NONE set is
647
+ * merely queued. It is also the on-tool source of a `jobKey` for {@link updateJobRetries} when
648
+ * the job is NOT behind an incident — {@link searchIncidents} only surfaces a `jobKey` for a
649
+ * job that already faulted (a `jobNoRetries` incident), so retrying an un-incidented job would
650
+ * otherwise be impossible without a direct engine-API call. An eventually consistent (zero-wait)
651
+ * read; each {@link JobSummary} carries the `jobKey` plus best-effort `worker`/`retries`/
652
+ * `elementId`/`deadline` diagnostics.
653
+ */
654
+ searchJobs(filter?: JobFilter): Promise<JobSummary[]>;
655
+ /**
656
+ * Fetch the deployed BPMN XML of a process definition by its `processDefinitionKey`
657
+ * (`GET /v2/process-definitions/{processDefinitionKey}/xml`), or `null` when the key is blank,
658
+ * unknown (a 404), or the definition carries no XML. This is the *deployed* model — the routing
659
+ * source of truth, including its FEEL gateway conditions — so an agent can reason about WHY an
660
+ * instance routed where it did without checking out `resources/processes/*.bpmn` from the app
661
+ * repo. An instance already carries its `processDefinitionKey` (via {@link searchProcessInstances}),
662
+ * so the path is instance → `processDefinitionKey` → deployed XML. A read is treated as absence
663
+ * (`null`) rather than propagating (mirroring {@link getForm}/{@link getElementInstance}), so a
664
+ * caller distinguishes "have it" from "don't" without a try/catch. A zero-wait read.
665
+ */
666
+ getProcessDefinitionXml(processDefinitionKey: string): Promise<string | null>;
563
667
  /**
564
668
  * Resolve an open incident by key (`POST /v2/incidents/{incidentKey}/resolution`), unblocking
565
669
  * the parked token: a job incident returns the job to the activatable pool (so it must have
@@ -614,4 +718,4 @@ export interface EngineClient {
614
718
  * and `getForm` before it) that a purely structural `implements EngineClient` check no
615
719
  * longer catches once the fake is published/compiled against an older `@nanobpm/urban`.
616
720
  */
617
- 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"];
721
+ export declare const ENGINE_CLIENT_METHODS: readonly ["deployResources", "createInstance", "cancelInstance", "publishMessage", "searchUserTasks", "openUserTasks", "getForm", "completeUserTask", "searchProcessInstances", "searchElementInstances", "searchElementInstanceWaitStates", "getElementInstance", "searchIncidents", "searchVariables", "searchJobs", "getProcessDefinitionXml", "resolveIncident", "updateJobRetries", "setVariables", "registerWorker", "close"];
@@ -118,6 +118,9 @@ export const ENGINE_CLIENT_METHODS = [
118
118
  "searchElementInstanceWaitStates",
119
119
  "getElementInstance",
120
120
  "searchIncidents",
121
+ "searchVariables",
122
+ "searchJobs",
123
+ "getProcessDefinitionXml",
121
124
  "resolveIncident",
122
125
  "updateJobRetries",
123
126
  "setVariables",
@@ -536,6 +536,74 @@ function buildDebugTools(app) {
536
536
  return app.engine.searchIncidents(filter);
537
537
  },
538
538
  },
539
+ {
540
+ name: `${DEBUG_PREFIX}search_variables`,
541
+ description: "Engine truth: search process variables by process instance and/or scope, optionally by name — the read counterpart to set_variables. Reads only, eventually consistent. This is the only way to see a parked token's business payload (prKey, round, an escalation question/answer, resolution, directive); the projections carry lifecycle/position, not the payload. Each result carries the variableKey/name/serialized-JSON value/scopeKey/processInstanceKey/isTruncated.",
542
+ inputSchema: {
543
+ type: "object",
544
+ properties: {
545
+ processInstanceKey: OPTIONAL_STRING,
546
+ scopeKey: OPTIONAL_STRING,
547
+ name: OPTIONAL_STRING,
548
+ },
549
+ },
550
+ run: (args) => {
551
+ const filter = {};
552
+ const pik = readPresentString(args, "processInstanceKey");
553
+ if (pik)
554
+ filter.processInstanceKey = pik;
555
+ const scopeKey = readPresentString(args, "scopeKey");
556
+ if (scopeKey)
557
+ filter.scopeKey = scopeKey;
558
+ const name = readPresentString(args, "name");
559
+ if (name)
560
+ filter.name = name;
561
+ return app.engine.searchVariables(filter);
562
+ },
563
+ },
564
+ {
565
+ name: `${DEBUG_PREFIX}search_jobs`,
566
+ description: "Engine truth: search jobs by process instance, state, and/or type/elementId/worker. Reads only, eventually consistent. The 'is it actually stuck?' test: a CREATED job WITH a worker set has been leased by an agent, one with NONE set is merely queued. This is also the on-tool source of a jobKey for retry_job (the only other source is a jobNoRetries incident via search_incidents). Each result carries jobKey/type/state/processInstanceKey and the best-effort worker/retries/elementId/deadline.",
567
+ inputSchema: {
568
+ type: "object",
569
+ properties: {
570
+ processInstanceKey: OPTIONAL_STRING,
571
+ state: OPTIONAL_STRING,
572
+ type: OPTIONAL_STRING,
573
+ elementId: OPTIONAL_STRING,
574
+ worker: OPTIONAL_STRING,
575
+ },
576
+ },
577
+ run: (args) => {
578
+ const filter = {};
579
+ const pik = readPresentString(args, "processInstanceKey");
580
+ if (pik)
581
+ filter.processInstanceKey = pik;
582
+ const state = readPresentString(args, "state");
583
+ if (state)
584
+ filter.state = state;
585
+ const type = readPresentString(args, "type");
586
+ if (type)
587
+ filter.type = type;
588
+ const elementId = readPresentString(args, "elementId");
589
+ if (elementId)
590
+ filter.elementId = elementId;
591
+ const worker = readPresentString(args, "worker");
592
+ if (worker)
593
+ filter.worker = worker;
594
+ return app.engine.searchJobs(filter);
595
+ },
596
+ },
597
+ {
598
+ name: `${DEBUG_PREFIX}get_process_definition_xml`,
599
+ description: "Engine truth: return the deployed BPMN XML of a process definition by key (the routing source of truth, with its FEEL gateway conditions) — reason about WHY an instance routed where it did without a repo checkout. Reads only; null when the key is unknown. An instance carries its processDefinitionKey via search_process_instances, so the path is instance → processDefinitionKey → deployed XML; pair with get_element_instance to locate the parked element.",
600
+ inputSchema: {
601
+ type: "object",
602
+ properties: { processDefinitionKey: OPTIONAL_STRING },
603
+ required: ["processDefinitionKey"],
604
+ },
605
+ run: (args) => app.engine.getProcessDefinitionXml(requireString(args, "processDefinitionKey")),
606
+ },
539
607
  {
540
608
  name: `${DEBUG_PREFIX}cancel_instance`,
541
609
  description: "MUTATING: cancel a running process instance. Requires the shared-secret guard.",