@sweny-ai/core 0.1.198 → 0.1.200

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 (44) hide show
  1. package/dist/agent-env.d.ts +94 -2
  2. package/dist/agent-env.js +144 -7
  3. package/dist/budget.d.ts +77 -11
  4. package/dist/budget.js +132 -16
  5. package/dist/cli/renderer.js +5 -0
  6. package/dist/cli/run-history.d.ts +12 -0
  7. package/dist/cli/run-history.js +16 -0
  8. package/dist/cli/run-output.d.ts +16 -1
  9. package/dist/cli/run-output.js +46 -4
  10. package/dist/cli/runs.js +38 -1
  11. package/dist/cli/ticket.js +7 -2
  12. package/dist/eval/index.d.ts +10 -0
  13. package/dist/eval/index.js +27 -6
  14. package/dist/eval/judge.d.ts +3 -0
  15. package/dist/eval/judge.js +12 -2
  16. package/dist/executor.d.ts +5 -1
  17. package/dist/executor.js +276 -31
  18. package/dist/harness/acp.d.ts +5 -2
  19. package/dist/harness/acp.js +11 -23
  20. package/dist/harness/auth.js +2 -1
  21. package/dist/harness/claude-code.d.ts +18 -13
  22. package/dist/harness/claude-code.js +34 -15
  23. package/dist/harness/codex.d.ts +22 -5
  24. package/dist/harness/codex.js +67 -11
  25. package/dist/harness/compat.js +1 -0
  26. package/dist/harness/index.d.ts +1 -1
  27. package/dist/harness/index.js +1 -1
  28. package/dist/harness/pi.d.ts +11 -5
  29. package/dist/harness/pi.js +27 -9
  30. package/dist/harness/prompts.d.ts +11 -1
  31. package/dist/harness/prompts.js +21 -2
  32. package/dist/harness/sandbox-wrapper.d.ts +13 -2
  33. package/dist/harness/sandbox-wrapper.js +29 -4
  34. package/dist/harness/types.d.ts +9 -1
  35. package/dist/index.d.ts +4 -4
  36. package/dist/index.js +2 -2
  37. package/dist/journal.d.ts +48 -10
  38. package/dist/journal.js +164 -16
  39. package/dist/safe-outputs.d.ts +9 -0
  40. package/dist/safe-outputs.js +41 -26
  41. package/dist/skills/linear.js +3 -1
  42. package/dist/studio.js +2 -1
  43. package/dist/types.d.ts +34 -1
  44. package/package.json +1 -1
@@ -114,6 +114,15 @@ export interface ApplySafeOutputsOptions {
114
114
  * passed every deterministic check). Anything but `ALLOW` is a veto.
115
115
  */
116
116
  screen?: (writes: Record<string, unknown>[]) => Promise<string | null>;
117
+ /**
118
+ * Resume: reconcile one write (its skill, tool and arguments) with the
119
+ * journal and, for an outstanding intent, the provider's read-only probes,
120
+ * before the screen. `applied`: recorded or found on the provider; it is not
121
+ * screened and keeps its receipt even when the screen vetoes the rest.
122
+ * `unknown`: the probe could not tell; it is not screened or vetoed, and the
123
+ * write handler reports the uncertainty. `absent`: screened as usual.
124
+ */
125
+ reconcile?: (skill: Skill, tool: string, args: unknown, ctx: ToolContext) => Promise<"applied" | "unknown" | "absent">;
117
126
  now?: () => number;
118
127
  }
