@nanobpm/urban 0.92.0 → 0.92.1
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.
|
@@ -41,7 +41,6 @@ import { resolveBindMode } from "../manifest.js";
|
|
|
41
41
|
import { json, makeRouter } from "../router.js";
|
|
42
42
|
import { API_BASE, readApiBinding } from "./api.js";
|
|
43
43
|
import { resolveAppPath } from "./datasource.js";
|
|
44
|
-
import { InstanceStateStore } from "./instance-state-store.js";
|
|
45
44
|
import { OpenUserTasksStore } from "./open-user-tasks-store.js";
|
|
46
45
|
/** The mount path of the MCP endpoint — a sibling of `/app/api` / `/app/agent`, mounted
|
|
47
46
|
* unconditionally so an MCP client always has a stable address to connect to. */
|
|
@@ -682,18 +681,19 @@ function buildDebugTools(app) {
|
|
|
682
681
|
},
|
|
683
682
|
{
|
|
684
683
|
name: `${DEBUG_PREFIX}instance_state`,
|
|
685
|
-
description: "
|
|
684
|
+
description: "Engine truth: the live lifecycle state of a process instance (ACTIVE/COMPLETED/TERMINATED) via searchProcessInstances, plus `waitingOnHuman` from its open user tasks. `null` means the engine has no such instance (not merely 'not yet terminal').",
|
|
686
685
|
inputSchema: {
|
|
687
686
|
type: "object",
|
|
688
687
|
properties: { processInstanceKey: OPTIONAL_STRING },
|
|
689
688
|
required: ["processInstanceKey"],
|
|
690
689
|
},
|
|
691
|
-
run: (args) => {
|
|
690
|
+
run: async (args) => {
|
|
692
691
|
const key = requireString(args, "processInstanceKey");
|
|
693
|
-
const
|
|
694
|
-
if (!
|
|
695
|
-
return
|
|
696
|
-
|
|
692
|
+
const [snapshot] = await app.engine.searchProcessInstances({ processInstanceKeys: [key] });
|
|
693
|
+
if (!snapshot)
|
|
694
|
+
return null;
|
|
695
|
+
const openTasks = await app.engine.openUserTasks({ processInstanceKey: key });
|
|
696
|
+
return { processInstanceKey: key, state: snapshot.state, waitingOnHuman: openTasks.length > 0 };
|
|
697
697
|
},
|
|
698
698
|
},
|
|
699
699
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/urban",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.1",
|
|
4
4
|
"description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,6 @@ import { makeRouter } from "../router.ts";
|
|
|
29
29
|
import { mountApi } from "./api.ts";
|
|
30
30
|
import { makeGateway } from "./gateway.ts";
|
|
31
31
|
import { DataLayer, type ProvisionedSource } from "./datasource.ts";
|
|
32
|
-
import { InstanceStateStore } from "./instance-state-store.ts";
|
|
33
32
|
import { OpenUserTasksStore } from "./open-user-tasks-store.ts";
|
|
34
33
|
import { evictExcessSessions, isLoopbackRequest, missingRequiredArgs, mountMcp, newSessionId, readHeader, readMcpConfig } from "./mcp.ts";
|
|
35
34
|
import { collectMcpToolProjection, diffMcpToolProjection, parseSpec } from "../../../openapi/spec.ts";
|
|
@@ -769,13 +768,11 @@ test("the reserved-namespace drop warns once, not on every tools/list and tools/
|
|
|
769
768
|
assert.equal(drops.length, 1, "the reserved-namespace drop must warn exactly once across many requests");
|
|
770
769
|
});
|
|
771
770
|
|
|
772
|
-
test("
|
|
771
|
+
test("urban_debug_open_user_tasks reads the ADR 0065 canonical projection", async () => {
|
|
773
772
|
const dir = await mkdtemp(join(tmpdir(), "urban-mcp-proj-"));
|
|
774
773
|
const host = createNodeHost({ cwd: dir, log: () => {} });
|
|
775
774
|
const db: SqliteDb = host.openSqlite(join(dir, "test.db"));
|
|
776
775
|
try {
|
|
777
|
-
new InstanceStateStore(db, { clock: { now: () => 0 } }).ensureSchema();
|
|
778
|
-
new InstanceStateStore(db, { clock: { now: () => 0 } }).recordState("pi-7", "ACTIVE");
|
|
779
776
|
const openStore = new OpenUserTasksStore(db, { clock: { now: () => 0 } });
|
|
780
777
|
openStore.ensureSchema();
|
|
781
778
|
openStore.recordOpenTask("pi-7", { userTaskKey: "ut-1", elementId: "Task_A" });
|
|
@@ -792,13 +789,6 @@ test("the projection debug tools read the ADR 0065 canonical stores", async () =
|
|
|
792
789
|
const { router } = buildHarness({ data });
|
|
793
790
|
const session = await connect(router);
|
|
794
791
|
|
|
795
|
-
const stateCall = await rpc(router, session, "tools/call", {
|
|
796
|
-
name: "urban_debug_instance_state",
|
|
797
|
-
arguments: { processInstanceKey: "pi-7" },
|
|
798
|
-
});
|
|
799
|
-
const state = JSON.parse(toolContentText(stateCall.result));
|
|
800
|
-
assert.equal(Reflect.get(state, "state"), "ACTIVE");
|
|
801
|
-
|
|
802
792
|
const tasksCall = await rpc(router, session, "tools/call", {
|
|
803
793
|
name: "urban_debug_open_user_tasks",
|
|
804
794
|
arguments: { processInstanceKey: "pi-7" },
|
|
@@ -812,6 +802,66 @@ test("the projection debug tools read the ADR 0065 canonical stores", async () =
|
|
|
812
802
|
}
|
|
813
803
|
});
|
|
814
804
|
|
|
805
|
+
test("urban_debug_instance_state returns engine truth for a live (ACTIVE) instance", async () => {
|
|
806
|
+
const snapshot: ProcessInstanceSnapshot = { processInstanceKey: "pi-7", state: "ACTIVE" };
|
|
807
|
+
const engine = fakeEngine({
|
|
808
|
+
searchProcessInstances: async (filter) => {
|
|
809
|
+
assert.deepEqual(filter?.processInstanceKeys, ["pi-7"]);
|
|
810
|
+
return [snapshot];
|
|
811
|
+
},
|
|
812
|
+
openUserTasks: async () => [],
|
|
813
|
+
});
|
|
814
|
+
const { router } = buildHarness({ engine });
|
|
815
|
+
const session = await connect(router);
|
|
816
|
+
|
|
817
|
+
const call = await rpc(router, session, "tools/call", {
|
|
818
|
+
name: "urban_debug_instance_state",
|
|
819
|
+
arguments: { processInstanceKey: "pi-7" },
|
|
820
|
+
});
|
|
821
|
+
const state = JSON.parse(toolContentText(call.result));
|
|
822
|
+
assert.deepEqual(state, { processInstanceKey: "pi-7", state: "ACTIVE", waitingOnHuman: false });
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
test("urban_debug_instance_state reports waitingOnHuman from open user tasks", async () => {
|
|
826
|
+
const snapshot: ProcessInstanceSnapshot = { processInstanceKey: "pi-7", state: "ACTIVE" };
|
|
827
|
+
const engine = fakeEngine({
|
|
828
|
+
searchProcessInstances: async () => [snapshot],
|
|
829
|
+
openUserTasks: async (filter) => {
|
|
830
|
+
assert.equal(filter?.processInstanceKey, "pi-7");
|
|
831
|
+
return [{ userTaskKey: "ut-1", processInstanceKey: "pi-7" }];
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
const { router } = buildHarness({ engine });
|
|
835
|
+
const session = await connect(router);
|
|
836
|
+
|
|
837
|
+
const call = await rpc(router, session, "tools/call", {
|
|
838
|
+
name: "urban_debug_instance_state",
|
|
839
|
+
arguments: { processInstanceKey: "pi-7" },
|
|
840
|
+
});
|
|
841
|
+
const state = JSON.parse(toolContentText(call.result));
|
|
842
|
+
assert.deepEqual(state, { processInstanceKey: "pi-7", state: "ACTIVE", waitingOnHuman: true });
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
test("urban_debug_instance_state returns null when the engine has no such instance", async () => {
|
|
846
|
+
let openUserTasksCalled = false;
|
|
847
|
+
const engine = fakeEngine({
|
|
848
|
+
searchProcessInstances: async () => [],
|
|
849
|
+
openUserTasks: async () => {
|
|
850
|
+
openUserTasksCalled = true;
|
|
851
|
+
return [];
|
|
852
|
+
},
|
|
853
|
+
});
|
|
854
|
+
const { router } = buildHarness({ engine });
|
|
855
|
+
const session = await connect(router);
|
|
856
|
+
|
|
857
|
+
const call = await rpc(router, session, "tools/call", {
|
|
858
|
+
name: "urban_debug_instance_state",
|
|
859
|
+
arguments: { processInstanceKey: "nope" },
|
|
860
|
+
});
|
|
861
|
+
assert.equal(toolContentText(call.result), "null");
|
|
862
|
+
assert.equal(openUserTasksCalled, false, "must not probe user tasks for an instance the engine doesn't know");
|
|
863
|
+
});
|
|
864
|
+
|
|
815
865
|
// ---- mutation guard (Slice 3) -----------------------------------------------------------------
|
|
816
866
|
|
|
817
867
|
// A repeated credential header must read the SAME under the MCP guard as under OpenAPI route
|
|
@@ -82,7 +82,6 @@ import { resolveBindMode } from "../manifest.ts";
|
|
|
82
82
|
import { json, makeRouter, type Route } from "../router.ts";
|
|
83
83
|
import { API_BASE, readApiBinding } from "./api.ts";
|
|
84
84
|
import { resolveAppPath } from "./datasource.ts";
|
|
85
|
-
import { InstanceStateStore } from "./instance-state-store.ts";
|
|
86
85
|
import { OpenUserTasksStore } from "./open-user-tasks-store.ts";
|
|
87
86
|
|
|
88
87
|
/** The mount path of the MCP endpoint — a sibling of `/app/api` / `/app/agent`, mounted
|
|
@@ -785,17 +784,19 @@ function buildDebugTools(app: AppApi): DebugTool[] {
|
|
|
785
784
|
},
|
|
786
785
|
{
|
|
787
786
|
name: `${DEBUG_PREFIX}instance_state`,
|
|
788
|
-
description:
|
|
787
|
+
description:
|
|
788
|
+
"Engine truth: the live lifecycle state of a process instance (ACTIVE/COMPLETED/TERMINATED) via searchProcessInstances, plus `waitingOnHuman` from its open user tasks. `null` means the engine has no such instance (not merely 'not yet terminal').",
|
|
789
789
|
inputSchema: {
|
|
790
790
|
type: "object",
|
|
791
791
|
properties: { processInstanceKey: OPTIONAL_STRING },
|
|
792
792
|
required: ["processInstanceKey"],
|
|
793
793
|
},
|
|
794
|
-
run: (args) => {
|
|
794
|
+
run: async (args) => {
|
|
795
795
|
const key = requireString(args, "processInstanceKey");
|
|
796
|
-
const
|
|
797
|
-
if (!
|
|
798
|
-
|
|
796
|
+
const [snapshot] = await app.engine.searchProcessInstances({ processInstanceKeys: [key] });
|
|
797
|
+
if (!snapshot) return null;
|
|
798
|
+
const openTasks = await app.engine.openUserTasks({ processInstanceKey: key });
|
|
799
|
+
return { processInstanceKey: key, state: snapshot.state, waitingOnHuman: openTasks.length > 0 };
|
|
799
800
|
},
|
|
800
801
|
},
|
|
801
802
|
{
|