@sweny-ai/core 0.1.75 → 0.1.77

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/schema.js CHANGED
@@ -101,6 +101,21 @@ export const nodeVerifyZ = z
101
101
  v.output_matches !== undefined, {
102
102
  message: "verify must declare at least one check (any_tool_called, all_tools_called, no_tool_called, output_required, or output_matches)",
103
103
  });
104
+ export const nodeRequiresZ = z
105
+ .object({
106
+ output_required: z.array(z.string().min(1)).min(1).optional(),
107
+ output_matches: z.array(outputMatchZ).min(1).optional(),
108
+ on_fail: z.enum(["fail", "skip"]).optional(),
109
+ })
110
+ .refine((r) => r.output_required !== undefined || r.output_matches !== undefined, {
111
+ message: "requires must declare at least one check (output_required or output_matches)",
112
+ });
113
+ const retryInstructionAutoZ = z.object({ auto: z.literal(true) }).strict();
114
+ const retryInstructionReflectZ = z.object({ reflect: z.string().min(1) }).strict();
115
+ export const nodeRetryZ = z.object({
116
+ max: z.number().int().min(1),
117
+ instruction: z.union([z.string().min(1), retryInstructionAutoZ, retryInstructionReflectZ]).optional(),
118
+ });
104
119
  export const nodeZ = z.object({
105
120
  name: z.string().min(1),
106
121
  instruction: sourceZ,
@@ -110,6 +125,8 @@ export const nodeZ = z.object({
110
125
  rules: nodeSourcesZ.optional(),
111
126
  context: nodeSourcesZ.optional(),
112
127
  verify: nodeVerifyZ.optional(),
128
+ requires: nodeRequiresZ.optional(),
129
+ retry: nodeRetryZ.optional(),
113
130
  });
114
131
  export const edgeZ = z.object({
115
132
  from: z.string().min(1),
@@ -375,6 +392,86 @@ export const workflowJsonSchema = {
375
392
  $ref: "#/$defs/NodeSources",
376
393
  description: "Per-node context. Additive by default; set { only: true } to block cascade.",
377
394
  },
395
+ verify: {
396
+ type: "object",
397
+ description: "Machine-checked post-conditions evaluated after the LLM finishes.",
398
+ additionalProperties: false,
399
+ properties: {
400
+ any_tool_called: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
401
+ all_tools_called: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
402
+ no_tool_called: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
403
+ output_required: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
404
+ output_matches: {
405
+ type: "array",
406
+ items: {
407
+ type: "object",
408
+ required: ["path"],
409
+ additionalProperties: false,
410
+ properties: {
411
+ path: { type: "string", minLength: 1 },
412
+ equals: {},
413
+ in: { type: "array" },
414
+ matches: { type: "string", minLength: 1 },
415
+ },
416
+ },
417
+ minItems: 1,
418
+ },
419
+ },
420
+ },
421
+ requires: {
422
+ type: "object",
423
+ description: "Pre-condition checks evaluated before the LLM runs.",
424
+ additionalProperties: false,
425
+ properties: {
426
+ output_required: {
427
+ type: "array",
428
+ items: { type: "string", minLength: 1 },
429
+ minItems: 1,
430
+ },
431
+ output_matches: {
432
+ type: "array",
433
+ items: {
434
+ type: "object",
435
+ required: ["path"],
436
+ additionalProperties: false,
437
+ properties: {
438
+ path: { type: "string", minLength: 1 },
439
+ equals: {},
440
+ in: { type: "array" },
441
+ matches: { type: "string", minLength: 1 },
442
+ },
443
+ },
444
+ minItems: 1,
445
+ },
446
+ on_fail: { type: "string", enum: ["fail", "skip"] },
447
+ },
448
+ },
449
+ retry: {
450
+ type: "object",
451
+ description: "Node-local retry on verify failure.",
452
+ required: ["max"],
453
+ additionalProperties: false,
454
+ properties: {
455
+ max: { type: "integer", minimum: 1 },
456
+ instruction: {
457
+ oneOf: [
458
+ { type: "string", minLength: 1 },
459
+ {
460
+ type: "object",
461
+ required: ["auto"],
462
+ additionalProperties: false,
463
+ properties: { auto: { const: true } },
464
+ },
465
+ {
466
+ type: "object",
467
+ required: ["reflect"],
468
+ additionalProperties: false,
469
+ properties: { reflect: { type: "string", minLength: 1 } },
470
+ },
471
+ ],
472
+ },
473
+ },
474
+ },
378
475
  },
379
476
  },
380
477
  },
package/dist/testing.d.ts CHANGED
@@ -38,6 +38,8 @@ export interface MockClaudeOptions {
38
38
  routes?: Record<string, string>;
39
39
  /** Workflow definition — enables instruction-based node matching (required for branching workflows) */
40
40
  workflow?: Workflow;
41
+ /** Scripted handler for `ask()` calls. */
42
+ ask?: (instruction: string, context: Record<string, unknown>) => string;
41
43
  }
42
44
  /**
43
45
  * A mock Claude client that follows a script.
@@ -51,6 +53,7 @@ export declare class MockClaude implements Claude {
51
53
  private responses;
52
54
  private routes;
53
55
  private instructionMap;
56
+ private askFn?;
54
57
  constructor(opts: MockClaudeOptions);
55
58
  /** Returns the order in which nodes were executed */
56
59
  get executedNodes(): string[];
@@ -68,6 +71,10 @@ export declare class MockClaude implements Claude {
68
71
  description: string;
69
72
  }[];
70
73
  }): Promise<string>;