119
128
  export interface ApplySafeOutputsResult {
@@ -545,26 +545,49 @@ export async function applySafeOutputs(o) {
545
545
  printPreview(write, o.logger);
546
546
  return { receipts };
547
547
  }
548
- // Model screen: veto only. It sees only what passed every deterministic check.
548
+ const toolCtx = {
549
+ config: o.config,
550
+ logger: o.logger,
551
+ ...(o.cwd ? { cwd: o.cwd } : {}),
552
+ ...(o.pushBranch ? { pushBranch: o.pushBranch } : {}),
553
+ ...(o.githubApiUrl ? { githubApiUrl: o.githubApiUrl } : {}),
554
+ ...(o.run ? { run: o.run } : {}),
555
+ ...(o.summary ? { summary: o.summary } : {}),
556
+ };
557
+ // Model screen: veto only. It sees only what passed every deterministic
558
+ // check and is not already applied: on a resume, outstanding writes are
559
+ // reconciled with the journal and the provider first, so a write that
560
+ // landed before the crash keeps its receipt, and one whose probe could not
561
+ // tell is reported as uncertain by its handler, never as vetoed.
562
+ let toApply = accepted;
549
563
  if (o.policy?.screen) {
550
- let verdict = null;
551
- try {
552
- verdict = o.screen ? await o.screen(accepted.map(({ write }) => ({ ...write }))) : null;
553
- }
554
- catch {
555
- verdict = null;
564
+ const state = new Map();
565
+ for (const a of accepted) {
566
+ const applier = APPLIERS[a.write.via][a.write.type];
567
+ const skill = o.skills.get(a.write.via);
568
+ state.set(a.index, o.reconcile && skill ? await o.reconcile(skill, applier.tool, applier.build(a.write), toolCtx) : "absent");
556
569
  }
557
- const allow = (verdict ?? "").trim().replace(/\.$/, "").toUpperCase() === "ALLOW";
558
- if (!allow) {
559
- o.logger.warn(` safe outputs: screen vetoed ${accepted.length} write(s)`, { node: o.nodeId });
560
- for (const { index } of accepted) {
561
- receipts[index] = { ...receipts[index], status: "vetoed", reason: "screen vetoed" };
570
+ const pending = accepted.filter((a) => state.get(a.index) === "absent");
571
+ if (pending.length > 0) {
572
+ let verdict = null;
573
+ try {
574
+ verdict = o.screen ? await o.screen(pending.map(({ write }) => ({ ...write }))) : null;
575
+ }
576
+ catch {
577
+ verdict = null;
578
+ }
579
+ const allow = (verdict ?? "").trim().replace(/\.$/, "").toUpperCase() === "ALLOW";
580
+ if (!allow) {
581
+ o.logger.warn(` safe outputs: screen vetoed ${pending.length} write(s)`, { node: o.nodeId });
582
+ for (const { index } of pending) {
583
+ receipts[index] = { ...receipts[index], status: "vetoed", reason: "screen vetoed" };
584
+ }
585
+ toApply = accepted.filter((a) => state.get(a.index) !== "absent");
562
586
  }
563
- return { receipts };
564
587
  }
565
588
  }
566
- for (let n = 0; n < accepted.length; n++) {
567
- const { index, write } = accepted[n];
589
+ for (let n = 0; n < toApply.length; n++) {
590
+ const { index, write } = toApply[n];
568
591
  const applier = APPLIERS[write.via][write.type];
569
592
  const skill = o.skills.get(write.via);
570
593
  const tool = skill?.tools.find((t) => t.name === applier.tool);
@@ -573,15 +596,7 @@ export async function applySafeOutputs(o) {
573
596
  continue;
574
597
  }
575
598
  try {
576
- const output = await tool.handler(applier.build(write), {
577
- config: o.config,
578
- logger: o.logger,
579
- ...(o.cwd ? { cwd: o.cwd } : {}),
580
- ...(o.pushBranch ? { pushBranch: o.pushBranch } : {}),
581
- ...(o.githubApiUrl ? { githubApiUrl: o.githubApiUrl } : {}),
582
- ...(o.run ? { run: o.run } : {}),
583
- ...(o.summary ? { summary: o.summary } : {}),
584
- });
599
+ const output = await tool.handler(applier.build(write), toolCtx);
585
600
  const ref = refOf(output);
586
601
  const url = urlOf(output);
587
602
  const produced = { ...(ref !== undefined ? { ref } : {}), ...(url ? { url } : {}) };
@@ -593,7 +608,7 @@ export async function applySafeOutputs(o) {
593
608
  // reason still counts as one.
594
609
  const reason = refusalReason(output);
595
610
  receipts[index] = { ...receipts[index], ...produced, status: "refused", reason };
596
- for (const rest of accepted.slice(n + 1)) {
611
+ for (const rest of toApply.slice(n + 1)) {
597
612
  receipts[rest.index] = { ...receipts[rest.index], status: "skipped", reason: "an earlier write was refused" };
598
613
  }
599
614
  return { receipts, error: `safe output ${write.type} via ${write.via} was refused: ${reason}` };
@@ -613,7 +628,7 @@ export async function applySafeOutputs(o) {
613
628
  catch (err) {
614
629
  const msg = err instanceof Error ? err.message : String(err);
615
630
  receipts[index] = { ...receipts[index], status: "failed", reason: "apply failed" };
616
- for (const rest of accepted.slice(n + 1)) {
631
+ for (const rest of toApply.slice(n + 1)) {
617
632
  receipts[rest.index] = { ...receipts[rest.index], status: "skipped", reason: "an earlier write failed" };
618
633
  }
619
634
  return { receipts, error: `safe output ${write.type} via ${write.via} failed: ${msg}` };
@@ -68,9 +68,11 @@ export const linear = {
68
68
  },
69
69
  required: ["query"],
70
70
  },
71
+ // `withDescription` is not in the schema: only the run journal's resume
72
+ // probe sets it, to match a write's marker. Agent results stay as they were.
71
73
  handler: async (input, ctx) => linearGql(`query($query: String!, $first: Int) {
72
74
  searchIssues(term: $query, first: $first) {
73
- nodes { id identifier title state { name } priority url }
75
+ nodes { id identifier title ${input.withDescription === true ? "description " : ""}state { name } priority url }
74
76
  }
75
77
  }`, { query: input.query, first: input.limit ?? 10 }, ctx),
76
78
  },
package/dist/studio.js CHANGED
@@ -120,7 +120,8 @@ export function applyExecutionEvent(event, nodeDataMap) {
120
120
  data.exec.currentTool = undefined;
121
121
  break;
122
122
  }
123
- case "node:exit": {
123
+ case "node:exit":
124
+ case "node:amended": {
124
125
  const data = nodeDataMap.get(event.node);
125
126
  if (data) {
126
127
  // NodeResult.status is already one of "success" | "failed" | "skipped";
package/dist/types.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { WorkflowInputs, WorkflowInputField, WorkflowInputType, InputValida
3
3
  export { WORKFLOW_INPUT_TYPES } from "./inputs.js";
4
4
  import type { Source as _Source, ResolvedSource as _ResolvedSource } from "./sources.js";
5
5
  import type { ToolClass } from "./harness/types.js";
6
- import type { Budget, BudgetOverrun } from "./budget.js";
6
+ import type { Budget, BudgetOverrun, SpendBreakdown } from "./budget.js";
7
7
  import type { DeciderConfig, DeciderRecord } from "./decider.js";
8
8
  export type JSONSchema = Record<string, unknown>;
9
9
  /** Context passed to tool handlers at execution time */
@@ -873,6 +873,16 @@ export type ExecutionEvent = {
873
873
  type: "node:exit";
874
874
  node: string;
875
875
  result: NodeResult;
876
+ }
877
+ /**
878
+ * A finished node's result changed after its `node:exit` (a budget stop at
879
+ * the route that followed it). Not a second exit: consumers that keep each
880
+ * node's final result replace it; timers and step printers ignore it.
881
+ */
882
+ | {
883
+ type: "node:amended";
884
+ node: string;
885
+ result: NodeResult;
876
886
  } | {
877
887
  type: "node:progress";
878
888
  node: string;
@@ -951,6 +961,13 @@ export interface ExecutionTrace {
951
961
  * URL, an open breaker or a spent call cap. Absent when nothing turned it off.
952
962
  */
953
963
  deciderOff?: string;
964
+ /**
965
+ * Model spend by kind: agent `node` runs and the executor's own `judge`,
966
+ * `route`, `reflect` (retry reflection) and `screen` completions. Absent
967
+ * unless one of those own completions reported usage. The run budget is
968
+ * over the sum. `node` covers every attempt of every visit.
969
+ */
970
+ spend?: SpendBreakdown;
954
971
  }
955
972
  /** Result of execute() — final node results + full execution trace */
956
973
  export interface ExecutionResult {
@@ -1047,6 +1064,14 @@ export interface Claude {
1047
1064
  timeoutMs?: number;
1048
1065
  /** Caller-supplied abort signal. Aborting it interrupts the query. */
1049
1066
  signal?: AbortSignal;
1067
+ /**
1068
+ * Called once with the completion's total usage, when the harness reports
1069
+ * it, before the outcome is known (a failed completion was still billed).
1070
+ * Unlike `run`'s `onUsage`, which streams cumulative reports while a node
1071
+ * runs, this is one report per completion. The executor counts it against
1072
+ * the run budget. A hook that throws never changes the result.
1073
+ */
1074
+ onUsage?: (usage: NodeUsage) => void;
1050
1075
  }): Promise<string | null>;
1051
1076
  /**
1052
1077
  * Single-completion free-text query. No tools, no output schema.
@@ -1064,6 +1089,14 @@ export interface Claude {
1064
1089
  timeoutMs?: number;
1065
1090
  /** Caller-supplied abort signal. Aborting it interrupts the query. */
1066
1091
  signal?: AbortSignal;
1092
+ /**
1093
+ * Called once with the completion's total usage, when the harness reports
1094
+ * it, before the outcome is known (a failed completion was still billed).
1095
+ * Unlike `run`'s `onUsage`, which streams cumulative reports while a node
1096
+ * runs, this is one report per completion. The executor counts it against
1097
+ * the run budget. A hook that throws never changes the result.
1098
+ */
1099
+ onUsage?: (usage: NodeUsage) => void;
1067
1100
  }): Promise<string>;
1068
1101
  }
1069
1102
  export interface McpServerConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.198",
3
+ "version": "0.1.200",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"