@sweny-ai/core 0.1.123 → 0.1.125

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.
Files changed (56) hide show
  1. package/dist/agent-env.d.ts +171 -0
  2. package/dist/agent-env.js +393 -0
  3. package/dist/browser.d.ts +2 -2
  4. package/dist/browser.js +1 -1
  5. package/dist/claude.d.ts +61 -3
  6. package/dist/claude.js +187 -27
  7. package/dist/cli/check.d.ts +42 -1
  8. package/dist/cli/check.js +130 -8
  9. package/dist/cli/cloud-lifecycle.d.ts +24 -29
  10. package/dist/cli/cloud-lifecycle.js +187 -12
  11. package/dist/cli/cloud-report.js +45 -2
  12. package/dist/cli/config-file.d.ts +8 -1
  13. package/dist/cli/config-file.js +42 -1
  14. package/dist/cli/config.js +11 -14
  15. package/dist/cli/e2e.d.ts +22 -0
  16. package/dist/cli/e2e.js +31 -4
  17. package/dist/cli/main.d.ts +1 -0
  18. package/dist/cli/main.js +138 -46
  19. package/dist/cli/marketplace.d.ts +3 -1
  20. package/dist/cli/marketplace.js +2 -2
  21. package/dist/cli/new.d.ts +15 -0
  22. package/dist/cli/new.js +139 -65
  23. package/dist/cli/publish.js +9 -0
  24. package/dist/cli/run-output.d.ts +59 -0
  25. package/dist/cli/run-output.js +189 -0
  26. package/dist/cli/skill-env.d.ts +34 -0
  27. package/dist/cli/skill-env.js +64 -0
  28. package/dist/cli/templates.js +587 -0
  29. package/dist/cli/workflow-input.d.ts +41 -0
  30. package/dist/cli/workflow-input.js +56 -0
  31. package/dist/executor.d.ts +17 -1
  32. package/dist/executor.js +119 -9
  33. package/dist/index.d.ts +3 -3
  34. package/dist/index.js +2 -2
  35. package/dist/mermaid.d.ts +6 -0
  36. package/dist/mermaid.js +10 -5
  37. package/dist/schema.d.ts +218 -188
  38. package/dist/schema.js +24 -3
  39. package/dist/skills/betterstack.js +4 -0
  40. package/dist/skills/datadog.js +3 -0
  41. package/dist/skills/github.js +8 -0
  42. package/dist/skills/linear.js +8 -0
  43. package/dist/skills/notification.js +3 -0
  44. package/dist/skills/sentry.js +4 -0
  45. package/dist/skills/slack.js +2 -0
  46. package/dist/skills/supabase.js +9 -0
  47. package/dist/testing.d.ts +3 -2
  48. package/dist/testing.js +9 -3
  49. package/dist/types.d.ts +99 -2
  50. package/dist/types.js +17 -0
  51. package/dist/untrusted.d.ts +32 -0
  52. package/dist/untrusted.js +46 -0
  53. package/dist/workflows/browser.js +2 -2
  54. package/dist/workflows/implement.yml +2 -0
  55. package/dist/workflows/triage.yml +3 -1
  56. package/package.json +1 -1
package/dist/schema.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * parsing untrusted input (JSON/YAML import, Studio editor).
8
8
  */
9
9
  import { z } from "zod";
10
- import { EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, REQUIRES_ON_FAIL, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, WORKFLOW_TYPES, } from "./types.js";
10
+ import { EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ON_FAIL, REQUIRES_ON_FAIL, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, TOOL_ACCESS, WORKFLOW_TYPES, } from "./types.js";
11
11
  import { sourceZ } from "./sources.js";
12
12
  import { workflowInputsZ, WORKFLOW_INPUT_TYPES } from "./inputs.js";
13
13
  export { sourceZ };
@@ -38,6 +38,7 @@ export const toolZ = z
38
38
  name: z.string().min(1),
39
39
  description: z.string(),
40
40
  input_schema: jsonSchemaZ,
41
+ access: z.enum(TOOL_ACCESS).optional(),
41
42
  })
42
43
  .strict();
43
44
  export const skillCategoryZ = z.enum(SKILL_CATEGORIES);
