@nanobpm/urban 0.92.1 → 0.93.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/runtime/core/host.d.ts +124 -1
- package/dist/runtime/core/host.js +3 -0
- package/dist/runtime/engine/nanosdk.d.ts +28 -1
- package/dist/runtime/engine/nanosdk.js +262 -0
- package/dist/runtime/index.d.ts +2 -1
- package/package.json +2 -1
- package/src/runtime/core/host.ts +146 -0
- package/src/runtime/core/modules/actions.test.ts +3 -0
- package/src/runtime/core/modules/agent.test.ts +3 -0
- package/src/runtime/core/modules/api.test.ts +3 -0
- package/src/runtime/core/modules/cancel.test.ts +3 -0
- package/src/runtime/core/modules/connectors.test.ts +3 -0
- package/src/runtime/core/modules/datasource.migrations.test.ts +3 -0
- package/src/runtime/core/modules/instance-tracking.test.ts +3 -0
- package/src/runtime/core/modules/mcp.test.ts +6 -0
- package/src/runtime/core/modules/pages.test.ts +3 -0
- package/src/runtime/core/modules/surfaces.test.ts +6 -0
- package/src/runtime/core/modules/triggers.test.ts +3 -0
- package/src/runtime/core/modules/workers.test.ts +3 -0
- package/src/runtime/core/runtime.extensions.test.ts +9 -0
- package/src/runtime/core/runtime.test.ts +9 -0
- package/src/runtime/engine/nanosdk.test.ts +209 -0
- package/src/runtime/engine/nanosdk.ts +305 -0
- package/src/runtime/index.ts +16 -0
package/src/runtime/core/host.ts
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
// written against these interfaces and MUST NOT import `node:*` or reference `Deno`.
|
|
4
4
|
// The adapters/ directory supplies concrete implementations.
|
|
5
5
|
|
|
6
|
+
// The engine-native AgentHistory read shapes ({@link AgentHistoryRecord}) reuse the
|
|
7
|
+
// Camunda `AgentHistoryRecordValue`-parity types the transcript store already models
|
|
8
|
+
// (roles, content blocks, per-turn metrics, tool calls) rather than forking a second
|
|
9
|
+
// projection of the same conversation grammar (No Drift Surfaces). This is a type-only
|
|
10
|
+
// import — fully erased at runtime — so `core/` stays free of any concrete host/engine
|
|
11
|
+
// module load, exactly as the rule above requires.
|
|
12
|
+
import type {
|
|
13
|
+
TranscriptContentBlock,
|
|
14
|
+
TranscriptToolCall,
|
|
15
|
+
TranscriptTurnMetrics,
|
|
16
|
+
TranscriptTurnRole,
|
|
17
|
+
} from "@nanobpm/agentic/transcript";
|
|
18
|
+
|
|
6
19
|
/** A minimal HTTP request as seen by a mounted surface/trigger handler. */
|
|
7
20
|
export interface HttpRequest {
|
|
8
21
|
method: string;
|
|
@@ -603,6 +616,100 @@ export interface JobSummary {
|
|
|
603
616
|
readonly deadline?: string;
|
|
604
617
|
}
|
|
605
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Aggregated metrics for an agent instance across all of its model calls — the
|
|
621
|
+
* `AgentInstanceMetrics` the engine rolls up on the instance itself (distinct from the
|
|
622
|
+
* per-turn {@link TranscriptTurnMetrics} carried on an {@link AgentHistoryRecord}). Total
|
|
623
|
+
* `inputTokens`/`outputTokens` consumed, and the count of `modelCalls`/`toolCalls` made.
|
|
624
|
+
*/
|
|
625
|
+
export interface AgentInstanceMetrics {
|
|
626
|
+
readonly inputTokens: number;
|
|
627
|
+
readonly outputTokens: number;
|
|
628
|
+
readonly modelCalls: number;
|
|
629
|
+
readonly toolCalls: number;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* A single engine-native agent instance as {@link EngineClient.searchAgentInstances} /
|
|
634
|
+
* {@link EngineClient.getAgentInstance} report it — the projection of the engine's
|
|
635
|
+
* `AgentInstanceResult`. Carries the `agentInstanceKey` a caller passes to
|
|
636
|
+
* {@link EngineClient.searchAgentInstanceHistory}, the lifecycle `status` (a bare string —
|
|
637
|
+
* the engine's `AgentInstanceStatusEnum` is broad: `INITIALIZING`/`THINKING`/`TOOL_CALLING`/
|
|
638
|
+
* `IDLE`/`COMPLETED`/…), the owning `processInstanceKey`, and best-effort diagnostics
|
|
639
|
+
* (`elementId`, the owning `elementInstanceKeys`, the process-definition linkage, aggregated
|
|
640
|
+
* `metrics`, and the creation/update/completion timestamps). Optional fields are omitted when
|
|
641
|
+
* the engine does not report them.
|
|
642
|
+
*/
|
|
643
|
+
export interface AgentInstanceSummary {
|
|
644
|
+
readonly agentInstanceKey: string;
|
|
645
|
+
readonly status: string;
|
|
646
|
+
readonly processInstanceKey: string;
|
|
647
|
+
readonly elementId?: string;
|
|
648
|
+
readonly elementInstanceKeys?: readonly string[];
|
|
649
|
+
readonly rootProcessInstanceKey?: string;
|
|
650
|
+
readonly processDefinitionKey?: string;
|
|
651
|
+
readonly processDefinitionId?: string;
|
|
652
|
+
readonly metrics?: AgentInstanceMetrics;
|
|
653
|
+
readonly creationDate?: string;
|
|
654
|
+
readonly lastUpdatedDate?: string;
|
|
655
|
+
readonly completionDate?: string;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* A single agent-instance history item as {@link EngineClient.searchAgentInstanceHistory}
|
|
660
|
+
* reports it — the projection of the engine's `AgentInstanceHistoryItemResult`, i.e. one
|
|
661
|
+
* Camunda `AgentHistoryRecordValue`. Its conversation grammar (`role`, `content` blocks,
|
|
662
|
+
* `toolCalls`, per-turn `metrics`) reuses the `@nanobpm/agentic/transcript` parity types the
|
|
663
|
+
* transcript store already models, so the engine-read seam and the transcript store project
|
|
664
|
+
* the same shape (No Drift Surfaces). `historyItemKey` is the stable, creation-ordered
|
|
665
|
+
* identity; `loopIteration` is the agent-loop counter (one LLM call + its tool dispatches +
|
|
666
|
+
* their results share an iteration); `commitStatus` is the engine's `COMMITTED`/`PENDING`/
|
|
667
|
+
* `DISCARDED` flag (a bare string). `metrics` is present only when the engine recorded per-call
|
|
668
|
+
* metrics for the item; `elementInstanceKey`/`jobKey`/`producedAt` are best-effort diagnostics,
|
|
669
|
+
* omitted when unreported.
|
|
670
|
+
*/
|
|
671
|
+
export interface AgentHistoryRecord {
|
|
672
|
+
readonly historyItemKey: string;
|
|
673
|
+
readonly agentInstanceKey: string;
|
|
674
|
+
readonly loopIteration: number;
|
|
675
|
+
readonly role: TranscriptTurnRole;
|
|
676
|
+
readonly content: readonly TranscriptContentBlock[];
|
|
677
|
+
readonly toolCalls: readonly TranscriptToolCall[];
|
|
678
|
+
readonly metrics?: TranscriptTurnMetrics;
|
|
679
|
+
readonly commitStatus: string;
|
|
680
|
+
readonly elementInstanceKey?: string;
|
|
681
|
+
readonly jobKey?: string;
|
|
682
|
+
readonly producedAt?: string;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** Selectors for {@link EngineClient.searchAgentInstances}. Every field is optional; an
|
|
686
|
+
* omitted/blank selector is not applied (No Drift Surfaces — the same presence rule the key
|
|
687
|
+
* selectors elsewhere in this seam use). */
|
|
688
|
+
export interface AgentInstanceFilter {
|
|
689
|
+
/** Only agent instances owned by this process instance. */
|
|
690
|
+
readonly processInstanceKey?: string;
|
|
691
|
+
/** Only agent instances belonging to this *root* process-instance hierarchy. */
|
|
692
|
+
readonly rootProcessInstanceKey?: string;
|
|
693
|
+
/** Only agent instances in this lifecycle status (the engine's `AgentInstanceStatusEnum`). */
|
|
694
|
+
readonly status?: string;
|
|
695
|
+
/** Only agent instances owned by this BPMN element (the AI-agent task / ad-hoc sub-process). */
|
|
696
|
+
readonly elementId?: string;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** Selectors for {@link EngineClient.searchAgentInstanceHistory} (the `agentInstanceKey` is a
|
|
700
|
+
* required positional argument, not a filter field). Every field is optional. Note the engine
|
|
701
|
+
* defaults an unfiltered history search to `COMMITTED` items only. */
|
|
702
|
+
export interface AgentHistoryFilter {
|
|
703
|
+
/** Only history items with this conversation role. Note that `TranscriptTurnRole` is the wider
|
|
704
|
+
* transcript union: only the engine-side subset `"USER" | "ASSISTANT" | "TOOL_RESULT"` is
|
|
705
|
+
* applied as a filter — `"CONFIGURATION"`/`"UNSPECIFIED"` are ignored (not forwarded). */
|
|
706
|
+
readonly role?: TranscriptTurnRole;
|
|
707
|
+
/** Only history items produced in this agent-loop iteration. */
|
|
708
|
+
readonly loopIteration?: number;
|
|
709
|
+
/** Only history items produced under this element instance. */
|
|
710
|
+
readonly elementInstanceKey?: string;
|
|
711
|
+
}
|
|
712
|
+
|
|
606
713
|
/**
|
|
607
714
|
* EngineClient is the seam onto a Nano engine. The SDK/REST-backed adapter implements
|
|
608
715
|
* it against a live engine; tests implement it in-memory. Core modules depend only on this.
|
|
@@ -772,6 +879,42 @@ export interface EngineClient {
|
|
|
772
879
|
* caller distinguishes "have it" from "don't" without a try/catch. A zero-wait read.
|
|
773
880
|
*/
|
|
774
881
|
getProcessDefinitionXml(processDefinitionKey: string): Promise<string | null>;
|
|
882
|
+
/**
|
|
883
|
+
* Search engine-native agent instances (`POST /agent-instances/search`), optionally narrowed
|
|
884
|
+
* by owning `processInstanceKey`/`rootProcessInstanceKey`, lifecycle `status`, or `elementId`.
|
|
885
|
+
* The read entry point onto the engine's AgentInstance/AgentHistory model: an
|
|
886
|
+
* {@link AgentInstanceSummary} carries the `agentInstanceKey` a caller passes to
|
|
887
|
+
* {@link searchAgentInstanceHistory} to read that instance's conversation. An eventually
|
|
888
|
+
* consistent (zero-wait) read; each malformed row (no `agentInstanceKey`/`processInstanceKey`)
|
|
889
|
+
* is dropped rather than surfaced.
|
|
890
|
+
*/
|
|
891
|
+
searchAgentInstances(filter?: AgentInstanceFilter): Promise<AgentInstanceSummary[]>;
|
|
892
|
+
/**
|
|
893
|
+
* Search one agent instance's conversation history
|
|
894
|
+
* (`POST /agent-instances/{agentInstanceKey}/history/search`) — the ordered
|
|
895
|
+
* {@link AgentHistoryRecord}s (turns) the engine retains for `agentInstanceKey`, optionally
|
|
896
|
+
* narrowed by `role`/`loopIteration`/`elementInstanceKey`. This is the production read path
|
|
897
|
+
* onto engine AgentHistory the historical-transcript + metrics consumer builds on
|
|
898
|
+
* (nanobpm/nano-workforce#747). Each record's conversation grammar reuses the
|
|
899
|
+
* `@nanobpm/agentic/transcript` parity shapes (roles, content blocks, per-turn metrics, tool
|
|
900
|
+
* calls). Note the engine defaults an unfiltered search to `COMMITTED` items only. A blank
|
|
901
|
+
* `agentInstanceKey` addresses no instance, so it returns the empty list rather than issuing a
|
|
902
|
+
* request with an empty path segment. An eventually consistent (zero-wait) read; malformed rows
|
|
903
|
+
* are dropped.
|
|
904
|
+
*/
|
|
905
|
+
searchAgentInstanceHistory(
|
|
906
|
+
agentInstanceKey: string,
|
|
907
|
+
filter?: AgentHistoryFilter,
|
|
908
|
+
): Promise<AgentHistoryRecord[]>;
|
|
909
|
+
/**
|
|
910
|
+
* Fetch a single agent instance by its key (`GET /agent-instances/{agentInstanceKey}`), or
|
|
911
|
+
* `null` when it cannot be resolved — the key is blank, no such agent instance exists (a 404),
|
|
912
|
+
* or the fetch otherwise fails. A read is treated as absence rather than propagating (mirroring
|
|
913
|
+
* {@link getForm}/{@link getElementInstance}/{@link getProcessDefinitionXml}), so a caller
|
|
914
|
+
* distinguishes "have it" from "don't" without a try/catch. A zero-wait read returning the same
|
|
915
|
+
* {@link AgentInstanceSummary} shape as {@link searchAgentInstances}.
|
|
916
|
+
*/
|
|
917
|
+
getAgentInstance(agentInstanceKey: string): Promise<AgentInstanceSummary | null>;
|
|
775
918
|
/**
|
|
776
919
|
* Resolve an open incident by key (`POST /v2/incidents/{incidentKey}/resolution`), unblocking
|
|
777
920
|
* the parked token: a job incident returns the job to the activatable pool (so it must have
|
|
@@ -839,6 +982,9 @@ export const ENGINE_CLIENT_METHODS = [
|
|
|
839
982
|
"searchVariables",
|
|
840
983
|
"searchJobs",
|
|
841
984
|
"getProcessDefinitionXml",
|
|
985
|
+
"searchAgentInstances",
|
|
986
|
+
"searchAgentInstanceHistory",
|
|
987
|
+
"getAgentInstance",
|
|
842
988
|
"resolveIncident",
|
|
843
989
|
"updateJobRetries",
|
|
844
990
|
"setVariables",
|
|
@@ -40,6 +40,9 @@ const engine: EngineClient = {
|
|
|
40
40
|
searchVariables: async () => [],
|
|
41
41
|
searchJobs: async () => [],
|
|
42
42
|
getProcessDefinitionXml: async () => null,
|
|
43
|
+
searchAgentInstances: async () => [],
|
|
44
|
+
searchAgentInstanceHistory: async () => [],
|
|
45
|
+
getAgentInstance: async () => null,
|
|
43
46
|
resolveIncident: async () => {},
|
|
44
47
|
updateJobRetries: async () => {},
|
|
45
48
|
setVariables: async () => {},
|
|
@@ -25,6 +25,9 @@ const fakeEngine: EngineClient = {
|
|
|
25
25
|
searchVariables: async () => [],
|
|
26
26
|
searchJobs: async () => [],
|
|
27
27
|
getProcessDefinitionXml: async () => null,
|
|
28
|
+
searchAgentInstances: async () => [],
|
|
29
|
+
searchAgentInstanceHistory: async () => [],
|
|
30
|
+
getAgentInstance: async () => null,
|
|
28
31
|
resolveIncident: async () => {},
|
|
29
32
|
updateJobRetries: async () => {},
|
|
30
33
|
setVariables: async () => {},
|
|
@@ -38,6 +38,9 @@ const engine: EngineClient = {
|
|
|
38
38
|
searchVariables: async () => [],
|
|
39
39
|
searchJobs: async () => [],
|
|
40
40
|
getProcessDefinitionXml: async () => null,
|
|
41
|
+
searchAgentInstances: async () => [],
|
|
42
|
+
searchAgentInstanceHistory: async () => [],
|
|
43
|
+
getAgentInstance: async () => null,
|
|
41
44
|
resolveIncident: async () => {},
|
|
42
45
|
updateJobRetries: async () => {},
|
|
43
46
|
setVariables: async () => {},
|
|
@@ -92,6 +92,9 @@ function cancelEngine(opts: {
|
|
|
92
92
|
searchVariables: notUsed("searchVariables"),
|
|
93
93
|
searchJobs: notUsed("searchJobs"),
|
|
94
94
|
getProcessDefinitionXml: notUsed("getProcessDefinitionXml"),
|
|
95
|
+
searchAgentInstances: notUsed("searchAgentInstances"),
|
|
96
|
+
searchAgentInstanceHistory: notUsed("searchAgentInstanceHistory"),
|
|
97
|
+
getAgentInstance: notUsed("getAgentInstance"),
|
|
95
98
|
resolveIncident: notUsed("resolveIncident"),
|
|
96
99
|
updateJobRetries: notUsed("updateJobRetries"),
|
|
97
100
|
setVariables: notUsed("setVariables"),
|
|
@@ -28,6 +28,9 @@ class MiniEngine implements EngineClient {
|
|
|
28
28
|
async searchVariables(): Promise<never[]> { return []; }
|
|
29
29
|
async searchJobs(): Promise<never[]> { return []; }
|
|
30
30
|
async getProcessDefinitionXml(): Promise<null> { return null; }
|
|
31
|
+
async searchAgentInstances(): Promise<never[]> { return []; }
|
|
32
|
+
async searchAgentInstanceHistory(): Promise<never[]> { return []; }
|
|
33
|
+
async getAgentInstance(): Promise<null> { return null; }
|
|
31
34
|
async resolveIncident(): Promise<void> {}
|
|
32
35
|
async updateJobRetries(): Promise<void> {}
|
|
33
36
|
async setVariables(): Promise<void> {}
|
|
@@ -27,6 +27,9 @@ const noEngine: EngineClient = {
|
|
|
27
27
|
searchVariables: async () => [],
|
|
28
28
|
searchJobs: async () => [],
|
|
29
29
|
getProcessDefinitionXml: async () => null,
|
|
30
|
+
searchAgentInstances: async () => [],
|
|
31
|
+
searchAgentInstanceHistory: async () => [],
|
|
32
|
+
getAgentInstance: async () => null,
|
|
30
33
|
resolveIncident: async () => {},
|
|
31
34
|
updateJobRetries: async () => {},
|
|
32
35
|
setVariables: async () => {},
|
|
@@ -97,6 +97,9 @@ function fakeEngine(
|
|
|
97
97
|
searchVariables: notUsed("searchVariables"),
|
|
98
98
|
searchJobs: notUsed("searchJobs"),
|
|
99
99
|
getProcessDefinitionXml: notUsed("getProcessDefinitionXml"),
|
|
100
|
+
searchAgentInstances: notUsed("searchAgentInstances"),
|
|
101
|
+
searchAgentInstanceHistory: notUsed("searchAgentInstanceHistory"),
|
|
102
|
+
getAgentInstance: notUsed("getAgentInstance"),
|
|
100
103
|
resolveIncident: notUsed("resolveIncident"),
|
|
101
104
|
updateJobRetries: notUsed("updateJobRetries"),
|
|
102
105
|
setVariables: notUsed("setVariables"),
|
|
@@ -93,6 +93,9 @@ function fakeEngine(overrides: Partial<EngineClient> = {}): EngineClient {
|
|
|
93
93
|
searchVariables: async () => [],
|
|
94
94
|
searchJobs: async () => [],
|
|
95
95
|
getProcessDefinitionXml: async () => null,
|
|
96
|
+
searchAgentInstances: async () => [],
|
|
97
|
+
searchAgentInstanceHistory: async () => [],
|
|
98
|
+
getAgentInstance: async () => null,
|
|
96
99
|
resolveIncident: async () => {},
|
|
97
100
|
updateJobRetries: async () => {},
|
|
98
101
|
setVariables: async () => {},
|
|
@@ -646,6 +649,9 @@ test("urban_debug_search_variables / search_jobs / get_process_definition_xml ar
|
|
|
646
649
|
seenXmlKeys.push(key);
|
|
647
650
|
return key === "pd-1" ? "<bpmn:definitions/>" : null;
|
|
648
651
|
},
|
|
652
|
+
searchAgentInstances: async () => [],
|
|
653
|
+
searchAgentInstanceHistory: async () => [],
|
|
654
|
+
getAgentInstance: async () => null,
|
|
649
655
|
});
|
|
650
656
|
const { router } = buildHarness({ engine });
|
|
651
657
|
const session = await connect(router);
|
|
@@ -55,6 +55,9 @@ function fakeEngine(): { engine: EngineClient; calls: FakeEngineCalls } {
|
|
|
55
55
|
async searchVariables() { return []; },
|
|
56
56
|
async searchJobs() { return []; },
|
|
57
57
|
async getProcessDefinitionXml() { return null; },
|
|
58
|
+
async searchAgentInstances() { return []; },
|
|
59
|
+
async searchAgentInstanceHistory() { return []; },
|
|
60
|
+
async getAgentInstance() { return null; },
|
|
58
61
|
async resolveIncident() {},
|
|
59
62
|
async updateJobRetries() {},
|
|
60
63
|
async setVariables() {},
|
|
@@ -41,6 +41,9 @@ function ctxWith(surfaces: Record<string, unknown>): Parameters<typeof mountSurf
|
|
|
41
41
|
searchVariables: async () => [],
|
|
42
42
|
searchJobs: async () => [],
|
|
43
43
|
getProcessDefinitionXml: async () => null,
|
|
44
|
+
searchAgentInstances: async () => [],
|
|
45
|
+
searchAgentInstanceHistory: async () => [],
|
|
46
|
+
getAgentInstance: async () => null,
|
|
44
47
|
resolveIncident: async () => {},
|
|
45
48
|
updateJobRetries: async () => {},
|
|
46
49
|
setVariables: async () => {},
|
|
@@ -67,6 +70,9 @@ const fakeEngine: EngineClient = {
|
|
|
67
70
|
searchVariables: async () => [],
|
|
68
71
|
searchJobs: async () => [],
|
|
69
72
|
getProcessDefinitionXml: async () => null,
|
|
73
|
+
searchAgentInstances: async () => [],
|
|
74
|
+
searchAgentInstanceHistory: async () => [],
|
|
75
|
+
getAgentInstance: async () => null,
|
|
70
76
|
resolveIncident: async () => {},
|
|
71
77
|
updateJobRetries: async () => {},
|
|
72
78
|
setVariables: async () => {},
|
|
@@ -42,6 +42,9 @@ class MiniEngine implements EngineClient {
|
|
|
42
42
|
async searchVariables(): Promise<never[]> { return []; }
|
|
43
43
|
async searchJobs(): Promise<never[]> { return []; }
|
|
44
44
|
async getProcessDefinitionXml(): Promise<null> { return null; }
|
|
45
|
+
async searchAgentInstances(): Promise<never[]> { return []; }
|
|
46
|
+
async searchAgentInstanceHistory(): Promise<never[]> { return []; }
|
|
47
|
+
async getAgentInstance(): Promise<null> { return null; }
|
|
45
48
|
async resolveIncident(): Promise<void> {}
|
|
46
49
|
async updateJobRetries(): Promise<void> {}
|
|
47
50
|
async setVariables(): Promise<void> {}
|
|
@@ -39,6 +39,9 @@ class MiniEngine implements EngineClient {
|
|
|
39
39
|
async searchVariables(): Promise<never[]> { return []; }
|
|
40
40
|
async searchJobs(): Promise<never[]> { return []; }
|
|
41
41
|
async getProcessDefinitionXml(): Promise<null> { return null; }
|
|
42
|
+
async searchAgentInstances(): Promise<never[]> { return []; }
|
|
43
|
+
async searchAgentInstanceHistory(): Promise<never[]> { return []; }
|
|
44
|
+
async getAgentInstance(): Promise<null> { return null; }
|
|
42
45
|
async resolveIncident(): Promise<void> {}
|
|
43
46
|
async updateJobRetries(): Promise<void> {}
|
|
44
47
|
async setVariables(): Promise<void> {}
|
|
@@ -53,6 +53,15 @@ class NoopEngine implements EngineClient {
|
|
|
53
53
|
async getProcessDefinitionXml() {
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
|
+
async searchAgentInstances() {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
async searchAgentInstanceHistory() {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
async getAgentInstance() {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
56
65
|
async resolveIncident() {}
|
|
57
66
|
async updateJobRetries() {}
|
|
58
67
|
async setVariables() {}
|
|
@@ -74,6 +74,15 @@ class FakeEngine implements EngineClient {
|
|
|
74
74
|
async getProcessDefinitionXml() {
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
77
|
+
async searchAgentInstances() {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
async searchAgentInstanceHistory() {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
async getAgentInstance() {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
77
86
|
async resolveIncident() {}
|
|
78
87
|
async updateJobRetries() {}
|
|
79
88
|
async setVariables() {}
|
|
@@ -109,6 +109,18 @@ function fakeSdkClient(overrides: Partial<NanoSdkClient> = {}): NanoSdkClient &
|
|
|
109
109
|
calls.push("getProcessDefinitionXml");
|
|
110
110
|
return "";
|
|
111
111
|
},
|
|
112
|
+
async searchAgentInstances() {
|
|
113
|
+
calls.push("searchAgentInstances");
|
|
114
|
+
return { items: [] };
|
|
115
|
+
},
|
|
116
|
+
async searchAgentInstanceHistory() {
|
|
117
|
+
calls.push("searchAgentInstanceHistory");
|
|
118
|
+
return { items: [] };
|
|
119
|
+
},
|
|
120
|
+
async getAgentInstance() {
|
|
121
|
+
calls.push("getAgentInstance");
|
|
122
|
+
return {};
|
|
123
|
+
},
|
|
112
124
|
async resolveIncident(input) {
|
|
113
125
|
calls.push("resolveIncident");
|
|
114
126
|
return { ...input };
|
|
@@ -1408,3 +1420,200 @@ test("publishMessage threads lineage so message-started instances inherit it", a
|
|
|
1408
1420
|
}
|
|
1409
1421
|
assert.deepEqual(readLineage(client.msgVars), { rootRequestKey: "root-9", causedByInstanceKey: "pi-9" });
|
|
1410
1422
|
});
|
|
1423
|
+
|
|
1424
|
+
// ── Engine-native AgentInstance / AgentHistory read seam (nanobpm/nano-ide#563) ──
|
|
1425
|
+
// The read path onto engine AgentInstance/AgentHistory that unblocks the historical-transcript
|
|
1426
|
+
// consumer (nanobpm/nano-workforce#747). These pin the request-mapping + response-mapping
|
|
1427
|
+
// contract of the three `EngineClient` methods against the fake nano-sdk client, mirroring the
|
|
1428
|
+
// other search-seam tests above (so the same scenarios would hold against a live engine).
|
|
1429
|
+
|
|
1430
|
+
test("searchAgentInstances normalizes selectors, reads zero-wait, and maps rows", async () => {
|
|
1431
|
+
let seenInput: { filter?: Record<string, unknown> } | undefined;
|
|
1432
|
+
let seenConsistency: unknown;
|
|
1433
|
+
const client = fakeSdkClient({
|
|
1434
|
+
searchAgentInstances: async (input, consistency) => {
|
|
1435
|
+
seenInput = input;
|
|
1436
|
+
seenConsistency = consistency;
|
|
1437
|
+
return {
|
|
1438
|
+
items: [
|
|
1439
|
+
{
|
|
1440
|
+
agentInstanceKey: " ai-1 ", // padded → trimmed
|
|
1441
|
+
status: "THINKING",
|
|
1442
|
+
processInstanceKey: 10,
|
|
1443
|
+
elementId: "agent",
|
|
1444
|
+
rootProcessInstanceKey: 30,
|
|
1445
|
+
processDefinitionKey: "pd-1",
|
|
1446
|
+
processDefinitionId: "proc",
|
|
1447
|
+
elementInstanceKeys: ["ei-1", "", 42],
|
|
1448
|
+
metrics: { inputTokens: 5, outputTokens: 7, modelCalls: 2, toolCalls: 1 },
|
|
1449
|
+
creationDate: "2024-01-01T00:00:00Z",
|
|
1450
|
+
lastUpdatedDate: "2024-01-02T00:00:00Z",
|
|
1451
|
+
completionDate: null,
|
|
1452
|
+
},
|
|
1453
|
+
{ status: "IDLE", processInstanceKey: "11" }, // keyless → skipped
|
|
1454
|
+
{ agentInstanceKey: "ai-2" }, // no processInstanceKey → skipped
|
|
1455
|
+
],
|
|
1456
|
+
};
|
|
1457
|
+
},
|
|
1458
|
+
});
|
|
1459
|
+
const engine = new SdkEngineClient(client);
|
|
1460
|
+
const out = await engine.searchAgentInstances({
|
|
1461
|
+
processInstanceKey: " 10 ", // padded → trimmed
|
|
1462
|
+
rootProcessInstanceKey: " ", // whitespace-only → dropped
|
|
1463
|
+
status: "THINKING",
|
|
1464
|
+
elementId: "agent",
|
|
1465
|
+
});
|
|
1466
|
+
assert.deepEqual(seenInput, {
|
|
1467
|
+
filter: { processInstanceKey: "10", status: "THINKING", elementId: "agent" },
|
|
1468
|
+
});
|
|
1469
|
+
assert.deepEqual(seenConsistency, { consistency: { waitUpToMs: 0 } });
|
|
1470
|
+
assert.deepEqual(out, [
|
|
1471
|
+
{
|
|
1472
|
+
agentInstanceKey: "ai-1",
|
|
1473
|
+
status: "THINKING",
|
|
1474
|
+
processInstanceKey: "10",
|
|
1475
|
+
elementId: "agent",
|
|
1476
|
+
elementInstanceKeys: ["ei-1", "42"],
|
|
1477
|
+
rootProcessInstanceKey: "30",
|
|
1478
|
+
processDefinitionKey: "pd-1",
|
|
1479
|
+
processDefinitionId: "proc",
|
|
1480
|
+
metrics: { inputTokens: 5, outputTokens: 7, modelCalls: 2, toolCalls: 1 },
|
|
1481
|
+
creationDate: "2024-01-01T00:00:00Z",
|
|
1482
|
+
lastUpdatedDate: "2024-01-02T00:00:00Z",
|
|
1483
|
+
},
|
|
1484
|
+
]);
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
test("searchAgentInstanceHistory keys the path, maps the transcript grammar, and short-circuits a blank key", async () => {
|
|
1488
|
+
let seenInput: { agentInstanceKey?: string; filter?: Record<string, unknown> } | undefined;
|
|
1489
|
+
const client = fakeSdkClient({
|
|
1490
|
+
searchAgentInstanceHistory: async (input) => {
|
|
1491
|
+
seenInput = input;
|
|
1492
|
+
return {
|
|
1493
|
+
items: [
|
|
1494
|
+
{
|
|
1495
|
+
historyItemKey: "h-1",
|
|
1496
|
+
agentInstanceKey: "ai-1",
|
|
1497
|
+
loopIteration: 3,
|
|
1498
|
+
role: "ASSISTANT",
|
|
1499
|
+
content: [
|
|
1500
|
+
{ contentType: "TEXT", text: "hi" },
|
|
1501
|
+
{ contentType: "OBJECT", object: { a: 1 } },
|
|
1502
|
+
{ contentType: "MYSTERY" }, // unknown → UNSPECIFIED
|
|
1503
|
+
],
|
|
1504
|
+
toolCalls: [
|
|
1505
|
+
{ toolCallId: "t-1", toolName: "search", elementId: "tool", arguments: { q: "x" } },
|
|
1506
|
+
{ toolName: "no-id", arguments: null }, // no toolCallId → dropped
|
|
1507
|
+
],
|
|
1508
|
+
metrics: { inputTokens: 11, outputTokens: null, durationMs: 42 },
|
|
1509
|
+
commitStatus: "COMMITTED",
|
|
1510
|
+
elementInstanceKey: "ei-9",
|
|
1511
|
+
jobKey: "job-9",
|
|
1512
|
+
producedAt: "2024-01-01T00:00:00Z",
|
|
1513
|
+
},
|
|
1514
|
+
{ agentInstanceKey: "ai-1", role: "USER" }, // no historyItemKey → skipped
|
|
1515
|
+
],
|
|
1516
|
+
};
|
|
1517
|
+
},
|
|
1518
|
+
});
|
|
1519
|
+
const engine = new SdkEngineClient(client);
|
|
1520
|
+
const out = await engine.searchAgentInstanceHistory(" ai-1 ", {
|
|
1521
|
+
role: "ASSISTANT",
|
|
1522
|
+
loopIteration: 3,
|
|
1523
|
+
elementInstanceKey: " ei-9 ",
|
|
1524
|
+
});
|
|
1525
|
+
assert.deepEqual(seenInput, {
|
|
1526
|
+
agentInstanceKey: "ai-1",
|
|
1527
|
+
filter: { role: "ASSISTANT", loopIteration: 3, elementInstanceKey: "ei-9" },
|
|
1528
|
+
});
|
|
1529
|
+
assert.deepEqual(out, [
|
|
1530
|
+
{
|
|
1531
|
+
historyItemKey: "h-1",
|
|
1532
|
+
agentInstanceKey: "ai-1",
|
|
1533
|
+
loopIteration: 3,
|
|
1534
|
+
role: "ASSISTANT",
|
|
1535
|
+
content: [
|
|
1536
|
+
{ contentType: "TEXT", text: "hi" },
|
|
1537
|
+
{ contentType: "OBJECT", object: { a: 1 } },
|
|
1538
|
+
{ contentType: "UNSPECIFIED" },
|
|
1539
|
+
],
|
|
1540
|
+
toolCalls: [{ toolCallId: "t-1", toolName: "search", elementId: "tool", arguments: { q: "x" } }],
|
|
1541
|
+
metrics: {
|
|
1542
|
+
inputTokens: 11,
|
|
1543
|
+
outputTokens: 0,
|
|
1544
|
+
reasoningTokenCount: 0,
|
|
1545
|
+
cacheCreationTokenCount: 0,
|
|
1546
|
+
cacheReadTokenCount: 0,
|
|
1547
|
+
durationMs: 42,
|
|
1548
|
+
},
|
|
1549
|
+
commitStatus: "COMMITTED",
|
|
1550
|
+
elementInstanceKey: "ei-9",
|
|
1551
|
+
jobKey: "job-9",
|
|
1552
|
+
producedAt: "2024-01-01T00:00:00Z",
|
|
1553
|
+
},
|
|
1554
|
+
]);
|
|
1555
|
+
|
|
1556
|
+
// A blank key addresses no instance → empty list, no request issued.
|
|
1557
|
+
seenInput = undefined;
|
|
1558
|
+
const blank = await engine.searchAgentInstanceHistory(" ");
|
|
1559
|
+
assert.deepEqual(blank, []);
|
|
1560
|
+
assert.equal(seenInput, undefined, "no history search is issued for a blank key");
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
test("searchAgentInstanceHistory drops a transcript role the engine filter does not accept", async () => {
|
|
1564
|
+
// `TranscriptTurnRole` is the wider transcript union; only `USER`/`ASSISTANT`/`TOOL_RESULT` are
|
|
1565
|
+
// valid engine history filters. An unsupported role (e.g. `CONFIGURATION`/`UNSPECIFIED`) must not
|
|
1566
|
+
// be forwarded, else the engine 4xxes on the request. Guards the fix against reintroduction.
|
|
1567
|
+
let seenInput: { agentInstanceKey?: string; filter?: Record<string, unknown> } | undefined;
|
|
1568
|
+
const client = fakeSdkClient({
|
|
1569
|
+
searchAgentInstanceHistory: async (input) => {
|
|
1570
|
+
seenInput = input;
|
|
1571
|
+
return { items: [] };
|
|
1572
|
+
},
|
|
1573
|
+
});
|
|
1574
|
+
const engine = new SdkEngineClient(client);
|
|
1575
|
+
|
|
1576
|
+
await engine.searchAgentInstanceHistory("ai-1", { role: "CONFIGURATION", loopIteration: 2 });
|
|
1577
|
+
assert.deepEqual(
|
|
1578
|
+
seenInput,
|
|
1579
|
+
{ agentInstanceKey: "ai-1", filter: { loopIteration: 2 } },
|
|
1580
|
+
"an unsupported role is not forwarded as an engine filter",
|
|
1581
|
+
);
|
|
1582
|
+
|
|
1583
|
+
await engine.searchAgentInstanceHistory("ai-1", { role: "UNSPECIFIED" });
|
|
1584
|
+
assert.deepEqual(
|
|
1585
|
+
seenInput,
|
|
1586
|
+
{ agentInstanceKey: "ai-1", filter: {} },
|
|
1587
|
+
"UNSPECIFIED is dropped, leaving no role filter",
|
|
1588
|
+
);
|
|
1589
|
+
|
|
1590
|
+
await engine.searchAgentInstanceHistory("ai-1", { role: "TOOL_RESULT" });
|
|
1591
|
+
assert.deepEqual(
|
|
1592
|
+
seenInput,
|
|
1593
|
+
{ agentInstanceKey: "ai-1", filter: { role: "TOOL_RESULT" } },
|
|
1594
|
+
"a supported engine role is forwarded",
|
|
1595
|
+
);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
test("getAgentInstance reads-as-absence on a blank key and on a fetch error", async () => {
|
|
1599
|
+
let calls = 0;
|
|
1600
|
+
const client = fakeSdkClient({
|
|
1601
|
+
getAgentInstance: async (input) => {
|
|
1602
|
+
calls++;
|
|
1603
|
+
if (input.agentInstanceKey === "boom") throw new Error("404");
|
|
1604
|
+
return { agentInstanceKey: input.agentInstanceKey, status: "COMPLETED", processInstanceKey: "10" };
|
|
1605
|
+
},
|
|
1606
|
+
});
|
|
1607
|
+
const engine = new SdkEngineClient(client);
|
|
1608
|
+
|
|
1609
|
+
assert.equal(await engine.getAgentInstance(" "), null, "blank key → null without a fetch");
|
|
1610
|
+
assert.equal(calls, 0);
|
|
1611
|
+
|
|
1612
|
+
assert.equal(await engine.getAgentInstance("boom"), null, "a failed fetch is absence, not a throw");
|
|
1613
|
+
|
|
1614
|
+
assert.deepEqual(await engine.getAgentInstance(" ai-7 "), {
|
|
1615
|
+
agentInstanceKey: "ai-7",
|
|
1616
|
+
status: "COMPLETED",
|
|
1617
|
+
processInstanceKey: "10",
|
|
1618
|
+
});
|
|
1619
|
+
});
|