callman-core 1.31.0 → 1.33.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/ai-packs/db-queries.md +1 -1
- package/ai-packs/scenario-nodes.md +89 -15
- package/dist/ai/prompts/scenario.d.ts +1 -1
- package/dist/ai/prompts/scenario.d.ts.map +1 -1
- package/dist/ai/prompts/scenario.js +6 -3
- package/dist/ai/prompts/scenario.js.map +1 -1
- package/dist/ai/prompts/scenarioNodeDefaults.d.ts.map +1 -1
- package/dist/ai/prompts/scenarioNodeDefaults.js +9 -0
- package/dist/ai/prompts/scenarioNodeDefaults.js.map +1 -1
- package/dist/ai/prompts/scenarioNodeReference.d.ts.map +1 -1
- package/dist/ai/prompts/scenarioNodeReference.js +136 -17
- package/dist/ai/prompts/scenarioNodeReference.js.map +1 -1
- package/dist/host/db/db-query-reference.js +1 -1
- package/dist/host/db/db-query-reference.js.map +1 -1
- package/dist/host/db/mongoAdapter.d.ts +1 -0
- package/dist/host/db/mongoAdapter.d.ts.map +1 -1
- package/dist/host/db/mongoAdapter.js +21 -0
- package/dist/host/db/mongoAdapter.js.map +1 -1
- package/dist/host/db/queryExecutor.js +2 -2
- package/dist/host/db/queryExecutor.js.map +1 -1
- package/dist/host/index.d.ts +3 -1
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +4 -1
- package/dist/host/index.js.map +1 -1
- package/dist/host/temporal/temporalService.d.ts +67 -0
- package/dist/host/temporal/temporalService.d.ts.map +1 -0
- package/dist/host/temporal/temporalService.js +1182 -0
- package/dist/host/temporal/temporalService.js.map +1 -0
- package/dist/host/temporal/types.d.ts +13 -0
- package/dist/host/temporal/types.d.ts.map +1 -0
- package/dist/host/temporal/types.js +5 -0
- package/dist/host/temporal/types.js.map +1 -0
- package/dist/scenario-runner/adapters/connection-config.d.ts +17 -1
- package/dist/scenario-runner/adapters/connection-config.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/index.d.ts +4 -2
- package/dist/scenario-runner/adapters/index.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/index.js +8 -0
- package/dist/scenario-runner/adapters/index.js.map +1 -1
- package/dist/scenario-runner/adapters/kafka-executor.d.ts +5 -0
- package/dist/scenario-runner/adapters/kafka-executor.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/kafka-executor.js +12 -3
- package/dist/scenario-runner/adapters/kafka-executor.js.map +1 -1
- package/dist/scenario-runner/adapters/temporal-executor.d.ts +10 -0
- package/dist/scenario-runner/adapters/temporal-executor.d.ts.map +1 -0
- package/dist/scenario-runner/adapters/temporal-executor.js +186 -0
- package/dist/scenario-runner/adapters/temporal-executor.js.map +1 -0
- package/dist/scenario-runner/adapters/types.d.ts +17 -5
- package/dist/scenario-runner/adapters/types.d.ts.map +1 -1
- package/dist/scenario-runner/condition.d.ts.map +1 -1
- package/dist/scenario-runner/condition.js +73 -0
- package/dist/scenario-runner/condition.js.map +1 -1
- package/dist/scenario-runner/graphTraversal.d.ts +6 -0
- package/dist/scenario-runner/graphTraversal.d.ts.map +1 -1
- package/dist/scenario-runner/graphTraversal.js +9 -0
- package/dist/scenario-runner/graphTraversal.js.map +1 -1
- package/dist/scenario-runner/index.d.ts +4 -0
- package/dist/scenario-runner/index.d.ts.map +1 -1
- package/dist/scenario-runner/index.js +8 -0
- package/dist/scenario-runner/index.js.map +1 -1
- package/dist/scenario-runner/kafkaEvent.d.ts +3 -0
- package/dist/scenario-runner/kafkaEvent.d.ts.map +1 -0
- package/dist/scenario-runner/kafkaEvent.js +18 -0
- package/dist/scenario-runner/kafkaEvent.js.map +1 -0
- package/dist/scenario-runner/nodePolicy.d.ts +9 -2
- package/dist/scenario-runner/nodePolicy.d.ts.map +1 -1
- package/dist/scenario-runner/nodePolicy.js +53 -0
- package/dist/scenario-runner/nodePolicy.js.map +1 -1
- package/dist/scenario-runner/runScenario.d.ts.map +1 -1
- package/dist/scenario-runner/runScenario.js +115 -22
- package/dist/scenario-runner/runScenario.js.map +1 -1
- package/dist/scenario-runner/scenarioReference.d.ts +45 -0
- package/dist/scenario-runner/scenarioReference.d.ts.map +1 -0
- package/dist/scenario-runner/scenarioReference.js +151 -0
- package/dist/scenario-runner/scenarioReference.js.map +1 -0
- package/dist/scenario-runner/temporal.d.ts +73 -0
- package/dist/scenario-runner/temporal.d.ts.map +1 -0
- package/dist/scenario-runner/temporal.js +377 -0
- package/dist/scenario-runner/temporal.js.map +1 -0
- package/dist/scenario-runner/types.d.ts +211 -4
- package/dist/scenario-runner/types.d.ts.map +1 -1
- package/dist/ui-test/commands.d.ts.map +1 -1
- package/dist/ui-test/commands.js +71 -0
- package/dist/ui-test/commands.js.map +1 -1
- package/dist-cjs/ai/prompts/scenario.js +6 -3
- package/dist-cjs/ai/prompts/scenario.js.map +1 -1
- package/dist-cjs/ai/prompts/scenarioNodeDefaults.js +9 -0
- package/dist-cjs/ai/prompts/scenarioNodeDefaults.js.map +1 -1
- package/dist-cjs/ai/prompts/scenarioNodeReference.js +135 -16
- package/dist-cjs/ai/prompts/scenarioNodeReference.js.map +1 -1
- package/dist-cjs/host/db/db-query-reference.js +1 -1
- package/dist-cjs/host/db/db-query-reference.js.map +1 -1
- package/dist-cjs/host/db/mongoAdapter.js +23 -1
- package/dist-cjs/host/db/mongoAdapter.js.map +1 -1
- package/dist-cjs/host/db/queryExecutor.js +1 -1
- package/dist-cjs/host/db/queryExecutor.js.map +1 -1
- package/dist-cjs/host/index.js +21 -2
- package/dist-cjs/host/index.js.map +1 -1
- package/dist-cjs/host/temporal/temporalService.js +1200 -0
- package/dist-cjs/host/temporal/temporalService.js.map +1 -0
- package/dist-cjs/host/temporal/types.js +6 -0
- package/dist-cjs/host/temporal/types.js.map +1 -0
- package/dist-cjs/scenario-runner/adapters/index.js +17 -3
- package/dist-cjs/scenario-runner/adapters/index.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/kafka-executor.js +15 -4
- package/dist-cjs/scenario-runner/adapters/kafka-executor.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/temporal-executor.js +190 -0
- package/dist-cjs/scenario-runner/adapters/temporal-executor.js.map +1 -0
- package/dist-cjs/scenario-runner/condition.js +73 -0
- package/dist-cjs/scenario-runner/condition.js.map +1 -1
- package/dist-cjs/scenario-runner/graphTraversal.js +10 -1
- package/dist-cjs/scenario-runner/graphTraversal.js.map +1 -1
- package/dist-cjs/scenario-runner/index.js +24 -1
- package/dist-cjs/scenario-runner/index.js.map +1 -1
- package/dist-cjs/scenario-runner/kafkaEvent.js +22 -0
- package/dist-cjs/scenario-runner/kafkaEvent.js.map +1 -0
- package/dist-cjs/scenario-runner/nodePolicy.js +56 -1
- package/dist-cjs/scenario-runner/nodePolicy.js.map +1 -1
- package/dist-cjs/scenario-runner/runScenario.js +114 -21
- package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
- package/dist-cjs/scenario-runner/scenarioReference.js +159 -0
- package/dist-cjs/scenario-runner/scenarioReference.js.map +1 -0
- package/dist-cjs/scenario-runner/temporal.js +386 -0
- package/dist-cjs/scenario-runner/temporal.js.map +1 -0
- package/dist-cjs/ui-test/commands.js +71 -0
- package/dist-cjs/ui-test/commands.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2
|
+
export const buildScenarioKafkaEventValue = (message, topic) => {
|
|
3
|
+
const facts = {
|
|
4
|
+
value: message.valueJson ?? message.valueText,
|
|
5
|
+
text: message.valueText,
|
|
6
|
+
key: message.key,
|
|
7
|
+
...(topic ? { topic } : {}),
|
|
8
|
+
partition: message.partition,
|
|
9
|
+
offset: message.offset,
|
|
10
|
+
timestamp: message.timestamp,
|
|
11
|
+
headers: Object.fromEntries(message.headers.map((header) => [header.key, header.value])),
|
|
12
|
+
};
|
|
13
|
+
if (isPlainRecord(message.valueJson)) {
|
|
14
|
+
return { ...message.valueJson, meta: facts };
|
|
15
|
+
}
|
|
16
|
+
return facts;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=kafkaEvent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kafkaEvent.js","sourceRoot":"","sources":["../../src/scenario-runner/kafkaEvent.ts"],"names":[],"mappings":"AASA,MAAM,aAAa,GAAG,CAAC,KAAc,EAAoC,EAAE,CACzE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEvE,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,OAAqC,EACrC,KAAc,EACW,EAAE;IAC3B,MAAM,KAAK,GAA4B;QACrC,KAAK,EAAE,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS;QAC7C,IAAI,EAAE,OAAO,CAAC,SAAS;QACvB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;KACzF,CAAC;IAEF,IAAI,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC/C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { AggregatorScenarioNode, AiScenarioNode, DbScenarioNode, EndScenarioNode, KafkaScenarioNode, NotificationScenarioNode, RedisScenarioNode, RequestScenarioNode, ScenarioNode, ScenarioNodeExecutionPolicy, ScenarioPolicyCapableNode, ScenarioPolicyCapableNodeType, ScriptScenarioNode, SubScenarioNode, ForeachScenarioNode, WebsocketScenarioNode, WaitScenarioNode } from "./types.js";
|
|
1
|
+
import { AggregatorScenarioNode, AiScenarioNode, DbScenarioNode, EndScenarioNode, KafkaScenarioNode, NotificationScenarioNode, RedisScenarioNode, RequestScenarioNode, ScenarioNode, ScenarioNodeExecutionPolicy, ScenarioNodeExpectedFailure, ScenarioNodeType, ScenarioPolicyCapableNode, ScenarioPolicyCapableNodeType, ScriptScenarioNode, SubScenarioNode, ForeachScenarioNode, TemporalScenarioNode, WebsocketScenarioNode, WaitScenarioNode } from "./types.js";
|
|
2
2
|
export declare const STRICT_SCENARIO_NODE_EXECUTION_POLICY: ScenarioNodeExecutionPolicy;
|
|
3
|
+
export declare const EXPECT_FAILURE_PATTERN_MAX_LENGTH = 500;
|
|
4
|
+
export declare const SCENARIO_EXPECT_FAILURE_NODE_TYPES: readonly ScenarioNodeType[];
|
|
5
|
+
export declare const supportsScenarioNodeExpectedFailure: (type: ScenarioNodeType) => boolean;
|
|
6
|
+
export declare const matchScenarioExpectedFailure: (expectation: ScenarioNodeExpectedFailure, message: string) => {
|
|
7
|
+
matched: boolean;
|
|
8
|
+
mismatchReason: string | null;
|
|
9
|
+
};
|
|
3
10
|
export declare const supportsScenarioNodeExecutionPolicy: (nodeOrType: ScenarioNode | ScenarioPolicyCapableNodeType) => nodeOrType is ScenarioPolicyCapableNode | ScenarioPolicyCapableNodeType;
|
|
4
11
|
export declare const normalizeScenarioNodeExecutionPolicy: (value: unknown, fallback?: ScenarioNodeExecutionPolicy) => ScenarioNodeExecutionPolicy;
|
|
5
12
|
export declare const getLegacyScenarioNodeExecutionPolicy: () => ScenarioNodeExecutionPolicy;
|
|
@@ -7,5 +14,5 @@ export declare const getNewScenarioNodeExecutionPolicyPreset: (type: ScenarioPol
|
|
|
7
14
|
export declare const getScenarioNodeExecutionPolicy: (node: ScenarioPolicyCapableNode) => ScenarioNodeExecutionPolicy;
|
|
8
15
|
export declare const getScenarioNodeMaxAttempts: (policy: ScenarioNodeExecutionPolicy) => number;
|
|
9
16
|
export declare const getScenarioNodePolicyBadges: (node: ScenarioPolicyCapableNode) => string[];
|
|
10
|
-
export declare const patchScenarioNodeExecutionPolicy: <TNode extends RequestScenarioNode | DbScenarioNode | WaitScenarioNode | ScriptScenarioNode | KafkaScenarioNode | RedisScenarioNode | NotificationScenarioNode | EndScenarioNode | AggregatorScenarioNode | SubScenarioNode | ForeachScenarioNode | WebsocketScenarioNode | AiScenarioNode>(node: TNode, executionPolicy: ScenarioNodeExecutionPolicy) => TNode;
|
|
17
|
+
export declare const patchScenarioNodeExecutionPolicy: <TNode extends RequestScenarioNode | DbScenarioNode | WaitScenarioNode | ScriptScenarioNode | KafkaScenarioNode | RedisScenarioNode | NotificationScenarioNode | EndScenarioNode | AggregatorScenarioNode | SubScenarioNode | ForeachScenarioNode | WebsocketScenarioNode | AiScenarioNode | TemporalScenarioNode>(node: TNode, executionPolicy: ScenarioNodeExecutionPolicy) => TNode;
|
|
11
18
|
//# sourceMappingURL=nodePolicy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodePolicy.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,qCAAqC,EAAE,2BAOnD,CAAC;
|
|
1
|
+
{"version":3,"file":"nodePolicy.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,2BAA2B,EAC3B,2BAA2B,EAC3B,gBAAgB,EAChB,yBAAyB,EACzB,6BAA6B,EAC7B,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,qCAAqC,EAAE,2BAOnD,CAAC;AAkEF,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAKrD,eAAO,MAAM,kCAAkC,EAAE,SAAS,gBAAgB,EAYzE,CAAC;AAEF,eAAO,MAAM,mCAAmC,GAAI,MAAM,gBAAgB,KAAG,OAC1B,CAAC;AAapD,eAAO,MAAM,4BAA4B,GACvC,aAAa,2BAA2B,EACxC,SAAS,MAAM,KACd;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAcnD,CAAC;AAEF,eAAO,MAAM,mCAAmC,GAC9C,YAAY,YAAY,GAAG,6BAA6B,KACvD,UAAU,IAAI,yBAAyB,GAAG,6BAI5C,CAAC;AAEF,eAAO,MAAM,oCAAoC,GAC/C,OAAO,OAAO,EACd,WAAU,2BAAmE,KAC5E,2BAqEF,CAAC;AAEF,eAAO,MAAM,oCAAoC,QAAO,2BACJ,CAAC;AAErD,eAAO,MAAM,uCAAuC,GAClD,MAAM,6BAA6B,KAClC,2BAAqF,CAAC;AAEzF,eAAO,MAAM,8BAA8B,GACzC,MAAM,yBAAyB,KAC9B,2BAIA,CAAC;AAEJ,eAAO,MAAM,0BAA0B,GACrC,QAAQ,2BAA2B,KAClC,MAA4E,CAAC;AAEhF,eAAO,MAAM,2BAA2B,GACtC,MAAM,yBAAyB,KAC9B,MAAM,EAmBR,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAC3C,KAAK,SACD,mBAAmB,GACnB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,GACjB,wBAAwB,GACxB,eAAe,GACf,sBAAsB,GACtB,eAAe,GACf,mBAAmB,GACnB,qBAAqB,GACrB,cAAc,GACd,oBAAoB,EAExB,MAAM,KAAK,EACX,iBAAiB,2BAA2B,KAC3C,KASD,CAAC"}
|
|
@@ -52,6 +52,8 @@ const RECOMMENDED_NEW_NODE_POLICY_PRESETS = {
|
|
|
52
52
|
websocket: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
|
|
53
53
|
// Retry re-sends the whole prompt (a fresh provider call each attempt).
|
|
54
54
|
ai: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
|
|
55
|
+
// No retry by default: startWorkflow / signal are not idempotent.
|
|
56
|
+
temporal: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
|
|
55
57
|
};
|
|
56
58
|
const clonePolicy = (policy) => ({
|
|
57
59
|
onFailure: policy.onFailure,
|
|
@@ -61,6 +63,50 @@ const clonePolicy = (policy) => ({
|
|
|
61
63
|
delayMs: policy.retry.delayMs,
|
|
62
64
|
},
|
|
63
65
|
});
|
|
66
|
+
export const EXPECT_FAILURE_PATTERN_MAX_LENGTH = 500;
|
|
67
|
+
// Leaf nodes whose own work can fail. Containers (foreach, subscenario,
|
|
68
|
+
// scenario-ref), routing (condition, end), pure data nodes (set-vars,
|
|
69
|
+
// aggregator) and wait have no failure of their own to expect.
|
|
70
|
+
export const SCENARIO_EXPECT_FAILURE_NODE_TYPES = [
|
|
71
|
+
"request",
|
|
72
|
+
"db",
|
|
73
|
+
"redis",
|
|
74
|
+
"kafka",
|
|
75
|
+
"websocket",
|
|
76
|
+
"script",
|
|
77
|
+
"assertion",
|
|
78
|
+
"poll",
|
|
79
|
+
"ai",
|
|
80
|
+
"temporal",
|
|
81
|
+
"notification",
|
|
82
|
+
];
|
|
83
|
+
export const supportsScenarioNodeExpectedFailure = (type) => SCENARIO_EXPECT_FAILURE_NODE_TYPES.includes(type);
|
|
84
|
+
const normalizeExpectedFailure = (value) => {
|
|
85
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const raw = value.messagePattern;
|
|
89
|
+
const pattern = typeof raw === "string" ? raw.trim().slice(0, EXPECT_FAILURE_PATTERN_MAX_LENGTH) : "";
|
|
90
|
+
return pattern ? { messagePattern: pattern } : {};
|
|
91
|
+
};
|
|
92
|
+
// Whether a failure message satisfies the expectation. An empty expectation
|
|
93
|
+
// accepts any failure; an invalid regex never matches (and says so).
|
|
94
|
+
export const matchScenarioExpectedFailure = (expectation, message) => {
|
|
95
|
+
const pattern = expectation.messagePattern?.trim();
|
|
96
|
+
if (!pattern) {
|
|
97
|
+
return { matched: true, mismatchReason: null };
|
|
98
|
+
}
|
|
99
|
+
let re;
|
|
100
|
+
try {
|
|
101
|
+
re = new RegExp(pattern);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return { matched: false, mismatchReason: `invalid expectFailure.messagePattern regex: ${pattern}` };
|
|
105
|
+
}
|
|
106
|
+
return re.test(message)
|
|
107
|
+
? { matched: true, mismatchReason: null }
|
|
108
|
+
: { matched: false, mismatchReason: `the error did not match /${pattern}/` };
|
|
109
|
+
};
|
|
64
110
|
export const supportsScenarioNodeExecutionPolicy = (nodeOrType) => {
|
|
65
111
|
const type = typeof nodeOrType === "string" ? nodeOrType : nodeOrType.type;
|
|
66
112
|
return type !== "condition";
|
|
@@ -104,6 +150,9 @@ export const normalizeScenarioNodeExecutionPolicy = (value, fallback = STRICT_SC
|
|
|
104
150
|
const maxDelayMs = retryEnabled && backoffMultiplier && typeof retryMaxDelayRaw === "number" && retryMaxDelayRaw > 0
|
|
105
151
|
? Math.max(delayMs, Math.round(retryMaxDelayRaw))
|
|
106
152
|
: undefined;
|
|
153
|
+
const expectFailure = typeof value === "object" && value !== null && "expectFailure" in value
|
|
154
|
+
? normalizeExpectedFailure(value.expectFailure)
|
|
155
|
+
: undefined;
|
|
107
156
|
return {
|
|
108
157
|
onFailure: normalizedOnFailure,
|
|
109
158
|
retry: {
|
|
@@ -113,6 +162,7 @@ export const normalizeScenarioNodeExecutionPolicy = (value, fallback = STRICT_SC
|
|
|
113
162
|
...(backoffMultiplier ? { backoffMultiplier } : {}),
|
|
114
163
|
...(maxDelayMs ? { maxDelayMs } : {}),
|
|
115
164
|
},
|
|
165
|
+
...(expectFailure ? { expectFailure } : {}),
|
|
116
166
|
};
|
|
117
167
|
};
|
|
118
168
|
export const getLegacyScenarioNodeExecutionPolicy = () => clonePolicy(STRICT_SCENARIO_NODE_EXECUTION_POLICY);
|
|
@@ -131,6 +181,9 @@ export const getScenarioNodePolicyBadges = (node) => {
|
|
|
131
181
|
if (policy.retry.enabled) {
|
|
132
182
|
badges.push(`Retry ×${getScenarioNodeMaxAttempts(policy)}`);
|
|
133
183
|
}
|
|
184
|
+
if (policy.expectFailure && supportsScenarioNodeExpectedFailure(node.type)) {
|
|
185
|
+
badges.push("Expects failure");
|
|
186
|
+
}
|
|
134
187
|
return badges;
|
|
135
188
|
};
|
|
136
189
|
export const patchScenarioNodeExecutionPolicy = (node, executionPolicy) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodePolicy.js","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"nodePolicy.js","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EAMV,kBAAkB,EAUlB,iBAAiB,EAMlB,MAAM,YAAY,CAAC;AA2tBpB,eAAO,MAAM,WAAW,GAAU,qHAS/B,kBAAkB,KAAG,OAAO,CAAC,iBAAiB,CAgvFhD,CAAC"}
|
|
@@ -6,7 +6,8 @@ import { createSubScenarioStepId, buildRuntimeStepDescriptors, DEFAULT_END_LOOP_
|
|
|
6
6
|
import { evaluateWebsocketExpect } from "./websocket.js";
|
|
7
7
|
import { executeWithScenarioNodePolicy } from "./executionPolicyRunner.js";
|
|
8
8
|
import { boundSnapshot } from "./boundSnapshot.js";
|
|
9
|
-
import {
|
|
9
|
+
import { buildScenarioKafkaEventValue } from "./kafkaEvent.js";
|
|
10
|
+
import { getLegacyScenarioNodeExecutionPolicy, getScenarioNodeExecutionPolicy, matchScenarioExpectedFailure, supportsScenarioNodeExecutionPolicy, supportsScenarioNodeExpectedFailure, } from "./nodePolicy.js";
|
|
10
11
|
import { buildScenarioExecutionGraph, buildScenarioTemplateValues, collectExclusiveBranchNodeIds, evaluateScenarioExpression, resolveScenarioJsonTemplateString, resolveScenarioTemplateString, } from "./templateResolver.js";
|
|
11
12
|
import { executeWaitNode, ScenarioWaitNodeCancelledError, ScenarioWaitNodeValidationError, } from "./waitNodeExecutor.js";
|
|
12
13
|
import { buildNotificationDeliveryRequest, renderNotificationTemplate, } from "./notification/index.js";
|
|
@@ -22,6 +23,11 @@ class ScenarioNodeFailureError extends Error {
|
|
|
22
23
|
this.name = "ScenarioNodeFailureError";
|
|
23
24
|
}
|
|
24
25
|
}
|
|
26
|
+
// An attempt that succeeded although the node's policy expects a failure.
|
|
27
|
+
// Kept apart from ordinary failures so it is never mistaken for the
|
|
28
|
+
// expected one.
|
|
29
|
+
class ScenarioExpectedFailureNotMetError extends ScenarioNodeFailureError {
|
|
30
|
+
}
|
|
25
31
|
const DEFAULT_MIN_NODE_VISUAL_STATE_MS = 180;
|
|
26
32
|
const createId = (prefix) => {
|
|
27
33
|
const randomUuid = globalThis.crypto?.randomUUID?.();
|
|
@@ -122,24 +128,6 @@ const createContextSnapshot = (context, mode = "full") => {
|
|
|
122
128
|
redisResult: bound(context.redisResult),
|
|
123
129
|
};
|
|
124
130
|
};
|
|
125
|
-
const buildKafkaEventValue = (message) => {
|
|
126
|
-
const fallbackValue = {
|
|
127
|
-
value: message.valueJson ?? message.valueText,
|
|
128
|
-
text: message.valueText,
|
|
129
|
-
key: message.key,
|
|
130
|
-
partition: message.partition,
|
|
131
|
-
offset: message.offset,
|
|
132
|
-
timestamp: message.timestamp,
|
|
133
|
-
headers: Object.fromEntries(message.headers.map((header) => [header.key, header.value])),
|
|
134
|
-
};
|
|
135
|
-
if (isRecord(message.valueJson)) {
|
|
136
|
-
return {
|
|
137
|
-
...message.valueJson,
|
|
138
|
-
meta: fallbackValue,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
return fallbackValue;
|
|
142
|
-
};
|
|
143
131
|
const buildKafkaFailureMessage = (result, usedMatch) => {
|
|
144
132
|
if (result.errorMessage?.trim()) {
|
|
145
133
|
return result.errorMessage;
|
|
@@ -331,6 +319,7 @@ const createStepExecutionRecord = ({ stepId, stepType, title, }) => ({
|
|
|
331
319
|
conditionDebug: null,
|
|
332
320
|
activeBranch: null,
|
|
333
321
|
ai: null,
|
|
322
|
+
temporal: null,
|
|
334
323
|
inputData: null,
|
|
335
324
|
resolvedInput: null,
|
|
336
325
|
outputData: null,
|
|
@@ -511,6 +500,7 @@ const updateRecordFromEvent = (records, event) => {
|
|
|
511
500
|
conditionDebug: event.conditionDebug ?? current.conditionDebug,
|
|
512
501
|
activeBranch: event.activeBranch ?? current.activeBranch,
|
|
513
502
|
ai: event.ai ?? current.ai,
|
|
503
|
+
temporal: event.temporal ?? current.temporal,
|
|
514
504
|
inputData: event.inputData ?? current.inputData,
|
|
515
505
|
resolvedInput: event.resolvedInput ?? current.resolvedInput ?? null,
|
|
516
506
|
outputData: event.outputData ?? current.outputData,
|
|
@@ -864,6 +854,16 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
864
854
|
retry: { enabled: false, maxAttempts: 0, delayMs: 0 },
|
|
865
855
|
};
|
|
866
856
|
}
|
|
857
|
+
// Negative test: this node must fail. On a db node a non-empty
|
|
858
|
+
// expectError is the more precise expectation and wins.
|
|
859
|
+
const dbExpectsError = node.type === "db" &&
|
|
860
|
+
Boolean(node.data.config.expectError?.sqlState?.trim() ||
|
|
861
|
+
node.data.config.expectError?.messagePattern?.trim());
|
|
862
|
+
const expectedFailure = executionPolicy.expectFailure &&
|
|
863
|
+
supportsScenarioNodeExpectedFailure(node.type) &&
|
|
864
|
+
!dbExpectsError
|
|
865
|
+
? executionPolicy.expectFailure
|
|
866
|
+
: undefined;
|
|
867
867
|
const executionStartedAtMs = Date.now();
|
|
868
868
|
let inputData = createInputSnapshot(runtimeContext);
|
|
869
869
|
// The node's OWN resolved input (query + params, topic + payload, the
|
|
@@ -897,6 +897,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
897
897
|
let shouldMarkPartialSuccess = false;
|
|
898
898
|
let workflowOutput = undefined;
|
|
899
899
|
let aiDetails = null;
|
|
900
|
+
let temporalDetails = null;
|
|
900
901
|
const resetAttemptArtifacts = () => {
|
|
901
902
|
statusCode = null;
|
|
902
903
|
response = null;
|
|
@@ -924,6 +925,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
924
925
|
shouldMarkPartialSuccess = false;
|
|
925
926
|
workflowOutput = undefined;
|
|
926
927
|
aiDetails = null;
|
|
928
|
+
temporalDetails = null;
|
|
927
929
|
};
|
|
928
930
|
const executeNodeAttempt = async (attempt, maxAttempts) => {
|
|
929
931
|
throwIfStopped(signal);
|
|
@@ -1140,6 +1142,10 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1140
1142
|
: null;
|
|
1141
1143
|
// Thinned emission keeps stored events bounded (~100 tick events).
|
|
1142
1144
|
const pollEmitStride = Math.max(1, Math.ceil(pollMaxAttempts / 100));
|
|
1145
|
+
// Temporal ticks have no runtime-context slot (unlike response /
|
|
1146
|
+
// db / redis), so the last tick's output rides along on the poll
|
|
1147
|
+
// output as `<Label>.temporal`.
|
|
1148
|
+
let lastTemporalTickOutput = null;
|
|
1143
1149
|
resolvedInput = {
|
|
1144
1150
|
actionType: pollConfig.actionType,
|
|
1145
1151
|
intervalMs: pollBaseIntervalMs,
|
|
@@ -1245,6 +1251,39 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1245
1251
|
runtimeContext.redisResult = tickResult.redisResult;
|
|
1246
1252
|
return tickResult.failureMessage ?? null;
|
|
1247
1253
|
}
|
|
1254
|
+
if (pollConfig.actionType === "temporal") {
|
|
1255
|
+
if (!pollConfig.temporal) {
|
|
1256
|
+
throw new ScenarioNodeFailureError("Poll node has no Temporal configuration.");
|
|
1257
|
+
}
|
|
1258
|
+
if (!runtimeAdapters.temporalExecutor) {
|
|
1259
|
+
throw new ScenarioNodeFailureError("This host does not support Temporal nodes yet.");
|
|
1260
|
+
}
|
|
1261
|
+
const tickResult = await runtimeAdapters.temporalExecutor({
|
|
1262
|
+
runId,
|
|
1263
|
+
stepId,
|
|
1264
|
+
node: {
|
|
1265
|
+
id: node.id,
|
|
1266
|
+
type: "temporal",
|
|
1267
|
+
position: node.position,
|
|
1268
|
+
data: { label: node.data.label, config: pollConfig.temporal },
|
|
1269
|
+
},
|
|
1270
|
+
context: snapshotContext(),
|
|
1271
|
+
signal,
|
|
1272
|
+
});
|
|
1273
|
+
temporalDetails = tickResult.details;
|
|
1274
|
+
assertionResults = tickResult.assertionResults;
|
|
1275
|
+
assertionSummary = tickResult.assertionSummary;
|
|
1276
|
+
lastTemporalTickOutput = tickResult.outputData;
|
|
1277
|
+
// Publish the tick's output under the label so the success
|
|
1278
|
+
// condition reads `<Label>.status` / `<Label>.result.x` exactly
|
|
1279
|
+
// like it would after a standalone temporal node.
|
|
1280
|
+
if (tickResult.outputData &&
|
|
1281
|
+
nodeLabel &&
|
|
1282
|
+
!RESERVED_WORKFLOW_ROOT_LABELS.has(nodeLabel)) {
|
|
1283
|
+
runtimeContext.workflowContext[nodeLabel] = tickResult.outputData;
|
|
1284
|
+
}
|
|
1285
|
+
return tickResult.failureMessage ?? null;
|
|
1286
|
+
}
|
|
1248
1287
|
if (!pollConfig.script) {
|
|
1249
1288
|
throw new ScenarioNodeFailureError("Poll node has no script configuration.");
|
|
1250
1289
|
}
|
|
@@ -1341,6 +1380,9 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1341
1380
|
timedOut: !pollPassed,
|
|
1342
1381
|
conditionResult: pollPassed,
|
|
1343
1382
|
...(lastTickError ? { lastTickError } : {}),
|
|
1383
|
+
...(pollConfig.actionType === "temporal" && lastTemporalTickOutput
|
|
1384
|
+
? { temporal: lastTemporalTickOutput }
|
|
1385
|
+
: {}),
|
|
1344
1386
|
};
|
|
1345
1387
|
if (!pollPassed) {
|
|
1346
1388
|
if ((pollConfig.onTimeout ?? "fail") === "continue") {
|
|
@@ -1512,7 +1554,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1512
1554
|
if (resolvedMatchMode === "rules" && rules.length > 0) {
|
|
1513
1555
|
matchedMessage =
|
|
1514
1556
|
captureResult.messages.find((message) => {
|
|
1515
|
-
const kafkaEventValue =
|
|
1557
|
+
const kafkaEventValue = buildScenarioKafkaEventValue(message, captureResult.topic);
|
|
1516
1558
|
const scopedValues = buildScenarioTemplateValues({
|
|
1517
1559
|
environment: runtimeContext.environment,
|
|
1518
1560
|
globals: runtimeContext.globals,
|
|
@@ -1531,7 +1573,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1531
1573
|
throw new ScenarioNodeFailureError(buildKafkaFailureMessage(captureResult, Boolean(rules.length)));
|
|
1532
1574
|
}
|
|
1533
1575
|
runtimeContext.kafkaCapture = captureResult;
|
|
1534
|
-
runtimeContext.kafkaEvent =
|
|
1576
|
+
runtimeContext.kafkaEvent = buildScenarioKafkaEventValue(matchedMessage, captureResult.topic);
|
|
1535
1577
|
outputData = runtimeContext.kafkaEvent;
|
|
1536
1578
|
}
|
|
1537
1579
|
}
|
|
@@ -1727,6 +1769,35 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1727
1769
|
throw new ScenarioNodeFailureError(aiResult.failureMessage);
|
|
1728
1770
|
}
|
|
1729
1771
|
}
|
|
1772
|
+
if (node.type === "temporal") {
|
|
1773
|
+
// Thin dispatch (ai pattern): the adapter resolves templates,
|
|
1774
|
+
// validates the operation, calls the host driver and evaluates the
|
|
1775
|
+
// node's own `expect` rules. A FAILED workflow is data (status +
|
|
1776
|
+
// failure in the output); only infrastructure errors and failed
|
|
1777
|
+
// expectations fail the node.
|
|
1778
|
+
if (!runtimeAdapters.temporalExecutor) {
|
|
1779
|
+
throw new ScenarioNodeFailureError("This host does not support Temporal nodes yet.");
|
|
1780
|
+
}
|
|
1781
|
+
const temporalResult = await runtimeAdapters.temporalExecutor({
|
|
1782
|
+
runId,
|
|
1783
|
+
stepId,
|
|
1784
|
+
node,
|
|
1785
|
+
context: snapshotContext(),
|
|
1786
|
+
signal,
|
|
1787
|
+
});
|
|
1788
|
+
throwIfStopped(signal);
|
|
1789
|
+
temporalDetails = temporalResult.details;
|
|
1790
|
+
outputData = temporalResult.outputData;
|
|
1791
|
+
resolvedInput = temporalResult.resolvedInput;
|
|
1792
|
+
assertionResults = temporalResult.assertionResults;
|
|
1793
|
+
assertionSummary = temporalResult.assertionSummary;
|
|
1794
|
+
if (temporalResult.degraded) {
|
|
1795
|
+
shouldMarkPartialSuccess = true;
|
|
1796
|
+
}
|
|
1797
|
+
if (temporalResult.failureMessage) {
|
|
1798
|
+
throw new ScenarioNodeFailureError(temporalResult.failureMessage);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1730
1801
|
if (node.type === "condition") {
|
|
1731
1802
|
const conditionIssues = validateConditionConfig(node.data.config);
|
|
1732
1803
|
const blockingConditionIssue = conditionIssues.find((issue) => issue.severity === "error");
|
|
@@ -2225,15 +2296,34 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
2225
2296
|
runtimeContext.workflowContext[nodeLabel] = workflowOutput;
|
|
2226
2297
|
}
|
|
2227
2298
|
await ensureMinimumVisualDuration(attemptStartedAtMs);
|
|
2299
|
+
if (expectedFailure) {
|
|
2300
|
+
throw new ScenarioExpectedFailureNotMetError(`Expected this node to fail${expectedFailure.messagePattern
|
|
2301
|
+
? ` with an error matching /${expectedFailure.messagePattern}/`
|
|
2302
|
+
: ""}, but it succeeded.`);
|
|
2303
|
+
}
|
|
2228
2304
|
}
|
|
2229
2305
|
catch (error) {
|
|
2230
2306
|
if (error instanceof ScenarioStopError) {
|
|
2231
2307
|
throw error;
|
|
2232
2308
|
}
|
|
2233
2309
|
await ensureMinimumVisualDuration(attemptStartedAtMs);
|
|
2234
|
-
|
|
2310
|
+
const failure = error instanceof ScenarioNodeFailureError
|
|
2235
2311
|
? error
|
|
2236
2312
|
: new ScenarioNodeFailureError(error instanceof Error ? error.message : "Scenario node failed");
|
|
2313
|
+
if (expectedFailure && !(failure instanceof ScenarioExpectedFailureNotMetError)) {
|
|
2314
|
+
const match = matchScenarioExpectedFailure(expectedFailure, failure.message);
|
|
2315
|
+
if (!match.matched) {
|
|
2316
|
+
throw new ScenarioNodeFailureError(`The node failed, but not as expected (${match.mismatchReason}): ${failure.message}`);
|
|
2317
|
+
}
|
|
2318
|
+
// The failure IS the result: the node succeeds and exposes it.
|
|
2319
|
+
outputData = { expectedFailure: { message: failure.message } };
|
|
2320
|
+
workflowOutput = outputData;
|
|
2321
|
+
if (nodeLabel && !RESERVED_WORKFLOW_ROOT_LABELS.has(nodeLabel)) {
|
|
2322
|
+
runtimeContext.workflowContext[nodeLabel] = workflowOutput;
|
|
2323
|
+
}
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
throw failure;
|
|
2237
2327
|
}
|
|
2238
2328
|
};
|
|
2239
2329
|
try {
|
|
@@ -2348,6 +2438,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
2348
2438
|
conditionDebug,
|
|
2349
2439
|
activeBranch,
|
|
2350
2440
|
ai: aiDetails,
|
|
2441
|
+
temporal: temporalDetails,
|
|
2351
2442
|
inputData,
|
|
2352
2443
|
resolvedInput: resolvedInputForEvent(),
|
|
2353
2444
|
outputData,
|
|
@@ -2432,6 +2523,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
2432
2523
|
conditionDebug,
|
|
2433
2524
|
activeBranch,
|
|
2434
2525
|
ai: aiDetails,
|
|
2526
|
+
temporal: temporalDetails,
|
|
2435
2527
|
inputData,
|
|
2436
2528
|
resolvedInput: resolvedInputForEvent(),
|
|
2437
2529
|
outputData,
|
|
@@ -2511,6 +2603,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
2511
2603
|
conditionDebug,
|
|
2512
2604
|
activeBranch,
|
|
2513
2605
|
ai: aiDetails,
|
|
2606
|
+
temporal: temporalDetails,
|
|
2514
2607
|
inputData,
|
|
2515
2608
|
resolvedInput: resolvedInputForEvent(),
|
|
2516
2609
|
outputData,
|