@@ -221,9 +222,18 @@ export const nodeToolsZ = z
221
222
  });
222
223
  const retryInstructionAutoZ = z.object({ auto: z.literal(true) }).strict();
223
224
  const retryInstructionReflectZ = z.object({ reflect: z.string().min(1) }).strict();
225
+ /**
226
+ * Ceiling on `retry.max`. Each retry attempt re-invokes the agent on the
227
+ * node (full model spend), so an unbounded value here is an unbounded spend
228
+ * multiplier per node, independent of the workflow-level `max_steps` budget.
229
+ * 10 comfortably covers every declared workflow today (max observed: 1) while
230
+ * still catching a fat-fingered value (e.g. a missing digit) at author time
231
+ * instead of at runtime. See #325.
232
+ */
233
+ const NODE_RETRY_MAX_CEILING = 10;
224
234
  export const nodeRetryZ = z
225
235
  .object({
226
- max: z.number().int().min(1),
236
+ max: z.number().int().min(1).max(NODE_RETRY_MAX_CEILING),
227
237
  instruction: z.union([z.string().min(1), retryInstructionAutoZ, retryInstructionReflectZ]).optional(),
228
238
  })
229
239
  .strict();
@@ -237,6 +247,7 @@ export const nodeZ = z
237
247
  disallowed_tools: z.array(z.string().min(1)).optional(),
238
248
  tools: nodeToolsZ.optional(),
239
249
  fail_soft: z.boolean().optional(),
250
+ on_fail: z.enum(NODE_ON_FAIL).optional(),
240
251
  rules: nodeSourcesZ.optional(),
241
252
  context: nodeSourcesZ.optional(),
242
253
  eval: z.array(evaluatorZ).min(1).optional(),
@@ -823,6 +834,11 @@ export const workflowJsonSchema = {
823
834
  type: "boolean",
824
835
  description: "When true, an agent-level failure at this node (max turns, early termination, SDK error) is downgraded to success with fail_soft: true and the error preserved in data; routing proceeds with partial output. Eval failures are not softened. Default false.",
825
836
  },
