@sweny-ai/core 0.1.169 → 0.1.170
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/executor.js +1 -0
- package/dist/testing.d.ts +16 -12
- package/dist/testing.js +45 -20
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/executor.js
CHANGED
|
@@ -419,6 +419,7 @@ export async function execute(workflow, input, options) {
|
|
|
419
419
|
const attemptBudget = budgetOn ? nodeBudget.attempt(signal) : undefined;
|
|
420
420
|
try {
|
|
421
421
|
result = await claude.run({
|
|
422
|
+
nodeId: currentId,
|
|
422
423
|
instruction: currentInstruction,
|
|
423
424
|
context,
|
|
424
425
|
tools: trackedTools,
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test Helpers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* MockHarness for running workflows without an API key.
|
|
5
5
|
* File-based skill for local testing.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
|
9
|
-
* import {
|
|
9
|
+
* import { MockHarness } from '@sweny-ai/core/testing'
|
|
10
10
|
*
|
|
11
|
-
* const
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* data: { logs: [
|
|
15
|
-
*
|
|
16
|
-
* investigate: {
|
|
17
|
-
* data: { root_cause: 'NPE in handler', severity: 'high' },
|
|
11
|
+
* const harness = new MockHarness({
|
|
12
|
+
* strict: true,
|
|
13
|
+
* responses: {
|
|
14
|
+
* gather: { data: { logs: [] } },
|
|
15
|
+
* investigate: { data: { root_cause: 'NPE in handler', severity: 'high' } },
|
|
18
16
|
* },
|
|
19
17
|
* })
|
|
20
18
|
* ```
|
|
@@ -33,11 +31,13 @@ export interface MockNodeResponse {
|
|
|
33
31
|
status?: "success" | "skipped" | "failed";
|
|
34
32
|
}
|
|
35
33
|
export interface MockHarnessOptions {
|
|
34
|
+
/** Refuse incomplete or ambiguous fixtures. Does not isolate supplied tool handlers. */
|
|
35
|
+
strict?: boolean;
|
|
36
36
|
/** Node ID → scripted response */
|
|
37
37
|
responses: Record<string, MockNodeResponse>;
|
|
38
38
|
/** Route decisions: "fromNode" → chosen target node ID */
|
|
39
39
|
routes?: Record<string, string>;
|
|
40
|
-
/**
|
|
40
|
+
/** Enables instruction matching for manual calls without nodeId; execute() supplies node IDs. */
|
|
41
41
|
workflow?: Workflow;
|
|
42
42
|
/** Scripted handler for `ask()` calls. */
|
|
43
43
|
ask?: (instruction: string, context: Record<string, unknown>) => string;
|
|
@@ -57,6 +57,7 @@ export declare class MockHarness implements Claude, AgentHarness {
|
|
|
57
57
|
private responses;
|
|
58
58
|
private routes;
|
|
59
59
|
private instructionMap;
|
|
60
|
+
private strict;
|
|
60
61
|
private askFn?;
|
|
61
62
|
constructor(opts: MockHarnessOptions);
|
|
62
63
|
/** Returns the order in which nodes were executed */
|
|
@@ -70,6 +71,7 @@ export declare class MockHarness implements Claude, AgentHarness {
|
|
|
70
71
|
prompt: string;
|
|
71
72
|
}): Promise<string | null>;
|
|
72
73
|
run(opts: {
|
|
74
|
+
nodeId?: string;
|
|
73
75
|
instruction: string;
|
|
74
76
|
context: Record<string, unknown>;
|
|
75
77
|
tools: Tool[];
|
|
@@ -92,9 +94,11 @@ export declare class MockHarness implements Claude, AgentHarness {
|
|
|
92
94
|
* Identify which node is being executed.
|
|
93
95
|
*
|
|
94
96
|
* Strategy:
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
+
* Manual calls without nodeId can match a unique workflow instruction.
|
|
98
|
+
* Legacy mode also permits heuristic/sequential matching for compatibility.
|
|
99
|
+
* Strict mode never guesses from response order or instruction substrings.
|
|
97
100
|
*/
|
|
101
|
+
private fixtureFailure;
|
|
98
102
|
private identifyNode;
|
|
99
103
|
}
|
|
100
104
|
/** @deprecated Use {@link MockHarnessOptions}. */
|
package/dist/testing.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test Helpers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* MockHarness for running workflows without an API key.
|
|
5
5
|
* File-based skill for local testing.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
|
9
|
-
* import {
|
|
9
|
+
* import { MockHarness } from '@sweny-ai/core/testing'
|
|
10
10
|
*
|
|
11
|
-
* const
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* data: { logs: [
|
|
15
|
-
*
|
|
16
|
-
* investigate: {
|
|
17
|
-
* data: { root_cause: 'NPE in handler', severity: 'high' },
|
|
11
|
+
* const harness = new MockHarness({
|
|
12
|
+
* strict: true,
|
|
13
|
+
* responses: {
|
|
14
|
+
* gather: { data: { logs: [] } },
|
|
15
|
+
* investigate: { data: { root_cause: 'NPE in handler', severity: 'high' } },
|
|
18
16
|
* },
|
|
19
17
|
* })
|
|
20
18
|
* ```
|
|
@@ -47,9 +45,11 @@ export class MockHarness {
|
|
|
47
45
|
callOrder = [];
|
|
48
46
|
responses;
|
|
49
47
|
routes;
|
|
50
|
-
instructionMap; // instruction text → node
|
|
48
|
+
instructionMap; // instruction text → matching node IDs
|
|
49
|
+
strict;
|
|
51
50
|
askFn;
|
|
52
51
|
constructor(opts) {
|
|
52
|
+
this.strict = opts.strict ?? false;
|
|
53
53
|
this.responses = opts.responses;
|
|
54
54
|
this.routes = opts.routes ?? {};
|
|
55
55
|
this.askFn = opts.ask;
|
|
@@ -59,7 +59,9 @@ export class MockHarness {
|
|
|
59
59
|
for (const [id, node] of Object.entries(opts.workflow.nodes)) {
|
|
60
60
|
// Source can be a string or an object — only index string instructions
|
|
61
61
|
if (typeof node.instruction === "string") {
|
|
62
|
-
this.instructionMap.
|
|
62
|
+
const ids = this.instructionMap.get(node.instruction) ?? [];
|
|
63
|
+
ids.push(id);
|
|
64
|
+
this.instructionMap.set(node.instruction, ids);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
}
|
|
@@ -80,19 +82,29 @@ export class MockHarness {
|
|
|
80
82
|
return { ...result, harness: { id: "mock", version: "mock" }, degraded: [] };
|
|
81
83
|
}
|
|
82
84
|
async runScripted(opts) {
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Executor identity is authoritative. Text matching is for older manual callers.
|
|
86
|
+
const nodeId = opts.nodeId ?? this.identifyNode(opts.instruction);
|
|
87
|
+
if (nodeId === null) {
|
|
88
|
+
return this.fixtureFailure("ambiguous or unknown instruction; pass nodeId or provide a workflow with unique instructions");
|
|
89
|
+
}
|
|
87
90
|
this.callOrder.push(nodeId);
|
|
88
|
-
const response = this.responses[nodeId];
|
|
91
|
+
const response = Object.hasOwn(this.responses, nodeId) ? this.responses[nodeId] : undefined;
|
|
89
92
|
if (!response) {
|
|
93
|
+
if (this.strict)
|
|
94
|
+
return this.fixtureFailure(`node "${nodeId}" has no scripted response`);
|
|
90
95
|
return {
|
|
91
96
|
status: "success",
|
|
92
97
|
data: { summary: `Mock: no scripted response for "${nodeId}"` },
|
|
93
98
|
toolCalls: [],
|
|
94
99
|
};
|
|
95
100
|
}
|
|
101
|
+
// Check the entire script before executing any handler, so an incomplete
|
|
102
|
+
// fixture cannot perform an earlier write before discovering a missing tool.
|
|
103
|
+
if (this.strict) {
|
|
104
|
+
const missing = response.toolCalls?.find((tc) => !opts.tools.some((tool) => tool.name === tc.tool));
|
|
105
|
+
if (missing)
|
|
106
|
+
return this.fixtureFailure(`node "${nodeId}" requests unavailable tool "${missing.tool}"`);
|
|
107
|
+
}
|
|
96
108
|
// Execute scripted tool calls
|
|
97
109
|
const toolCalls = [];
|
|
98
110
|
if (response.toolCalls) {
|
|
@@ -136,16 +148,29 @@ export class MockHarness {
|
|
|
136
148
|
* Identify which node is being executed.
|
|
137
149
|
*
|
|
138
150
|
* Strategy:
|
|
139
|
-
*
|
|
140
|
-
*
|
|
151
|
+
* Manual calls without nodeId can match a unique workflow instruction.
|
|
152
|
+
* Legacy mode also permits heuristic/sequential matching for compatibility.
|
|
153
|
+
* Strict mode never guesses from response order or instruction substrings.
|
|
141
154
|
*/
|
|
155
|
+
fixtureFailure(reason) {
|
|
156
|
+
return {
|
|
157
|
+
status: "failed",
|
|
158
|
+
data: { error: `MockHarness fixture error: ${reason}`, refused: true },
|
|
159
|
+
toolCalls: [],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
142
162
|
identifyNode(instruction) {
|
|
143
163
|
// 1. Instruction-based matching (accurate for branching workflows)
|
|
144
164
|
if (this.instructionMap.size > 0) {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
165
|
+
const matches = this.instructionMap.get(instruction);
|
|
166
|
+
if (this.strict)
|
|
167
|
+
return matches?.length === 1 ? matches[0] : null;
|
|
168
|
+
const nodeId = matches?.at(-1);
|
|
169
|
+
if (nodeId && Object.hasOwn(this.responses, nodeId))
|
|
147
170
|
return nodeId;
|
|
148
171
|
}
|
|
172
|
+
if (this.strict)
|
|
173
|
+
return null;
|
|
149
174
|
// 2. Check if any response key appears literally in the instruction
|
|
150
175
|
const keys = Object.keys(this.responses);
|
|
151
176
|
for (const key of keys) {
|
package/dist/types.d.ts
CHANGED
|
@@ -800,6 +800,8 @@ export interface Claude {
|
|
|
800
800
|
readonly defaultJudgeModel?: string;
|
|
801
801
|
/** Run a node: give Claude an instruction, context, and tools */
|
|
802
802
|
run(opts: {
|
|
803
|
+
/** Stable workflow node identity, preserved across prompt wrapping and retries. */
|
|
804
|
+
nodeId?: string;
|
|
803
805
|
instruction: string;
|
|
804
806
|
context: Record<string, unknown>;
|
|
805
807
|
tools: Tool[];
|