@sweny-ai/core 0.1.177 → 0.1.179
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/agent-env.d.ts +5 -3
- package/dist/agent-env.js +12 -7
- package/dist/cli/config-file.d.ts +22 -1
- package/dist/cli/config-file.js +42 -0
- package/dist/cli/final-output.js +4 -1
- package/dist/cli/main.d.ts +2 -1
- package/dist/cli/main.js +7 -8
- package/dist/cli/resume.d.ts +10 -1
- package/dist/cli/resume.js +77 -7
- package/dist/cli/run-history.d.ts +3 -7
- package/dist/cli/run-history.js +1 -10
- package/dist/cli/run-output.d.ts +16 -10
- package/dist/cli/run-output.js +19 -13
- package/dist/cli/ticket.js +3 -2
- package/dist/decider.d.ts +176 -64
- package/dist/decider.js +475 -155
- package/dist/executor.d.ts +50 -7
- package/dist/executor.js +533 -93
- package/dist/harness/acp.js +2 -0
- package/dist/harness/claude-code.d.ts +18 -0
- package/dist/harness/claude-code.js +51 -6
- package/dist/harness/codex.js +2 -0
- package/dist/harness/pi.js +2 -0
- package/dist/harness/sandbox-wrapper.js +8 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/journal.d.ts +258 -74
- package/dist/journal.js +749 -208
- package/dist/schema.d.ts +189 -241
- package/dist/schema.js +56 -36
- package/dist/skills/github.js +39 -0
- package/dist/types.d.ts +55 -3
- package/dist/types.js +22 -0
- package/dist/when.d.ts +8 -1
- package/dist/when.js +8 -1
- package/dist/workflow-builder.js +3 -1
- package/dist/workflows/browser.js +2 -2
- package/dist/workflows/implement.yml +37 -5
- package/dist/workflows/triage.yml +40 -6
- 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 { AUTHOR_ASSOCIATIONS, CONTEXT_MODES, EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ACCESS, NODE_ON_FAIL, REQUIRES_ON_FAIL, SAFE_OUTPUT_APPLIERS, SAFE_OUTPUT_EXPIRES_PATTERN, SAFE_OUTPUT_MAX_CEILING, SAFE_OUTPUT_STATES, SAFE_OUTPUT_TYPES, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, TOOL_ACCESS, TOOL_CLASSES, WORKFLOW_TYPES, } from "./types.js";
|
|
10
|
+
import { AUTHOR_ASSOCIATIONS, CONTEXT_MODES, DECIDER_CONFIDENCE_FLOOR, DECIDER_MARGIN_FLOOR, DECIDER_MIN_CONFIDENCE, DECIDER_MIN_MARGIN, DECIDER_MODE_REMOVED, DECIDER_PROVIDER_MOVED, EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ACCESS, NODE_ON_FAIL, REQUIRES_ON_FAIL, ROUTE_BY, SAFE_OUTPUT_APPLIERS, SAFE_OUTPUT_EXPIRES_PATTERN, SAFE_OUTPUT_MAX_CEILING, SAFE_OUTPUT_STATES, SAFE_OUTPUT_TYPES, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, TOOL_ACCESS, TOOL_CLASSES, WORKFLOW_TYPES, } from "./types.js";
|
|
11
11
|
import { sourceZ } from "./sources.js";
|
|
12
12
|
import { resolveNodePermissions } from "./node-policy.js";
|
|
13
13
|
import { workflowInputsZ, WORKFLOW_INPUT_TYPES } from "./inputs.js";
|
|
@@ -280,26 +280,22 @@ export const budgetZ = z
|
|
|
280
280
|
message: "budget must declare at least one of tokens, cost_usd",
|
|
281
281
|
});
|
|
282
282
|
/**
|
|
283
|
-
* Decision model
|
|
284
|
-
*
|
|
285
|
-
*
|
|
283
|
+
* Decision model thresholds (#357) for the nodes that set `route_by:
|
|
284
|
+
* decider`. Thresholds may be raised, and lowered no further than the floors.
|
|
285
|
+
* The provider (endpoint, model, key) comes only from operator config.
|
|
286
|
+
*
|
|
287
|
+
* Shapes from the shadow-mode release fail with an explanation instead of
|
|
288
|
+
* silently changing routing: `mode` ({@link DECIDER_MODE_REMOVED}) and
|
|
289
|
+
* `provider` ({@link DECIDER_PROVIDER_MOVED}).
|
|
286
290
|
*/
|
|
287
291
|
export const deciderZ = z
|
|
288
292
|
.object({
|
|
289
|
-
mode: z.
|
|
290
|
-
provider: z
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
model: z.string().min(1),
|
|
294
|
-
api_key_env: z.string().min(1).optional(),
|
|
295
|
-
})
|
|
296
|
-
.strict()
|
|
297
|
-
.optional(),
|
|
293
|
+
mode: z.custom(() => false, { message: DECIDER_MODE_REMOVED }).optional(),
|
|
294
|
+
provider: z.custom(() => false, { message: DECIDER_PROVIDER_MOVED }).optional(),
|
|
295
|
+
min_confidence: z.number().min(DECIDER_CONFIDENCE_FLOOR).max(1).optional(),
|
|
296
|
+
min_margin: z.number().min(DECIDER_MARGIN_FLOOR).max(1).optional(),
|
|
298
297
|
})
|
|
299
|
-
.strict()
|
|
300
|
-
.refine((d) => d.mode === "off" || d.provider !== undefined, {
|
|
301
|
-
message: "decider.provider is required when mode is shadow",
|
|
302
|
-
});
|
|
298
|
+
.strict();
|
|
303
299
|
/** One typed write intent a node may emit (#365). */
|
|
304
300
|
export const safeOutputDeclarationZ = z
|
|
305
301
|
.object({
|
|
@@ -342,6 +338,7 @@ export const nodeZ = z
|
|
|
342
338
|
tools: nodeToolsZ.optional(),
|
|
343
339
|
fail_soft: z.boolean().optional(),
|
|
344
340
|
on_fail: z.enum(NODE_ON_FAIL).optional(),
|
|
341
|
+
route_by: z.enum(ROUTE_BY).optional(),
|
|
345
342
|
permissions: nodePermissionsZ.optional(),
|
|
346
343
|
outputs: z.array(safeOutputDeclarationZ).min(1).optional(),
|
|
347
344
|
rules: nodeSourcesZ.optional(),
|
|
@@ -361,6 +358,7 @@ export const edgeZ = z
|
|
|
361
358
|
// Natural language (the harness picks the edge) or `{ expr }`, a
|
|
362
359
|
// deterministic expression sweny evaluates itself (#461). One or the other.
|
|
363
360
|
when: z.union([z.string(), z.object({ expr: z.string().min(1).max(WHEN_EXPR_MAX_LENGTH) }).strict()]).optional(),
|
|
361
|
+
description: z.string().min(1).optional(),
|
|
364
362
|
max_iterations: z.number().int().min(1).max(EDGE_MAX_ITERATIONS_CEILING).optional(),
|
|
365
363
|
})
|
|
366
364
|
.strict();
|
|
@@ -478,6 +476,22 @@ export function validateWorkflow(workflow, knownSkills) {
|
|
|
478
476
|
else
|
|
479
477
|
m.set(k, [v]);
|
|
480
478
|
};
|
|
479
|
+
// One edge per (from, to) pair (#357). A route picks a target node, so two
|
|
480
|
+
// edges to the same target would make their conditions ambiguous (which
|
|
481
|
+
// one's max_iterations counts, which one's condition the router saw). Write
|
|
482
|
+
// one edge whose condition covers both cases instead.
|
|
483
|
+
const seenPairs = new Set();
|
|
484
|
+
for (const edge of workflow.edges) {
|
|
485
|
+
const key = JSON.stringify([edge.from, edge.to]);
|
|
486
|
+
if (seenPairs.has(key)) {
|
|
487
|
+
errors.push({
|
|
488
|
+
code: "DUPLICATE_EDGE",
|
|
489
|
+
message: `More than one edge from "${edge.from}" to "${edge.to}"; merge them into one edge (combine the conditions with OR)`,
|
|
490
|
+
nodeId: edge.from,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
seenPairs.add(key);
|
|
494
|
+
}
|
|
481
495
|
// Edge targets must exist
|
|
482
496
|
for (const edge of workflow.edges) {
|
|
483
497
|
if (!nodeIds.has(edge.from)) {
|
|
@@ -1000,28 +1014,24 @@ export const workflowJsonSchema = {
|
|
|
1000
1014
|
},
|
|
1001
1015
|
Decider: {
|
|
1002
1016
|
type: "object",
|
|
1003
|
-
required: ["mode"],
|
|
1004
1017
|
additionalProperties: false,
|
|
1005
|
-
description: "Decision model for
|
|
1018
|
+
description: "Decision model thresholds for the nodes that set route_by: decider. The provider (URL, model, key) comes only from operator config (.sweny.yml or SWENY_DECIDER_* env), never from the workflow; 'provider' and the removed 'mode' key are rejected.",
|
|
1006
1019
|
properties: {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
},
|
|
1020
|
+
min_confidence: {
|
|
1021
|
+
type: "number",
|
|
1022
|
+
minimum: DECIDER_CONFIDENCE_FLOOR,
|
|
1023
|
+
maximum: 1,
|
|
1024
|
+
default: DECIDER_MIN_CONFIDENCE,
|
|
1025
|
+
description: "Accept the decider's answer only at or above this confidence.",
|
|
1026
|
+
},
|
|
1027
|
+
min_margin: {
|
|
1028
|
+
type: "number",
|
|
1029
|
+
minimum: DECIDER_MARGIN_FLOOR,
|
|
1030
|
+
maximum: 1,
|
|
1031
|
+
default: DECIDER_MIN_MARGIN,
|
|
1032
|
+
description: "Accept it only when the top label leads the runner-up by at least this much.",
|
|
1021
1033
|
},
|
|
1022
1034
|
},
|
|
1023
|
-
if: { properties: { mode: { const: "shadow" } } },
|
|
1024
|
-
then: { required: ["provider"] },
|
|
1025
1035
|
},
|
|
1026
1036
|
SafeOutput: {
|
|
1027
1037
|
type: "object",
|
|
@@ -1174,7 +1184,7 @@ export const workflowJsonSchema = {
|
|
|
1174
1184
|
},
|
|
1175
1185
|
decider: {
|
|
1176
1186
|
$ref: "#/$defs/Decider",
|
|
1177
|
-
description: "
|
|
1187
|
+
description: "Thresholds for the decision model that decides the routes of route_by: decider nodes when confident. Default: 0.85 confidence, 0.2 margin.",
|
|
1178
1188
|
},
|
|
1179
1189
|
context_mode: {
|
|
1180
1190
|
type: "string",
|
|
@@ -1335,6 +1345,11 @@ export const workflowJsonSchema = {
|
|
|
1335
1345
|
enum: [...NODE_ON_FAIL],
|
|
1336
1346
|
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).",
|
|
1337
1347
|
},
|
|
1348
|
+
route_by: {
|
|
1349
|
+
type: "string",
|
|
1350
|
+
enum: [...ROUTE_BY],
|
|
1351
|
+
description: "Who picks this node's natural-language route. 'agent' (default): the agent's route evaluation. 'decider': the operator's decision model first, the agent when it is not confident. Expressions always win over both; put safety conditions in expressions.",
|
|
1352
|
+
},
|
|
1338
1353
|
permissions: {
|
|
1339
1354
|
$ref: "#/$defs/Permissions",
|
|
1340
1355
|
description: "What this node's agent may do. Absent: read when the node declares outputs, else the workflow's permissions, else write.",
|
|
@@ -1456,6 +1471,11 @@ export const workflowJsonSchema = {
|
|
|
1456
1471
|
},
|
|
1457
1472
|
],
|
|
1458
1473
|
},
|
|
1474
|
+
description: {
|
|
1475
|
+
type: "string",
|
|
1476
|
+
minLength: 1,
|
|
1477
|
+
description: "The condition in natural language. For an { expr } edge, what the next rung reads when the expression cannot be evaluated because a field it needs is missing or invalid.",
|
|
1478
|
+
},
|
|
1459
1479
|
max_iterations: {
|
|
1460
1480
|
type: "integer",
|
|
1461
1481
|
minimum: 1,
|
package/dist/skills/github.js
CHANGED
|
@@ -181,6 +181,45 @@ export const github = {
|
|
|
181
181
|
return gh(`/search/issues?q=${encodeURIComponent(q)}&per_page=20`, ctx);
|
|
182
182
|
},
|
|
183
183
|
},
|
|
184
|
+
{
|
|
185
|
+
name: "github_list_issues",
|
|
186
|
+
access: "read",
|
|
187
|
+
description: "List a repository's most recently created issues (open and closed), newest first",
|
|
188
|
+
input_schema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
repo: { type: "string", description: "owner/repo" },
|
|
192
|
+
per_page: { type: "number", description: "How many per page (max 100, default 30)" },
|
|
193
|
+
page: { type: "number", description: "Page number, from 1 (default 1)" },
|
|
194
|
+
},
|
|
195
|
+
required: ["repo"],
|
|
196
|
+
},
|
|
197
|
+
handler: async (input, ctx) => {
|
|
198
|
+
const n = Math.min(100, Math.max(1, Math.floor(input.per_page ?? 30)));
|
|
199
|
+
const page = Math.max(1, Math.floor(input.page ?? 1));
|
|
200
|
+
return gh(`/repos/${input.repo}/issues?state=all&sort=created&direction=desc&per_page=${n}&page=${page}`, ctx);
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "github_list_issue_comments",
|
|
205
|
+
access: "read",
|
|
206
|
+
description: "List comments on an issue or pull request, optionally only those updated since a time",
|
|
207
|
+
input_schema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
repo: { type: "string", description: "owner/repo" },
|
|
211
|
+
issue_number: { type: "number", description: "Issue or PR number" },
|
|
212
|
+
since: { type: "string", description: "ISO 8601 time: only comments updated at or after it" },
|
|
213
|
+
page: { type: "number", description: "Page of 100 comments, oldest first, from 1 (default 1)" },
|
|
214
|
+
},
|
|
215
|
+
required: ["repo", "issue_number"],
|
|
216
|
+
},
|
|
217
|
+
handler: async (input, ctx) => {
|
|
218
|
+
const since = input.since ? `&since=${encodeURIComponent(input.since)}` : "";
|
|
219
|
+
const page = Math.max(1, Math.floor(input.page ?? 1));
|
|
220
|
+
return gh(`/repos/${input.repo}/issues/${Number(input.issue_number)}/comments?per_page=100&page=${page}${since}`, ctx);
|
|
221
|
+
},
|
|
222
|
+
},
|
|
184
223
|
{
|
|
185
224
|
name: "github_create_issue",
|
|
186
225
|
access: "write",
|
package/dist/types.d.ts
CHANGED
|
@@ -418,6 +418,13 @@ export interface Node {
|
|
|
418
418
|
* Distinct from `requires.on_fail`, which is the pre-condition gate.
|
|
419
419
|
*/
|
|
420
420
|
on_fail?: NodeOnFail;
|
|
421
|
+
/**
|
|
422
|
+
* Who picks this node's natural-language route (#357). `agent` (default):
|
|
423
|
+
* the agent's route evaluation. `decider`: the operator's decision model is
|
|
424
|
+
* asked first and its confident answer is the route; otherwise the agent.
|
|
425
|
+
* Expressions always win over both. Safety conditions belong in expressions.
|
|
426
|
+
*/
|
|
427
|
+
route_by?: RouteBy;
|
|
421
428
|
/**
|
|
422
429
|
* What this node's agent may do (#365). `read` runs the node read-only:
|
|
423
430
|
* only `access: "read"` skill tools, no external skill MCP servers, no
|
|
@@ -555,6 +562,12 @@ export interface Edge {
|
|
|
555
562
|
* evaluated by sweny with no model call.
|
|
556
563
|
*/
|
|
557
564
|
when?: EdgeWhen;
|
|
565
|
+
/**
|
|
566
|
+
* The condition in natural language (#357). For an `{ expr }` edge it is
|
|
567
|
+
* what the next rung (decider or agent) reads when the expression cannot be
|
|
568
|
+
* evaluated because a field it needs is missing or invalid.
|
|
569
|
+
*/
|
|
570
|
+
description?: string;
|
|
558
571
|
/** Max times this edge can be followed (enables retry loops). Default: unlimited. */
|
|
559
572
|
max_iterations?: number;
|
|
560
573
|
}
|
|
@@ -569,6 +582,25 @@ export interface Edge {
|
|
|
569
582
|
*/
|
|
570
583
|
export declare const WORKFLOW_TYPES: readonly ["pr_review", "e2e_test", "content_generation", "monitor", "data_sync", "generic"];
|
|
571
584
|
export type WorkflowType = (typeof WORKFLOW_TYPES)[number];
|
|
585
|
+
/**
|
|
586
|
+
* Decision model gates (#357). Shared by the Zod schema, the JSON schema and
|
|
587
|
+
* the executor; kept here so schema-only (browser) consumers need no Node code.
|
|
588
|
+
*/
|
|
589
|
+
/** Default: accept a decider answer only at or above this confidence. */
|
|
590
|
+
export declare const DECIDER_MIN_CONFIDENCE = 0.85;
|
|
591
|
+
/** Default: ... and only when the top label leads the runner-up by at least this much. */
|
|
592
|
+
export declare const DECIDER_MIN_MARGIN = 0.2;
|
|
593
|
+
/** A workflow may lower `min_confidence` to this and no further. */
|
|
594
|
+
export declare const DECIDER_CONFIDENCE_FLOOR = 0.7;
|
|
595
|
+
/** A workflow may lower `min_margin` to this and no further. */
|
|
596
|
+
export declare const DECIDER_MARGIN_FLOOR = 0.1;
|
|
597
|
+
/** `route_by` values: who picks a node's natural-language route. */
|
|
598
|
+
export declare const ROUTE_BY: readonly ["agent", "decider"];
|
|
599
|
+
export type RouteBy = (typeof ROUTE_BY)[number];
|
|
600
|
+
/** Load error for a workflow that still declares the removed `decider.mode`. */
|
|
601
|
+
export declare const DECIDER_MODE_REMOVED: string;
|
|
602
|
+
/** Load error for a workflow that declares the provider itself. */
|
|
603
|
+
export declare const DECIDER_PROVIDER_MOVED: string;
|
|
572
604
|
/** A complete workflow definition. Pure data, fully serializable. */
|
|
573
605
|
export interface Workflow {
|
|
574
606
|
id: string;
|
|
@@ -598,8 +630,9 @@ export interface Workflow {
|
|
|
598
630
|
*/
|
|
599
631
|
budget?: Budget;
|
|
600
632
|
/**
|
|
601
|
-
* Decision model
|
|
602
|
-
*
|
|
633
|
+
* Decision model thresholds (#357) for the nodes that set `route_by:
|
|
634
|
+
* decider`. Never looser than the floors. The provider itself comes only
|
|
635
|
+
* from operator config, never from the workflow.
|
|
603
636
|
*/
|
|
604
637
|
decider?: DeciderConfig;
|
|
605
638
|
/**
|
|
@@ -694,6 +727,12 @@ export interface NodeResult {
|
|
|
694
727
|
* Claude Code harness only; absent for harnesses that do not report it.
|
|
695
728
|
*/
|
|
696
729
|
policy?: NodePolicyFacts;
|
|
730
|
+
/**
|
|
731
|
+
* The agent ran inside an enforced sandbox that keeps it out of the sweny
|
|
732
|
+
* state dir (run keys and journals). Set by the harness adapters; absent
|
|
733
|
+
* (treated as not contained) for anything that does not report it.
|
|
734
|
+
*/
|
|
735
|
+
contained?: boolean;
|
|
697
736
|
/** Safe outputs (#365): what the write stage did with each intent. Absent when the node declares none. */
|
|
698
737
|
outputs?: SafeOutputReceipt[];
|
|
699
738
|
/**
|
|
@@ -801,12 +840,19 @@ export interface TraceStep {
|
|
|
801
840
|
/** 0-indexed retry attempt for this iteration. Absent when no retry fired. */
|
|
802
841
|
retryAttempt?: number;
|
|
803
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Which rung of the route ladder chose a conditional route (#357): a `when`
|
|
845
|
+
* expression, the decision model, or the agent's route evaluation.
|
|
846
|
+
*/
|
|
847
|
+
export type RouteRung = "expr" | "decider" | "agent";
|
|
804
848
|
/** A routing decision between nodes */
|
|
805
849
|
export interface TraceEdge {
|
|
806
850
|
from: string;
|
|
807
851
|
to: string;
|
|
808
852
|
/** Why this edge was chosen (condition text or "only path") */
|
|
809
853
|
reason: string;
|
|
854
|
+
/** Who chose it. Absent for an unconditional edge (nothing to decide). */
|
|
855
|
+
rung?: RouteRung;
|
|
810
856
|
}
|
|
811
857
|
/** Full execution trace — ordered steps + edges taken */
|
|
812
858
|
export interface ExecutionTrace {
|
|
@@ -816,8 +862,14 @@ export interface ExecutionTrace {
|
|
|
816
862
|
edges: TraceEdge[];
|
|
817
863
|
/** Resolved sources keyed by field path (e.g. "nodes.gather.instruction") */
|
|
818
864
|
sources: Record<string, _ResolvedSource>;
|
|
819
|
-
/**
|
|
865
|
+
/** One record per decision-model consultation (#357). Metadata only. Absent when no decider runs. */
|
|
820
866
|
decisions?: DeciderRecord[];
|
|
867
|
+
/**
|
|
868
|
+
* Why the decider did not run for some or all of this run's `route_by:
|
|
869
|
+
* decider` nodes (#357): missing operator config, `--no-decider`, a rejected
|
|
870
|
+
* URL, an open breaker or a spent call cap. Absent when nothing turned it off.
|
|
871
|
+
*/
|
|
872
|
+
deciderOff?: string;
|
|
821
873
|
}
|
|
822
874
|
/** Result of execute() — final node results + full execution trace */
|
|
823
875
|
export interface ExecutionResult {
|
package/dist/types.js
CHANGED
|
@@ -117,6 +117,28 @@ export const WORKFLOW_TYPES = [
|
|
|
117
117
|
"data_sync",
|
|
118
118
|
"generic",
|
|
119
119
|
];
|
|
120
|
+
/**
|
|
121
|
+
* Decision model gates (#357). Shared by the Zod schema, the JSON schema and
|
|
122
|
+
* the executor; kept here so schema-only (browser) consumers need no Node code.
|
|
123
|
+
*/
|
|
124
|
+
/** Default: accept a decider answer only at or above this confidence. */
|
|
125
|
+
export const DECIDER_MIN_CONFIDENCE = 0.85;
|
|
126
|
+
/** Default: ... and only when the top label leads the runner-up by at least this much. */
|
|
127
|
+
export const DECIDER_MIN_MARGIN = 0.2;
|
|
128
|
+
/** A workflow may lower `min_confidence` to this and no further. */
|
|
129
|
+
export const DECIDER_CONFIDENCE_FLOOR = 0.7;
|
|
130
|
+
/** A workflow may lower `min_margin` to this and no further. */
|
|
131
|
+
export const DECIDER_MARGIN_FLOOR = 0.1;
|
|
132
|
+
/** `route_by` values: who picks a node's natural-language route. */
|
|
133
|
+
export const ROUTE_BY = ["agent", "decider"];
|
|
134
|
+
/** Load error for a workflow that still declares the removed `decider.mode`. */
|
|
135
|
+
export const DECIDER_MODE_REMOVED = "decider.mode was removed with shadow mode. A decider now decides the routes of nodes that set `route_by: decider`, " +
|
|
136
|
+
"using the provider from operator config (.sweny.yml `decider:` or SWENY_DECIDER_URL and SWENY_DECIDER_MODEL). " +
|
|
137
|
+
"Delete the mode line; to keep routing with the agent only, delete the whole decider block.";
|
|
138
|
+
/** Load error for a workflow that declares the provider itself. */
|
|
139
|
+
export const DECIDER_PROVIDER_MOVED = "decider.provider is not allowed in a workflow: the endpoint and key come only from operator config " +
|
|
140
|
+
"(.sweny.yml `decider: { url, model }`, or SWENY_DECIDER_URL and SWENY_DECIDER_MODEL; the key only from SWENY_DECIDER_API_KEY). " +
|
|
141
|
+
"Move it there. A workflow may only set min_confidence and min_margin here, and `route_by: decider` on nodes.";
|
|
120
142
|
export const consoleLogger = {
|
|
121
143
|
info: (msg, data) => console.log(`[info] ${msg}`, data ?? ""),
|
|
122
144
|
warn: (msg, data) => console.warn(`[warn] ${msg}`, data ?? ""),
|
package/dist/when.d.ts
CHANGED
|
@@ -95,7 +95,14 @@ export interface ExpressionResult {
|
|
|
95
95
|
* Evaluate a parsed expression against node outputs. Never throws: a missing
|
|
96
96
|
* field or a type mismatch yields `{ value: false, problem }`.
|
|
97
97
|
*/
|
|
98
|
-
export declare function evaluateExpression(ast: ExprNode, scope: ExpressionScope
|
|
98
|
+
export declare function evaluateExpression(ast: ExprNode, scope: ExpressionScope, opts?: {
|
|
99
|
+
/**
|
|
100
|
+
* `node.field` paths whose value broke its declared type or enum. Any
|
|
101
|
+
* reference to one (or below it), `exists` included, is a problem: the
|
|
102
|
+
* value is unknown, which is neither present nor absent.
|
|
103
|
+
*/
|
|
104
|
+
invalid?: ReadonlySet<string>;
|
|
105
|
+
}): ExpressionResult;
|
|
99
106
|
/**
|
|
100
107
|
* Check an expression against the workflow it routes. Returns one message per
|
|
101
108
|
* problem, empty when the expression is valid:
|
package/dist/when.js
CHANGED
|
@@ -324,8 +324,14 @@ function lookup(scope, segments) {
|
|
|
324
324
|
* Evaluate a parsed expression against node outputs. Never throws: a missing
|
|
325
325
|
* field or a type mismatch yields `{ value: false, problem }`.
|
|
326
326
|
*/
|
|
327
|
-
export function evaluateExpression(ast, scope) {
|
|
327
|
+
export function evaluateExpression(ast, scope, opts = {}) {
|
|
328
|
+
const checkInvalid = (path) => {
|
|
329
|
+
if (opts.invalid && path.length >= 2 && opts.invalid.has(`${path[0]}.${path[1]}`)) {
|
|
330
|
+
throw new Abort(`field '${path[0]}.${path[1]}' does not match its declared type`);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
328
333
|
const read = (path) => {
|
|
334
|
+
checkInvalid(path);
|
|
329
335
|
const v = lookup(scope, path);
|
|
330
336
|
if (v === undefined)
|
|
331
337
|
throw new Abort(`field '${path.join(".")}' is missing`);
|
|
@@ -345,6 +351,7 @@ export function evaluateExpression(ast, scope) {
|
|
|
345
351
|
case "path":
|
|
346
352
|
return read(node.segments);
|
|
347
353
|
case "exists": {
|
|
354
|
+
checkInvalid(node.path.segments);
|
|
348
355
|
const v = lookup(scope, node.path.segments);
|
|
349
356
|
return v !== undefined && v !== null;
|
|
350
357
|
}
|
package/dist/workflow-builder.js
CHANGED
|
@@ -56,7 +56,9 @@ export function buildSystemPrompt(skills, existingWorkflow) {
|
|
|
56
56
|
"- Use snake_case for node IDs (e.g. gather_errors, create_ticket)",
|
|
57
57
|
"- Set `entry` to the first node in the flow",
|
|
58
58
|
"- Only reference skill IDs from the list above in node `skills` arrays",
|
|
59
|
-
|
|
59
|
+
'- Prefer `when: { expr: "..." }` expressions for edge conditions: declare the deciding values as enum, number, integer or boolean properties in the source node\'s `output`, then compare them (e.g. `{ "expr": "triage.severity in [\'high\', \'critical\']" }`). Expressions route with no model call and always the same way',
|
|
60
|
+
"- Use a natural-language `when` only for a fuzzy condition no declared field can express",
|
|
61
|
+
"- Put safety conditions (anything that gates a write, a PR or an issue) in expressions, never in natural language",
|
|
60
62
|
"- Every node must be reachable from the entry node",
|
|
61
63
|
"- Return ONLY the workflow JSON object — no markdown fences, no explanation",
|
|
62
64
|
];
|