74
+ ask(opts: {
75
+ instruction: string;
76
+ context: Record<string, unknown>;
77
+ }): Promise<string>;
71
78
  /**
72
79
  * Identify which node is being executed.
73
80
  *
package/dist/testing.js CHANGED
@@ -32,9 +32,11 @@ export class MockClaude {
32
32
  responses;
33
33
  routes;
34
34
  instructionMap; // instruction text → node ID
35
+ askFn;
35
36
  constructor(opts) {
36
37
  this.responses = opts.responses;
37
38
  this.routes = opts.routes ?? {};
39
+ this.askFn = opts.ask;
38
40
  // Build reverse map: instruction → node ID (for accurate matching in branching workflows)
39
41
  this.instructionMap = new Map();
40
42
  if (opts.workflow) {
@@ -98,6 +100,11 @@ export class MockClaude {
98
100
  // Default: first choice
99
101
  return opts.choices[0].id;
100
102
  }
103
+ async ask(opts) {
104
+ if (this.askFn)
105
+ return this.askFn(opts.instruction, opts.context);
106
+ return "";
107
+ }
101
108
  /**
102
109
  * Identify which node is being executed.
103
110
  *
package/dist/types.d.ts CHANGED
@@ -73,6 +73,50 @@ export interface NodeVerify {
73
73
  /** Each assertion must hold against `result.data`. */
74
74
  output_matches?: OutputMatch[];
75
75
  }
76
+ /**
77
+ * Machine-checked pre-condition for a node.
78
+ *
79
+ * Evaluated by the executor BEFORE the LLM runs. If any declared check fails,
80
+ * the node is marked `failed` (or `skipped` when `on_fail: "skip"`) and the
81
+ * LLM is never invoked.
82
+ *
83
+ * Path roots resolve against the cross-node context map:
84
+ * { input: <runtime input>, [priorNodeId]: <data of prior node>, ... }
85
+ *
86
+ * Reuses the same path grammar as `verify` (dotted segments, `[*]` wildcard,
87
+ * optional `all:`/`any:` prefix).
88
+ */
89
+ export interface NodeRequires {
90
+ /** Listed paths must be present and non-null in the context map. */
91
+ output_required?: string[];
92
+ /** Each assertion must hold against the context map. */
93
+ output_matches?: OutputMatch[];
94
+ /** Action when checks fail. Default: "fail". */
95
+ on_fail?: "fail" | "skip";
96
+ }
97
+ /**
98
+ * Node-local retry on verify failure.
99
+ *
100
+ * Re-runs the LLM up to `max` additional times, prepending feedback derived
101
+ * from the verify failure. Triggered ONLY by verify failure — not by tool/API
102
+ * errors and not by `requires` failure.
103
+ *
104
+ * `instruction` shapes the feedback preamble:
105
+ * - omitted → default "## Previous attempt failed verification..."
106
+ * - string → static text + verify error
107
+ * - { auto: true } → LLM-generated diagnosis from default reflection prompt
108
+ * - { reflect: s } → LLM-generated diagnosis from author-provided prompt
109
+ */
110
+ export interface NodeRetry {
111
+ /** Maximum number of retry attempts after the initial run. Must be ≥ 1. */
112
+ max: number;
113
+ /** Preamble shape — see interface docs. */
114
+ instruction?: string | {
115
+ auto: true;
116
+ } | {
117
+ reflect: string;
118
+ };
119
+ }
76
120
  /**
77
121
  * A single output assertion. Exactly one of `equals | in | matches` must be set.
78
122
  *
@@ -106,6 +150,10 @@ export interface Node {
106
150
  context?: NodeSources;
107
151
  /** Machine-checked post-conditions. Enforced by the executor after the LLM finishes. */
