@sweny-ai/core 0.1.199 → 0.1.201
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/budget.d.ts +77 -11
- package/dist/budget.js +132 -16
- package/dist/cli/config.js +5 -2
- package/dist/cli/deprecated-flags.d.ts +35 -0
- package/dist/cli/deprecated-flags.js +44 -0
- package/dist/cli/renderer.js +5 -0
- package/dist/cli/run-history.d.ts +12 -0
- package/dist/cli/run-history.js +16 -0
- package/dist/cli/run-output.d.ts +16 -1
- package/dist/cli/run-output.js +46 -4
- package/dist/cli/runs.js +38 -1
- package/dist/cli/ticket.js +7 -2
- package/dist/eval/index.d.ts +10 -0
- package/dist/eval/index.js +27 -6
- package/dist/eval/judge.d.ts +3 -0
- package/dist/eval/judge.js +12 -2
- package/dist/executor.js +276 -31
- package/dist/harness/acp.js +4 -1
- package/dist/harness/claude-code.d.ts +3 -1
- package/dist/harness/claude-code.js +4 -1
- package/dist/harness/codex.js +3 -1
- package/dist/harness/compat.js +1 -0
- package/dist/harness/index.d.ts +1 -1
- package/dist/harness/index.js +1 -1
- package/dist/harness/pi.js +3 -1
- package/dist/harness/prompts.d.ts +11 -1
- package/dist/harness/prompts.js +21 -2
- package/dist/harness/types.d.ts +9 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/journal.d.ts +48 -10
- package/dist/journal.js +164 -16
- package/dist/safe-outputs.d.ts +9 -0
- package/dist/safe-outputs.js +41 -26
- package/dist/skills/linear.js +3 -1
- package/dist/studio.js +2 -1
- package/dist/types.d.ts +34 -1
- package/package.json +1 -1
package/dist/cli/run-output.js
CHANGED
|
@@ -11,6 +11,7 @@ import { toMermaidBlock } from "../mermaid.js";
|
|
|
11
11
|
import { createPaint } from "./style.js";
|
|
12
12
|
import { colorEnabled, glyphs } from "./terminal.js";
|
|
13
13
|
import { trustedEnvValue } from "../startup-env.js";
|
|
14
|
+
import { SPEND_KINDS } from "../budget.js";
|
|
14
15
|
/** The short key of a `degraded` entry: the text before its first colon. */
|
|
15
16
|
export function degradedKey(entry) {
|
|
16
17
|
const i = entry.indexOf(":");
|
|
@@ -70,6 +71,19 @@ export function summarizeRun(results, durationMs, crashed = false, trace) {
|
|
|
70
71
|
}
|
|
71
72
|
if (staged > 0 || applied > 0)
|
|
72
73
|
policy.outputs = { staged, applied };
|
|
74
|
+
// The executor's own completions (judge, route, ...) are on no node result,
|
|
75
|
+
// and a node result holds only its last attempt. With the split present the
|
|
76
|
+
// totals are its sum, so they reconcile with it on one basis.
|
|
77
|
+
const split = Object.values(trace?.spend ?? {});
|
|
78
|
+
const extra = Object.keys(trace?.spend ?? {}).some((k) => k !== "node");
|
|
79
|
+
if (extra) {
|
|
80
|
+
const t = split.reduce((n, k) => n + (isNum(k.tokens) ? k.tokens : 0), 0);
|
|
81
|
+
const c = split.reduce((n, k) => n + (isNum(k.costUsd) ? k.costUsd : 0), 0);
|
|
82
|
+
if (t > 0 || tokens !== undefined)
|
|
83
|
+
tokens = t;
|
|
84
|
+
if (c > 0 || costUsd !== undefined)
|
|
85
|
+
costUsd = Math.round(c * 1e10) / 1e10;
|
|
86
|
+
}
|
|
73
87
|
return {
|
|
74
88
|
ok: !failed,
|
|
75
89
|
nodesOk,
|
|
@@ -83,6 +97,7 @@ export function summarizeRun(results, durationMs, crashed = false, trace) {
|
|
|
83
97
|
...(degraded.size > 0 ? { degraded: [...degraded] } : {}),
|
|
84
98
|
...(Object.keys(policy).length > 0 ? { policy } : {}),
|
|
85
99
|
...(budget ? { budget } : {}),
|
|
100
|
+
...(extra ? { spend: trace.spend } : {}),
|
|
86
101
|
...(trace && countRoutes(trace).total > 0 ? { routes: countRoutes(trace) } : {}),
|
|
87
102
|
...(trace?.deciderOff ? { deciderOff: trace.deciderOff } : {}),
|
|
88
103
|
};
|
|
@@ -160,9 +175,34 @@ export function formatBudgetAmount(b) {
|
|
|
160
175
|
const unit = b.unit === "cost_usd" ? "cost" : "tokens";
|
|
161
176
|
return `${unit} ${amount(b.spent)} of ${amount(b.limit)}`;
|
|
162
177
|
}
|
|
163
|
-
/** `budget exceeded: tokens 52k of 50k (run, at node gather)
|
|
178
|
+
/** `budget exceeded: tokens 52k of 50k (run, at node gather)`, plus `, judge` when an executor call crossed it. */
|
|
164
179
|
export function formatBudgetOverrun(b) {
|
|
165
|
-
return `budget exceeded: ${formatBudgetAmount(b)} (${b.scope}, at node ${b.node})`;
|
|
180
|
+
return `budget exceeded: ${formatBudgetAmount(b)} (${b.scope}, at node ${b.node}${b.kind ? `, ${b.kind}` : ""})`;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* `node $0.15, judge $0.03, route <$0.01`: spend by kind in a fixed order
|
|
184
|
+
* (node, judge, route, reflect, screen), only the kinds present. In tokens
|
|
185
|
+
* when no kind reported a cost. Undefined when there is no split to show.
|
|
186
|
+
*/
|
|
187
|
+
export function formatSpendSplit(spend, sep = " ") {
|
|
188
|
+
const kinds = SPEND_KINDS.flatMap((k) => (spend?.[k] ? [[k, spend[k]]] : []));
|
|
189
|
+
if (kinds.length === 0)
|
|
190
|
+
return undefined;
|
|
191
|
+
const byCost = kinds.some(([, v]) => v.costUsd > 0);
|
|
192
|
+
return kinds.map(([k, v]) => `${k}${sep}${byCost ? formatCost(v.costUsd) : formatTokenCount(v.tokens)}`).join(", ");
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* `12k tokens (judge 2k)`: the executor's own kinds in brackets, on the token
|
|
196
|
+
* segment, or on the cost (`$0.18 (judge $0.03)`) when no tokens were reported.
|
|
197
|
+
*/
|
|
198
|
+
function ownSpendBracket(spend, unit) {
|
|
199
|
+
const own = SPEND_KINDS.flatMap((k) => {
|
|
200
|
+
const v = spend?.[k];
|
|
201
|
+
if (k === "node" || !v || !(v[unit] > 0))
|
|
202
|
+
return [];
|
|
203
|
+
return [`${k} ${unit === "tokens" ? formatTokenCount(v.tokens) : formatCost(v.costUsd)}`];
|
|
204
|
+
});
|
|
205
|
+
return own.length === 0 ? "" : ` (${own.join(", ")})`;
|
|
166
206
|
}
|
|
167
207
|
/** `5 (3 decider, 1 expr, 1 agent)`: zero rungs are left out. */
|
|
168
208
|
export function formatRouteCounts(r) {
|
|
@@ -182,8 +222,10 @@ export function formatReceipt(s) {
|
|
|
182
222
|
...(s.nodesSkipped > 0 ? [`${s.nodesSkipped} skipped`] : []),
|
|
183
223
|
`${s.toolCalls} tool ${s.toolCalls === 1 ? "call" : "calls"}`,
|
|
184
224
|
formatReceiptDuration(s.durationMs),
|
|
185
|
-
...(s.tokens !== undefined ? [`${formatTokenCount(s.tokens)} tokens`] : []),
|
|
186
|
-
...(s.costUsd !== undefined
|
|
225
|
+
...(s.tokens !== undefined ? [`${formatTokenCount(s.tokens)} tokens${ownSpendBracket(s.spend, "tokens")}`] : []),
|
|
226
|
+
...(s.costUsd !== undefined
|
|
227
|
+
? [`${formatCost(s.costUsd)}${s.tokens === undefined ? ownSpendBracket(s.spend, "costUsd") : ""}`]
|
|
228
|
+
: []),
|
|
187
229
|
...(s.harness !== undefined && s.harness !== "claude-code" ? [s.harness] : []),
|
|
188
230
|
...(s.degraded && s.degraded.length > 0 ? [`degraded: ${s.degraded.join(", ")}`] : []),
|
|
189
231
|
...(formatPolicySegment(s.policy) ? [formatPolicySegment(s.policy)] : []),
|
package/dist/cli/runs.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* `color` is true (TTY and no NO_COLOR); the glyph set follows `unicode`.
|
|
6
6
|
* The diff always ends on one plain line with the answer counts.
|
|
7
7
|
*/
|
|
8
|
-
import { findRun, listRuns } from "./run-history.js";
|
|
8
|
+
import { findRun, listRuns, } from "./run-history.js";
|
|
9
|
+
import { SPEND_KINDS } from "../budget.js";
|
|
9
10
|
import { compareAnswers, readRunAnswers, } from "./run-answers.js";
|
|
10
11
|
import { formatCost, formatReceiptDuration, formatTokenCount } from "./run-output.js";
|
|
11
12
|
import { createPaint } from "./style.js";
|
|
@@ -84,8 +85,25 @@ export function printableRun(r) {
|
|
|
84
85
|
tokens: finiteOrNull(r.totals.tokens),
|
|
85
86
|
cost_usd: finiteOrNull(r.totals.cost_usd),
|
|
86
87
|
},
|
|
88
|
+
...(r.spend && typeof r.spend === "object" ? { spend: printableSpend(r.spend) } : {}),
|
|
87
89
|
};
|
|
88
90
|
}
|
|
91
|
+
/** The record's split, or its totals as all node spend when it has none. */
|
|
92
|
+
function splitOf(r) {
|
|
93
|
+
if (r.spend)
|
|
94
|
+
return r.spend;
|
|
95
|
+
return { node: { tokens: r.totals.tokens ?? 0, cost_usd: r.totals.cost_usd ?? 0, calls: 0 } };
|
|
96
|
+
}
|
|
97
|
+
/** Only the known kinds, own keys, finite numbers. */
|
|
98
|
+
function printableSpend(spend) {
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const k of SPEND_KINDS) {
|
|
101
|
+
const v = Object.hasOwn(spend, k) ? spend[k] : undefined;
|
|
102
|
+
if (v && typeof v === "object")
|
|
103
|
+
out[k] = { tokens: finite(v.tokens), cost_usd: finite(v.cost_usd), calls: finite(v.calls) };
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
89
107
|
// ── sweny runs ──────────────────────────────────────────────────
|
|
90
108
|
export function formatRunsTable(records, color, nowMs = Date.now(), unicode = true) {
|
|
91
109
|
const ICON = iconsFor(glyphsFor(unicode));
|
|
@@ -312,6 +330,25 @@ export function formatRunDiff(rawA, rawB, color, opts = {}) {
|
|
|
312
330
|
for (const t of tot)
|
|
313
331
|
if (t)
|
|
314
332
|
out.push(` ${t}`);
|
|
333
|
+
// Spend by kind, when either run has the split: what moved the totals. A
|
|
334
|
+
// record without one (node-only, or written before the split) is all node spend.
|
|
335
|
+
if (a.spend || b.spend) {
|
|
336
|
+
const sa = splitOf(a);
|
|
337
|
+
const sb = splitOf(b);
|
|
338
|
+
const byCost = SPEND_KINDS.some((k) => (sa[k]?.cost_usd ?? 0) > 0 || (sb[k]?.cost_usd ?? 0) > 0);
|
|
339
|
+
for (const k of SPEND_KINDS) {
|
|
340
|
+
const va = sa[k];
|
|
341
|
+
const vb = sb[k];
|
|
342
|
+
if (!va && !vb)
|
|
343
|
+
continue;
|
|
344
|
+
const label = ` ${pad(k, 10)}`;
|
|
345
|
+
const line = byCost
|
|
346
|
+
? metric(label, va ? va.cost_usd : null, vb ? vb.cost_usd : null, formatCost, deltaCost, g.arrow)
|
|
347
|
+
: metric(label, va ? va.tokens : null, vb ? vb.tokens : null, formatTokenCount, deltaTokens, g.arrow);
|
|
348
|
+
if (line)
|
|
349
|
+
out.push(` ${line}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
315
352
|
out.push("", " nodes");
|
|
316
353
|
const aNodes = new Map(a.nodes.map((n) => [n.id, n]));
|
|
317
354
|
const bNodes = new Map(b.nodes.map((n) => [n.id, n]));
|
package/dist/cli/ticket.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* METADATA ONLY, like the receipt line: counts, durations, tokens, cost,
|
|
7
7
|
* policy facts. Never prompts, node outputs, tool inputs, or model prose.
|
|
8
8
|
*/
|
|
9
|
-
import { formatBudgetAmount, formatCost, formatRouteCounts, formatReceiptDuration, formatTokenCount, policyParts, renderReceiptLine, } from "./run-output.js";
|
|
9
|
+
import { formatBudgetAmount, formatCost, formatRouteCounts, formatSpendSplit, formatReceiptDuration, formatTokenCount, policyParts, renderReceiptLine, } from "./run-output.js";
|
|
10
10
|
import { createPaint } from "./style.js";
|
|
11
11
|
import { colorEnabled, glyphsFor, richOutput, supportsUnicode, terminalColumns, ticketBoxFor } from "./terminal.js";
|
|
12
12
|
/** Widest ticket, in columns, frame included. */
|
|
@@ -52,8 +52,11 @@ export function stampWords(summary) {
|
|
|
52
52
|
}
|
|
53
53
|
return { words, strong };
|
|
54
54
|
}
|
|
55
|
+
const NBSP = "\u00a0";
|
|
55
56
|
/** Field rows, label then value, metadata only. */
|
|
56
57
|
function ticketFields(s) {
|
|
58
|
+
// A no-break space keeps each kind with its amount when the row wraps (put back after wrapping).
|
|
59
|
+
const spend = formatSpendSplit(s.spend, NBSP);
|
|
57
60
|
const failed = Math.max(0, s.nodesTotal - s.nodesOk - s.nodesSkipped);
|
|
58
61
|
const nodes = [
|
|
59
62
|
`${s.nodesOk}/${s.nodesTotal}`,
|
|
@@ -67,6 +70,7 @@ function ticketFields(s) {
|
|
|
67
70
|
["duration", formatReceiptDuration(s.durationMs)],
|
|
68
71
|
...(s.tokens !== undefined ? [["tokens", formatTokenCount(s.tokens)]] : []),
|
|
69
72
|
...(s.costUsd !== undefined ? [["cost", formatCost(s.costUsd)]] : []),
|
|
73
|
+
...(spend ? [["spend", spend]] : []),
|
|
70
74
|
...(s.harness !== undefined && s.harness !== "claude-code" ? [["harness", s.harness]] : []),
|
|
71
75
|
...(s.degraded && s.degraded.length > 0 ? [["degraded", s.degraded.join(", ")]] : []),
|
|
72
76
|
...(s.routes && s.routes.total > 0 ? [["routes", formatRouteCounts(s.routes)]] : []),
|
|
@@ -154,7 +158,8 @@ export function renderTicket(data, opts = {}) {
|
|
|
154
158
|
lines.push(row(""));
|
|
155
159
|
// Fields, aligned: label column then value column; long values wrap under the value.
|
|
156
160
|
for (const [label, value] of ticketFields(s)) {
|
|
157
|
-
wrap(value, valueWidth).forEach((
|
|
161
|
+
wrap(value, valueWidth).forEach((raw, i) => {
|
|
162
|
+
const part = raw.replaceAll(NBSP, " ");
|
|
158
163
|
const lab = (i === 0 ? label : "").padEnd(LABEL_WIDTH);
|
|
159
164
|
lines.push(row(lab + part, paint.muted(lab) + part));
|
|
160
165
|
});
|
package/dist/eval/index.d.ts
CHANGED
|
@@ -10,6 +10,16 @@ export interface EvaluateAllOptions {
|
|
|
10
10
|
node?: Node;
|
|
11
11
|
/** The workflow being executed. Used to resolve workflow-level `judge_model` overrides. */
|
|
12
12
|
workflow?: Workflow;
|
|
13
|
+
/**
|
|
14
|
+
* Run the evaluators one at a time instead of together. The executor sets it
|
|
15
|
+
* when a spend budget is on, so each judge's usage is counted (and a
|
|
16
|
+
* crossing stops the rest) before the next one starts.
|
|
17
|
+
*/
|
|
18
|
+
sequential?: boolean;
|
|
19
|
+
/** The run's abort signal: checked before each judge when sequential, and forwarded to judge calls. */
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
/** Per-call timeout forwarded to judge calls. */
|
|
22
|
+
timeoutMs?: number;
|
|
13
23
|
}
|
|
14
24
|
/**
|
|
15
25
|
* Run every declared evaluator against the node's result and return one
|
package/dist/eval/index.js
CHANGED
|
@@ -41,11 +41,28 @@ export async function evaluateAll(evaluators, result, opts = {}) {
|
|
|
41
41
|
throw new Error(`evaluator '${orphanJudge.name}' is kind: judge but no Claude client was provided to evaluateAll`);
|
|
42
42
|
}
|
|
43
43
|
// Evaluators are independent, so they run concurrently (#337): N judges on
|
|
44
|
-
// a node cost one round-trip of wall time, not N.
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
44
|
+
// a node cost one round-trip of wall time, not N. With `sequential` (a spend
|
|
45
|
+
// budget is on) they run one at a time, so each judge's spend is counted
|
|
46
|
+
// before the next starts, and an abort stops the rest. Results keep
|
|
47
|
+
// declaration order. Every evaluator settles before an error surfaces, and
|
|
48
|
+
// the first rejection in declaration order is rethrown, so the outcome never
|
|
49
|
+
// depends on which call finishes first.
|
|
50
|
+
const settled = [];
|
|
51
|
+
if (opts.sequential) {
|
|
52
|
+
for (const e of evaluators) {
|
|
53
|
+
if (opts.signal?.aborted)
|
|
54
|
+
throw new Error("workflow aborted");
|
|
55
|
+
try {
|
|
56
|
+
settled.push({ status: "fulfilled", value: await evaluateOne(e, result, opts) });
|
|
57
|
+
}
|
|
58
|
+
catch (reason) {
|
|
59
|
+
settled.push({ status: "rejected", reason });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
settled.push(...(await Promise.allSettled(evaluators.map((e) => evaluateOne(e, result, opts)))));
|
|
65
|
+
}
|
|
49
66
|
const out = [];
|
|
50
67
|
for (const s of settled) {
|
|
51
68
|
if (s.status === "rejected")
|
|
@@ -66,7 +83,11 @@ async function evaluateOne(e, result, opts) {
|
|
|
66
83
|
}
|
|
67
84
|
if (e.kind === "judge" && opts.claude) {
|
|
68
85
|
const model = resolveJudgeModel(e, opts.node, opts.workflow, opts.claude.defaultJudgeModel);
|
|
69
|
-
const verdict = await evaluateJudge(e, result, opts.claude, {
|
|
86
|
+
const verdict = await evaluateJudge(e, result, opts.claude, {
|
|
87
|
+
model,
|
|
88
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
89
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
90
|
+
});
|
|
70
91
|
return { name: e.name, kind: "judge", pass: verdict.pass, reasoning: capReasoning(verdict.reasoning) };
|
|
71
92
|
}
|
|
72
93
|
return null;
|
package/dist/eval/judge.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ import type { Claude, Evaluator, NodeResult } from "../types.js";
|
|
|
2
2
|
export interface JudgeOptions {
|
|
3
3
|
/** Resolved judge model (evaluator > node > workflow > package default). */
|
|
4
4
|
model: string;
|
|
5
|
+
/** The run's abort signal and per-call timeout, forwarded to the judge call. */
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
timeoutMs?: number;
|
|
5
8
|
}
|
|
6
9
|
interface ParsedResponse {
|
|
7
10
|
verdict: string | null;
|
package/dist/eval/judge.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// For a typical 4-node workflow with 2 judges per node, that's 8 extra
|
|
14
14
|
// calls per run. The workflow-level `judge_budget` (default 50) gives
|
|
15
15
|
// authors a load-time warning when the total stretches past expected.
|
|
16
|
+
import { BudgetRefusal } from "../budget.js";
|
|
16
17
|
/**
|
|
17
18
|
* Build the structured judge prompt.
|
|
18
19
|
*
|
|
@@ -122,10 +123,19 @@ export async function evaluateJudge(evaluator, result, claude, opts) {
|
|
|
122
123
|
instruction: prompt,
|
|
123
124
|
context: {},
|
|
124
125
|
model: opts.model,
|
|
126
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
127
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
125
128
|
});
|
|
126
129
|
}
|
|
127
|
-
catch {
|
|
128
|
-
//
|
|
130
|
+
catch (err) {
|
|
131
|
+
// A budget refusal is no answer at all: it reaches the executor, which
|
|
132
|
+
// stops the node on the budget instead of recording a parse failure.
|
|
133
|
+
if (err instanceof BudgetRefusal)
|
|
134
|
+
throw err;
|
|
135
|
+
// An aborted run is no answer either: never retried as a parse failure.
|
|
136
|
+
if (opts.signal?.aborted)
|
|
137
|
+
throw err;
|
|
138
|
+
// Treat other throws as parse failure for retry purposes; the second attempt
|
|
129
139
|
// either succeeds or surfaces the same path through the same channel.
|
|
130
140
|
continue;
|
|
131
141
|
}
|