837
+ on_fail: {
838
+ type: "string",
839
+ enum: [...NODE_ON_FAIL],
840
+ description: "What to do when this node finishes 'failed' (agent-level failure, or an eval failure that exhausted retries and was not softened by fail_soft). 'halt' (default) stops the workflow with the failure surfaced so a broken node never advances down a conditional edge; 'continue' preserves the legacy fall-through where routing proceeds from the failed node. Distinct from requires.on_fail (the pre-condition gate).",
841
+ },
826
842
  rules: {
827
843
  $ref: "#/$defs/NodeSources",
828
844
  description: "Per-node rules. Additive by default; set { only: true } to block cascade.",
@@ -883,7 +899,7 @@ export const workflowJsonSchema = {
883
899
  required: ["max"],
884
900
  additionalProperties: false,
885
901
  properties: {
886
- max: { type: "integer", minimum: 1 },
902
+ max: { type: "integer", minimum: 1, maximum: 10 },
887
903
  instruction: {
888
904
  oneOf: [
889
905
  { type: "string", minLength: 1 },
@@ -1080,6 +1096,11 @@ export const skillJsonSchema = {
1080
1096
  type: "object",
1081
1097
  description: "JSON Schema defining the tool's input parameters.",
1082
1098
  },
1099
+ access: {
1100
+ type: "string",
1101
+ enum: [...TOOL_ACCESS],
1102
+ description: "Side-effect class: read (only reads) or write (creates, updates, deletes, posts, sends). Absent means write. Dry runs pass only read tools to nodes.",
1103
+ },
1083
1104
  },
1084
1105
  },
1085
1106
  McpServerConfig: {
@@ -121,6 +121,7 @@ export const betterstack = {
121
121
  tools: [
122
122
  {
123
123
  name: "betterstack_list_sources",
124
+ access: "read",
124
125
  description: "List available telemetry sources (id, name, table_name, platform)",
125
126
  input_schema: {
126
127
  type: "object",
@@ -143,6 +144,7 @@ export const betterstack = {
143
144
  },
144
145
  {
145
146
  name: "betterstack_get_source",
147
+ access: "read",
146
148
  description: "Get full details for a telemetry source (table name, retention, config)",
147
149
  input_schema: {
148
150
  type: "object",
@@ -158,6 +160,7 @@ export const betterstack = {
158
160
  },
159
161
  {
160
162
  name: "betterstack_get_source_fields",
163
+ access: "read",
161
164
  description: "Get queryable fields for a source table (column names and types)",
162
165
  input_schema: {
163
166
  type: "object",
@@ -172,6 +175,7 @@ export const betterstack = {
172
175
  },
173
176
  {
174
177
  name: "betterstack_query",
178
+ access: "read",
175
179
  description: `Execute a read-only ClickHouse SQL query against a telemetry source.
176
180
  Tables: remote(TABLE_logs) for recent logs, s3Cluster(primary, TABLE_s3) for historical (add WHERE _row_type = 1).
177
181
  Key fields: dt (timestamp), raw (JSON blob with all log fields).
@@ -45,6 +45,7 @@ export const datadog = {
45
45
  tools: [
46
46
  {
47
47
  name: "datadog_search_logs",
48
+ access: "read",
48
49
  description: "Search logs in Datadog",
49
50
  input_schema: {
50
51
  type: "object",
@@ -72,6 +73,7 @@ export const datadog = {
72
73
  },
73
74
  {
74
75
  name: "datadog_query_metrics",
76
+ access: "read",
75
77
  description: "Query time-series metrics from Datadog",
76
78
  input_schema: {
77
79
  type: "object",
@@ -86,6 +88,7 @@ export const datadog = {
86
88
  },
87
89
  {
88
90
  name: "datadog_list_monitors",
91
+ access: "read",
89
92
  description: "List Datadog monitors, optionally filtered by tag or name",
90
93
  input_schema: {
91
94
  type: "object",
@@ -75,6 +75,7 @@ export const github = {
75
75
  tools: [
76
76
  {
77
77
  name: "github_search_code",
78
+ access: "read",
78
79
  description: "Search for code in a GitHub repository",
79
80
  input_schema: {
80
81
  type: "object",
@@ -88,6 +89,7 @@ export const github = {
88
89
  },
89
90
  {
90
91
  name: "github_get_issue",
92
+ access: "read",
91
93
  description: "Get details of a GitHub issue",
92
94
  input_schema: {
93
95
  type: "object",
@@ -101,6 +103,7 @@ export const github = {
101
103
  },
102
104
  {
103
105
  name: "github_search_issues",
106
+ access: "read",
104
107
  description: "Search issues and pull requests",
105
108
  input_schema: {
106
109
  type: "object",
@@ -117,6 +120,7 @@ export const github = {
117
120
  },
118
121
  {
119
122
  name: "github_create_issue",
123
+ access: "write",
120
124
  description: "Create a new GitHub issue",
121
125
  input_schema: {
122
126
  type: "object",
@@ -135,6 +139,7 @@ export const github = {
135
139
  },
136
140
  {
137
141
  name: "github_add_comment",
142
+ access: "write",
138
143
  description: "Add a comment to a GitHub issue or pull request",
139
144
  input_schema: {
140
145
  type: "object",
@@ -152,6 +157,7 @@ export const github = {
152
157
  },
153
158
  {
154
159
  name: "github_create_pr",
160
+ access: "write",
155
161
  description: "Create a pull request",
156
162
  input_schema: {
157
163
  type: "object",
@@ -225,6 +231,7 @@ export const github = {
225
231
  },
226
232
  {
227
233
  name: "github_list_recent_commits",
234
+ access: "read",
228
235
  description: "List recent commits on a branch",
229
236
  input_schema: {
230
237
  type: "object",
@@ -239,6 +246,7 @@ export const github = {
239
246
  },
240
247
  {
241
248
  name: "github_get_file",
249
+ access: "read",
242
250
  description: "Get a file's contents from a repository",
243
251
  input_schema: {
244
252
  type: "object",
@@ -35,6 +35,7 @@ export const linear = {
35
35
  tools: [
36
36
  {
37
37
  name: "linear_create_issue",
38
+ access: "write",
38
39
  description: "Create a new Linear issue",
39
40
  input_schema: {
40
41
  type: "object",
@@ -57,6 +58,7 @@ export const linear = {
57
58
  },
58
59
  {
59
60
  name: "linear_search_issues",
61
+ access: "read",
60
62
  description: "Search Linear issues by text query",
61
63
  input_schema: {
62
64
  type: "object",
@@ -74,6 +76,7 @@ export const linear = {
74
76
  },
75
77
  {
76
78
  name: "linear_add_comment",
79
+ access: "write",
77
80
  description: "Add a comment to a Linear issue",
78
81
  input_schema: {
79
82
  type: "object",
@@ -87,6 +90,7 @@ export const linear = {
87
90
  },
88
91
  {
89
92
  name: "linear_get_issue",
93
+ access: "read",
90
94
  description: "Get a Linear issue by ID or identifier (e.g. 'OFF-1020')",
91
95
  input_schema: {
92
96
  type: "object",
@@ -109,6 +113,7 @@ export const linear = {
109
113
  },
110
114
  {
111
115
  name: "linear_list_comments",
116
+ access: "read",
112
117
  description: "List comments on a Linear issue (returns id, body, author, and timestamp for each)",
113
118
  input_schema: {
114
119
  type: "object",
@@ -134,6 +139,7 @@ export const linear = {
134
139
  },
135
140
  {
136
141
  name: "linear_list_teams",
142
+ access: "read",
137
143
  description: "List Linear teams (needed for teamId when creating issues)",
138
144
  input_schema: {
139
145
  type: "object",
@@ -147,6 +153,7 @@ export const linear = {
147
153
  },
148
154
  {
149
155
  name: "linear_list_labels",
156
+ access: "read",
150
157
  description: "List issue labels for a Linear team — returns id, name, and color for each label",
151
158
  input_schema: {
152
159
  type: "object",
@@ -166,6 +173,7 @@ export const linear = {
166
173
  },
167
174
  {
168
175
  name: "linear_update_issue",
176
+ access: "write",
169
177
  description: "Update an existing Linear issue",
170
178
  input_schema: {
171
179
  type: "object",
@@ -95,6 +95,7 @@ export const notification = {
95
95
  tools: [
96
96
  {
97
97
  name: "notify_webhook",
98
+ access: "write",
98
99
  description: "Send a JSON payload to the configured notification webhook (NOTIFICATION_WEBHOOK_URL). " +
99
100
  "The destination is fixed: an optional `url` is only honored when its host is on the " +
100
101
  "NOTIFICATION_WEBHOOK_ALLOWED_HOSTS allowlist; arbitrary hosts are rejected.",
@@ -125,6 +126,7 @@ export const notification = {
125
126
  },
126
127
  {
127
128
  name: "notify_discord",
129
+ access: "write",
128
130
  description: "Send a message to Discord via webhook",
129
131
  input_schema: {
130
132
  type: "object",
@@ -171,6 +173,7 @@ export const notification = {
171
173
  },
172
174
  {
173
175
  name: "notify_teams",
176
+ access: "write",
174
177
  description: "Send a message to Microsoft Teams via webhook",
175
178
  input_schema: {
176
179
  type: "object",
@@ -38,6 +38,7 @@ export const sentry = {
38
38
  tools: [
39
39
  {
40
40
  name: "sentry_list_issues",
41
+ access: "read",
41
42
  description: "List recent issues for a Sentry project",
42
43
  input_schema: {
43
44
  type: "object",
@@ -54,6 +55,7 @@ export const sentry = {
54
55
  },
55
56
  {
56
57
  name: "sentry_get_issue",
58
+ access: "read",
57
59
  description: "Get detailed information about a Sentry issue",
58
60
  input_schema: {
59
61
  type: "object",
@@ -66,6 +68,7 @@ export const sentry = {
66
68
  },
67
69
  {
68
70
  name: "sentry_get_issue_events",
71
+ access: "read",
69
72
  description: "Get recent events (occurrences) for a Sentry issue",
70
73
  input_schema: {
71
74
  type: "object",
@@ -78,6 +81,7 @@ export const sentry = {
78
81
  },
79
82
  {
80
83
  name: "sentry_search_events",
84
+ access: "read",
81
85
  description: "Search events across a project using Discover query syntax",
82
86
  input_schema: {
83
87
  type: "object",
@@ -24,6 +24,7 @@ export const slack = {
24
24
  tools: [
25
25
  {
26
26
  name: "slack_send_message",
27
+ access: "write",
27
28
  description: "Send a message to a Slack channel via webhook or API",
28
29
  input_schema: {
29
30
  type: "object",
@@ -77,6 +78,7 @@ export const slack = {
77
78
  },
78
79
  {
79
80
  name: "slack_send_thread_reply",
81
+ access: "write",
80
82
  description: "Reply to an existing Slack message thread",
81
83
  input_schema: {
82
84
  type: "object",
@@ -144,6 +144,7 @@ export const supabase = {
144
144
  // ─── Read ────────────────────────────────────────────────
145
145
  {
146
146
  name: "supabase_query",
147
+ access: "read",
147
148
  description: "Query rows from a Supabase table. Uses PostgREST query syntax. " + "Returns an array of matching rows.",
148
149
  input_schema: {
149
150
  type: "object",
@@ -182,6 +183,7 @@ export const supabase = {
182
183
  },
183
184
  {
184
185
  name: "supabase_count",
186
+ access: "read",
185
187
  description: "Count rows in a table, optionally with filters.",
186
188
  input_schema: {
187
189
  type: "object",
@@ -221,6 +223,7 @@ export const supabase = {
221
223
  // ─── Write ───────────────────────────────────────────────
222
224
  {
223
225
  name: "supabase_insert",
226
+ access: "write",
224
227
  description: "Insert one or more rows into a table. Returns the inserted rows. " +
225
228
  "Use upsert=true to update existing rows on conflict.",
226
229
  input_schema: {
@@ -254,6 +257,7 @@ export const supabase = {
254
257
  },
255
258
  {
256
259
  name: "supabase_update",
260
+ access: "write",
257
261
  description: "Update rows matching filters. Returns the updated rows.",
258
262
  input_schema: {
259
263
  type: "object",
@@ -285,6 +289,7 @@ export const supabase = {
285
289
  },
286
290
  {
287
291
  name: "supabase_delete",
292
+ access: "write",
288
293
  description: "Delete rows matching filters. Returns the deleted rows.",
289
294
  input_schema: {
290
295
  type: "object",
@@ -310,6 +315,7 @@ export const supabase = {
310
315
  // ─── RPC ─────────────────────────────────────────────────
311
316
  {
312
317
  name: "supabase_rpc",
318
+ access: "write",
313
319
  description: "Call a Supabase RPC (stored procedure / database function).",
314
320
  input_schema: {
315
321
  type: "object",
@@ -332,6 +338,7 @@ export const supabase = {
332
338
  // ─── Edge Functions ──────────────────────────────────────
333
339
  {
334
340
  name: "supabase_invoke_function",
341
+ access: "write",
335
342
  description: "Invoke a Supabase Edge Function by name with a JSON body.",
336
343
  input_schema: {
337
344
  type: "object",
@@ -354,6 +361,7 @@ export const supabase = {
354
361
  // ─── Auth Admin ──────────────────────────────────────────
355
362
  {
356
363
  name: "supabase_list_users",
364
+ access: "read",
357
365
  description: "List auth users. Returns user IDs, emails, and metadata.",
358
366
  input_schema: {
359
367
  type: "object",
@@ -383,6 +391,7 @@ export const supabase = {
383
391
  // ─── Schema Introspection ────────────────────────────────
384
392
  {
385
393
  name: "supabase_list_tables",
394
+ access: "read",
386
395
  description: "List all public tables with row counts. Useful for content auditing.",
387
396
  input_schema: {
388
397
  type: "object",
package/dist/testing.d.ts CHANGED
@@ -46,7 +46,8 @@ export interface MockClaudeOptions {
46
46
  *
47
47
  * For each node, it executes scripted tool calls and returns
48
48
  * scripted results. For routing decisions, it follows the
49
- * routes map or defaults to the first choice.
49
+ * routes map. With no scripted route (or an invalid one) it returns
50
+ * `null`, mirroring the real client failing closed on a route-eval failure.
50
51
  */
51
52
  export declare class MockClaude implements Claude {
52
53
  private callOrder;
@@ -70,7 +71,7 @@ export declare class MockClaude implements Claude {
70
71
  id: string;
71
72
  description: string;
72
73
  }[];
73
- }): Promise<string>;
74
+ }): Promise<string | null>;
74
75
  ask(opts: {
75
76
  instruction: string;
76
77
  context: Record<string, unknown>;
package/dist/testing.js CHANGED
@@ -25,7 +25,8 @@ import { consoleLogger } from "./types.js";
25
25
  *
26
26
  * For each node, it executes scripted tool calls and returns
27
27
  * scripted results. For routing decisions, it follows the
28
- * routes map or defaults to the first choice.
28
+ * routes map. With no scripted route (or an invalid one) it returns
29
+ * `null`, mirroring the real client failing closed on a route-eval failure.
29
30
  */
30
31
  export class MockClaude {
31
32
  callOrder = [];
@@ -97,8 +98,8 @@ export class MockClaude {
97
98
  return route;
98
99
  }
99
100
  }
100
- // Default: first choice
101
- return opts.choices[0].id;
101
+ // No scripted route: fail closed (same as ClaudeClient.evaluate).
102
+ return null;
102
103
  }
103
104
  async ask(opts) {
104
105
  if (this.askFn)
@@ -164,6 +165,7 @@ export function createFileSkill(outputDir) {
164
165
  tools: [
165
166
  {
166
167
  name: "fs_read_json",
168
+ access: "read",
167
169
  description: "Read and parse a JSON file",
168
170
  input_schema: {
169
171
  type: "object",
@@ -182,6 +184,7 @@ export function createFileSkill(outputDir) {
182
184
  },
183
185
  {
184
186
  name: "fs_read_text",
187
+ access: "read",
185
188
  description: "Read a text file",
186
189
  input_schema: {
187
190
  type: "object",
@@ -199,6 +202,7 @@ export function createFileSkill(outputDir) {
199
202
  },
200
203
  {
201
204
  name: "fs_write_json",
205
+ access: "write",
202
206
  description: "Write a JSON object to a file",
203
207
  input_schema: {
204
208
  type: "object",
@@ -219,6 +223,7 @@ export function createFileSkill(outputDir) {
219
223
  },
220
224
  {
221
225
  name: "fs_write_markdown",
226
+ access: "write",
222
227
  description: "Write a markdown file (for issues, PRs, notifications)",
223
228
  input_schema: {
224
229
  type: "object",
@@ -239,6 +244,7 @@ export function createFileSkill(outputDir) {
239
244
  },
240
245
  {
241
246
  name: "fs_list_dir",
247
+ access: "read",
242
248
  description: "List files in a directory",
243
249
  input_schema: {
244
250
  type: "object",
package/dist/types.d.ts CHANGED
@@ -10,11 +10,23 @@ export interface ToolContext {
10
10
  /** Structured logger */
11
11
  logger: Logger;
12
12
  }
13
+ /**
14
+ * Side-effect class of a tool. `"read"` only reads. `"write"` can change
15
+ * something outside the run: create, update, delete, post, send, invoke.
16
+ */
17
+ export declare const TOOL_ACCESS: readonly ["read", "write"];
18
+ export type ToolAccess = (typeof TOOL_ACCESS)[number];
13
19
  /** A single tool Claude can invoke */
14
20
  export interface Tool {
15
21
  name: string;
16
22
  description: string;
17
23
  input_schema: JSONSchema;
24
+ /**
25
+ * Side-effect class. Under dry-run (`input.dryRun === true`) the executor
26
+ * passes only `"read"` tools to a node. Absent means `"write"`, so a new or
27
+ * unclassified tool fails safe: it is withheld from dry runs.
28
+ */
29
+ access?: ToolAccess;
18
30
  handler: (input: any, ctx: ToolContext) => Promise<unknown>;
19
31
  }
20
32
  /** Config field declaration — skills say what they need */
@@ -127,6 +139,19 @@ export type EvalPolicy = (typeof EVAL_POLICIES)[number];
127
139
  /** Action when a `requires` precondition fails. */
128
140
  export declare const REQUIRES_ON_FAIL: readonly ["fail", "skip"];
129
141
  export type RequiresOnFail = (typeof REQUIRES_ON_FAIL)[number];
142
+ /**
143
+ * Post-execution failure policy for a node (the top-level `on_fail` field).
144
+ *
145
+ * Distinct from {@link REQUIRES_ON_FAIL}, which governs the pre-condition
146
+ * `requires` gate (`fail | skip`). This one governs what happens AFTER the
147
+ * node ran and its retry budget is exhausted with `status: "failed"`:
148
+ * - `halt` (default) — stop the workflow, leaving the failed node in the
149
+ * results so the run surfaces as failed. Fails closed: a broken
150
+ * node never advances down a conditional edge.
151
+ * - `continue` — legacy fall-through: routing proceeds from the failed node.
152
+ */
153
+ export declare const NODE_ON_FAIL: readonly ["halt", "continue"];
154
+ export type NodeOnFail = (typeof NODE_ON_FAIL)[number];
130
155
  /** MCP server transport type. Inferred from command/url when omitted. */
131
156
  export declare const MCP_TRANSPORTS: readonly ["stdio", "http"];
132
157
  export type McpTransport = (typeof MCP_TRANSPORTS)[number];
@@ -321,6 +346,21 @@ export interface Node {
321
346
  * Default false (previous behavior, back-compat).
322
347
  */
323
348
  fail_soft?: boolean;
349
+ /**
350
+ * What to do when this node finishes with `status: "failed"` (agent-level
351
+ * failure or an eval failure that exhausted the retry budget and was not
352
+ * softened by `fail_soft`).
353
+ *
354
+ * - `"halt"` (default) — stop the workflow. The failed node stays in the
355
+ * results so the run surfaces as failed; routing does NOT proceed. This
356
+ * fails closed: a broken node can never take a conditional out-edge (e.g.
357
+ * file an issue/PR) on the back of a failure.
358
+ * - `"continue"` — legacy fall-through: routing proceeds from the failed
359
+ * node, letting a downstream branch inspect or recover from the failure.
360
+ *
361
+ * Distinct from `requires.on_fail`, which is the pre-condition gate.
362
+ */
363
+ on_fail?: NodeOnFail;
324
364
  }
325
365
  /**
326
366
  * Per-node skill-tool filter. See {@link Node.tools}.
@@ -396,6 +436,29 @@ export interface SkillDefinition {
396
436
  /** External MCP server. */
397
437
  mcp?: McpServerConfig;
398
438
  }
439
+ /**
440
+ * Model usage + cost accounting for a single node's AI invocation.
441
+ *
442
+ * SHAPE-ONLY telemetry: counts, cost, and turn totals. Never any prompt,
443
+ * response, or tool payload. Populated from the Claude Agent SDK's terminal
444
+ * `result` message (`total_cost_usd`, `usage`, `num_turns`). Absent when the
445
+ * node made no AI call (e.g. a deterministic node) or when running against a
446
+ * mock/older SDK that doesn't emit the fields.
447
+ */
448
+ export interface NodeUsage {
449
+ /** Aggregate USD cost the SDK billed for this node's turn(s). */
450
+ costUsd?: number;
451
+ /** Input (prompt) tokens across the node's turns. */
452
+ inputTokens?: number;
453
+ /** Output (completion) tokens across the node's turns. */
454
+ outputTokens?: number;
455
+ /** Cache-read input tokens (prompt-cache hits). */
456
+ cacheReadTokens?: number;
457
+ /** Cache-creation input tokens (prompt-cache writes). */
458
+ cacheCreationTokens?: number;
459
+ /** Number of model turns the SDK ran for this node. */
460
+ numTurns?: number;
461
+ }
399
462
  export interface NodeResult {
400
463
  status: "success" | "skipped" | "failed";
401
464
  /** Arbitrary data produced by this node */
@@ -408,6 +471,17 @@ export interface NodeResult {
408
471
  * eval was not run (e.g. node failed during execution).
409
472
  */
410
473
  evals?: EvalResult[];
474
+ /**
475
+ * Token + cost accounting for this node's AI invocation. Shape-only
476
+ * telemetry; see {@link NodeUsage}. Absent for nodes that made no AI call.
477
+ */
478
+ usage?: NodeUsage;
479
+ /**
480
+ * Dry-run only: names of the node's skill tools that were withheld because
481
+ * they are write-capable or unclassified. Absent on normal runs and on
482
+ * dry-run nodes that had no write tools.
483
+ */
484
+ skippedWrites?: string[];
411
485
  }
412
486
  export interface ToolCall {
413
487
  tool: string;
@@ -540,8 +614,31 @@ export interface Claude {
540
614
  timeoutMs?: number;
541
615
  /** Caller-supplied abort signal. Aborting it interrupts the query. */
542
616
  signal?: AbortSignal;
617
+ /**
618
+ * What this node's agent may see (#360): env var names declared by the
619
+ * node's skills (added to the scoped subprocess env) and the provider
620
+ * hosts its sandboxed commands may reach. Absent = allowlist only.
621
+ */
622
+ agentAccess?: {
623
+ envVars: string[];
624
+ domains: string[];
625
+ };
626
+ /**
627
+ * Dry-run: the node must not change anything. `tools` is already filtered
628
+ * to reads; implementations MUST NOT add any other write-capable tool
629
+ * (external MCP servers, shell, file-edit built-ins).
630
+ */
631
+ readOnly?: boolean;
543
632
  }): Promise<NodeResult>;
544
- /** Evaluate a routing condition — pick one of N choices */
633
+ /**
634
+ * Evaluate a routing condition — pick one of N choices.
635
+ *
636
+ * Fails closed: returns `null` when the routing decision could not be made
637
+ * (SDK error, timeout, non-success subtype, or an unparseable answer).
638
+ * Callers MUST treat `null` as "no decision" and take an explicit default
639
+ * edge or terminate — never fall through to the first choice. Returning a
640
+ * choice id on that path is the fail-open bug this contract exists to close.
641
+ */
545
642
  evaluate(opts: {
546
643
  question: string;
547
644
  context: Record<string, unknown>;
@@ -553,7 +650,7 @@ export interface Claude {
553
650
  timeoutMs?: number;
554
651
  /** Caller-supplied abort signal. Aborting it interrupts the query. */
555
652
  signal?: AbortSignal;
556
- }): Promise<string>;
653
+ }): Promise<string | null>;
557
654
  /**
558
655
  * Single-completion free-text query. No tools, no output schema.
559
656
  * Used by the executor to generate retry strategies in autonomous reflection mode
package/dist/types.js CHANGED
@@ -1,4 +1,9 @@
1
1
  export { WORKFLOW_INPUT_TYPES } from "./inputs.js";
2
+ /**
3
+ * Side-effect class of a tool. `"read"` only reads. `"write"` can change
4
+ * something outside the run: create, update, delete, post, send, invoke.
5
+ */
6
+ export const TOOL_ACCESS = ["read", "write"];
2
7
  /**
3
8
  * Skill categories: used for per-node validation and grouping.
4
9
  *
@@ -48,6 +53,18 @@ export const EVALUATOR_KINDS = ["value", "function", "judge"];
48
53
  export const EVAL_POLICIES = ["all_pass", "any_pass", "weighted"];
49
54
  /** Action when a `requires` precondition fails. */
50
55
  export const REQUIRES_ON_FAIL = ["fail", "skip"];
56
+ /**
57
+ * Post-execution failure policy for a node (the top-level `on_fail` field).
58
+ *
59
+ * Distinct from {@link REQUIRES_ON_FAIL}, which governs the pre-condition
60
+ * `requires` gate (`fail | skip`). This one governs what happens AFTER the
61
+ * node ran and its retry budget is exhausted with `status: "failed"`:
62
+ * - `halt` (default) — stop the workflow, leaving the failed node in the
63
+ * results so the run surfaces as failed. Fails closed: a broken
64
+ * node never advances down a conditional edge.
65
+ * - `continue` — legacy fall-through: routing proceeds from the failed node.
66
+ */
67
+ export const NODE_ON_FAIL = ["halt", "continue"];
51
68
  /** MCP server transport type. Inferred from command/url when omitted. */
52
69
  export const MCP_TRANSPORTS = ["stdio", "http"];
53
70
  /**