108
152
  verify?: NodeVerify;
153
+ /** Machine-checked pre-conditions. Enforced by the executor before the LLM runs. */
154
+ requires?: NodeRequires;
155
+ /** Node-local retry on verify failure (with optional autonomous reflection). */
156
+ retry?: NodeRetry;
109
157
  }
110
158
  /** An edge connecting two nodes */
111
159
  export interface Edge {
@@ -182,6 +230,12 @@ export type ExecutionEvent = {
182
230
  type: "node:progress";
183
231
  node: string;
184
232
  message: string;
233
+ } | {
234
+ type: "node:retry";
235
+ node: string;
236
+ attempt: number;
237
+ reason: string;
238
+ preamble: string;
185
239
  } | {
186
240
  type: "route";
187
241
  from: string;
@@ -200,6 +254,8 @@ export interface TraceStep {
200
254
  status: "success" | "failed" | "skipped";
201
255
  /** 1-based iteration count (2 = second time this node ran) */
202
256
  iteration: number;
257
+ /** 0-indexed retry attempt for this iteration. Absent when no retry fired. */
258
+ retryAttempt?: number;
203
259
  }
204
260
  /** A routing decision between nodes */
205
261
  export interface TraceEdge {
@@ -245,6 +301,14 @@ export interface Claude {
245
301
  description: string;
246
302
  }[];
247
303
  }): Promise<string>;
304
+ /**
305
+ * Single-completion free-text query. No tools, no output schema.
306
+ * Used by the executor to generate retry strategies in autonomous reflection mode.
307
+ */
308
+ ask(opts: {
309
+ instruction: string;
310
+ context: Record<string, unknown>;
311
+ }): Promise<string>;
248
312
  }
249
313
  export interface McpServerConfig {
250
314
  type?: "stdio" | "http";
package/dist/verify.d.ts CHANGED
@@ -8,6 +8,7 @@ export type Resolution = {
8
8
  reason: string;
9
9
  };
10
10
  export declare function resolvePath(data: unknown, path: string): Resolution;
11
+ export declare function isErrorOutput(output: unknown): boolean;
11
12
  export declare function checkAnyToolCalled(required: string[], toolCalls: ToolCall[]): string | null;
12
13
  export declare function checkAllToolsCalled(required: string[], toolCalls: ToolCall[]): string | null;
13
14
  export declare function checkNoToolCalled(forbidden: string[], toolCalls: ToolCall[]): string | null;
package/dist/verify.js CHANGED
@@ -72,7 +72,7 @@ export function resolvePath(data, path) {
72
72
  // A tool call "succeeded" when its output does not carry a non-null `error` field.
73
73
  // `{ error: null }` and `{ error: false }` are treated as success — many real
74
74
  // tools include the key with a null sentinel.
75
- function isErrorOutput(output) {
75
+ export function isErrorOutput(output) {
76
76
  if (!output || typeof output !== "object")
77
77
  return false;
78
78
  const err = output.error;
@@ -73,6 +73,9 @@ describe("buildWorkflow", () => {
73
73
  async evaluate() {
74
74
  return "";
75
75
  },
76
+ async ask() {
77
+ return "";
78
+ },
76
79
  };
77
80
  const opts = { claude: capturingClaude, skills: [mockSkill] };
78
81
  await buildWorkflow("Triage Sentry errors", opts);
@@ -108,6 +111,9 @@ describe("refineWorkflow", () => {
108
111
  async evaluate() {
109
112
  return "";
110
113
  },
114
+ async ask() {
115
+ return "";
116
+ },
111
117
  };
112
118
  const opts = { claude: capturingClaude, skills: [mockSkill] };
113
119
  await refineWorkflow(validWorkflow, "Add a notification node", opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"
@@ -60,8 +60,8 @@
60
60
  "zod": "^3.25.76"
61
61
  },
62
62
  "devDependencies": {
63
- "@anthropic-ai/claude-code": "^2.1.92",
64
- "vitest": "^4.1.2"
63
+ "@anthropic-ai/claude-code": "^2.1.114",
64
+ "vitest": "^4.1.4"
65
65
  },
66
66
  "publishConfig": {
67
67
  "access": "public"