@foldrun/core 2.5.0 → 2.7.0

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 (70) hide show
  1. package/dist/src/agent-import.js +2 -2
  2. package/dist/src/agent-tools.js +2 -2
  3. package/dist/src/agents-md.d.ts +5 -1
  4. package/dist/src/agents-md.js +42 -18
  5. package/dist/src/capture.js +39 -1
  6. package/dist/src/completions.js +25 -3
  7. package/dist/src/deploy.js +72 -7
  8. package/dist/src/egress.d.ts +16 -0
  9. package/dist/src/evals.d.ts +22 -1
  10. package/dist/src/evals.js +43 -14
  11. package/dist/src/flow-lint.d.ts +14 -0
  12. package/dist/src/flow-lint.js +35 -0
  13. package/dist/src/flow-patterns.js +29 -14
  14. package/dist/src/frontmatter-fields.d.ts +27 -0
  15. package/dist/src/frontmatter-fields.js +51 -3
  16. package/dist/src/frontmatter-guard.d.ts +19 -0
  17. package/dist/src/frontmatter-guard.js +67 -0
  18. package/dist/src/frontmatter-schema.d.ts +2 -2
  19. package/dist/src/frontmatter-schema.js +30 -2
  20. package/dist/src/frontmatter-text.d.ts +31 -0
  21. package/dist/src/frontmatter-text.js +102 -0
  22. package/dist/src/judge-verdict.d.ts +19 -0
  23. package/dist/src/judge-verdict.js +54 -0
  24. package/dist/src/matter.d.ts +34 -0
  25. package/dist/src/matter.js +104 -0
  26. package/dist/src/network.d.ts +0 -1
  27. package/dist/src/network.js +10 -7
  28. package/dist/src/package.js +9 -2
  29. package/dist/src/provider-health.js +2 -2
  30. package/dist/src/run-container.d.ts +9 -0
  31. package/dist/src/run-container.js +34 -1
  32. package/dist/src/runner.js +193 -39
  33. package/dist/src/scan.d.ts +11 -0
  34. package/dist/src/scan.js +86 -8
  35. package/dist/src/secret-files.js +23 -2
  36. package/dist/src/secrets.d.ts +62 -2
  37. package/dist/src/secrets.js +206 -20
  38. package/dist/src/starter.js +23 -0
  39. package/dist/src/step-exec.d.ts +10 -2
  40. package/dist/src/step-exec.js +12 -9
  41. package/dist/src/store.d.ts +17 -3
  42. package/dist/src/store.js +14 -10
  43. package/package.json +1 -1
  44. package/src/agent-import.ts +2 -2
  45. package/src/agent-tools.ts +2 -2
  46. package/src/agents-md.ts +39 -14
  47. package/src/capture.ts +33 -1
  48. package/src/completions.ts +26 -3
  49. package/src/deploy.ts +59 -7
  50. package/src/egress.ts +15 -1
  51. package/src/evals.ts +45 -13
  52. package/src/flow-lint.ts +36 -0
  53. package/src/flow-patterns.ts +26 -13
  54. package/src/frontmatter-fields.ts +74 -3
  55. package/src/frontmatter-guard.ts +70 -0
  56. package/src/frontmatter-schema.ts +30 -4
  57. package/src/frontmatter-text.ts +109 -0
  58. package/src/judge-verdict.ts +52 -0
  59. package/src/matter.ts +112 -0
  60. package/src/network.ts +10 -6
  61. package/src/package.ts +8 -2
  62. package/src/provider-health.ts +2 -2
  63. package/src/run-container.ts +41 -1
  64. package/src/runner.ts +189 -40
  65. package/src/scan.ts +86 -8
  66. package/src/secret-files.ts +22 -2
  67. package/src/secrets.ts +237 -23
  68. package/src/starter.ts +23 -0
  69. package/src/step-exec.ts +21 -8
  70. package/src/store.ts +23 -10
package/src/step-exec.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  // results. That is what lets the same function run somewhere the vault, the
9
9
  // library and the account do not exist.
10
10
 
11
+ import { judgeVerdict, JUDGE_VERDICT_INSTRUCTION } from "./judge-verdict.ts";
11
12
  import { runHook, runToolCallHooks, matchesTool, hookLine, resolveHooks, type HookDef, type HookRecord } from "./hooks.ts";
12
13
  import fs from "node:fs";
13
14
  import os from "node:os";
@@ -133,6 +134,11 @@ export interface ExecOptions {
133
134
  * `matches: re`, `file: path`, `judge: sentence`) — see checkVerify. */
134
135
  verify?: string;
135
136
  verifyEnv?: Record<string, string>;
137
+ /** The model `verify: judge:` grades with — the workspace's `judge:` from
138
+ * AGENTS.md, resolved; absent, the fast tier. */
139
+ judgeModel?: string;
140
+ /** And how long it thinks — `judge: {effort}`; absent, the SDK's default. */
141
+ judgeEffort?: Effort;
136
142
  /** `output: json` — the reply must carry one JSON value; extracting it is
137
143
  * part of finishing the step, and failing to is failing the step. */
138
144
  output?: "json";
@@ -1066,6 +1072,8 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
1066
1072
  data,
1067
1073
  modelEnv: opts.env,
1068
1074
  signal: halt.signal,
1075
+ ...(opts.judgeModel ? { judgeModel: opts.judgeModel } : {}),
1076
+ ...(opts.judgeEffort ? { judgeEffort: opts.judgeEffort } : {}),
1069
1077
  });
1070
1078
  } finally {
1071
1079
  for (const t of clocks) clearTimeout(t);
@@ -1170,6 +1178,9 @@ export async function checkVerify(
1170
1178
  /** The step's own model environment, for `judge:` — it grades on the
1171
1179
  * fast tier through the same credential the step rode. */
1172
1180
  modelEnv?: Record<string, string | undefined>;
1181
+ /** The judge's model and effort (the workspace's `judge:`). */
1182
+ judgeModel?: string;
1183
+ judgeEffort?: Effort;
1173
1184
  /** Ends a shell check or a judge mid-way: the step's clock ran out, or a
1174
1185
  * person asked the run to stop. */
1175
1186
  signal?: AbortSignal;
@@ -1233,9 +1244,9 @@ export async function checkVerify(
1233
1244
  // GOOD` on a reply that did (fix-desk, run-muwevg72-7mza): the joined
1234
1245
  // text opened with the narration before it.
1235
1246
  const reply = ctx.conclusion?.trim() ? ctx.conclusion : output;
1236
- const verdict = await judgeReply(value, reply, output, ctx.modelEnv ?? {}, ctx.signal);
1237
- const ok = /^\s*PASS\b/i.test(verdict);
1238
- return { ok, headline: ok ? "PASS" : "FAIL", detail: verdict.slice(0, 300) };
1247
+ const said = await judgeReply(value, reply, output, ctx.modelEnv ?? {}, ctx.signal, ctx.judgeModel, ctx.judgeEffort);
1248
+ const { passed: ok, detail } = judgeVerdict(said);
1249
+ return { ok, headline: ok ? "PASS" : "FAIL", detail };
1239
1250
  }
1240
1251
  }
1241
1252
  return { ok: false, headline: `unknown check "${kind}"`, detail: "" };
@@ -1243,8 +1254,8 @@ export async function checkVerify(
1243
1254
 
1244
1255
  /**
1245
1256
  * A toolless, fast-tier grading call: does the reply satisfy the sentence?
1246
- * Same shape as the eval judge, and answered with one word first so the
1247
- * verdict is a prefix test rather than a reading.
1257
+ * Same shape as the eval judge: reasoning first, then the verdict alone on
1258
+ * the last line (judge-verdict.ts says why).
1248
1259
  */
1249
1260
  export function judgePrompt(rubric: string, reply: string, work: string): string {
1250
1261
  const shown = reply.slice(0, 20_000);
@@ -1253,8 +1264,7 @@ export function judgePrompt(rubric: string, reply: string, work: string): string
1253
1264
  `never this:\n<work>\n${work.slice(-20_000)}\n</work>\n\n`
1254
1265
  : "";
1255
1266
  return (
1256
- `You are grading a reply against one requirement. Answer with PASS or FAIL as the ` +
1257
- `first word, then one sentence of reason.\n\nRequirement: ${rubric}\n\n` +
1267
+ `You are grading a reply against one requirement. ${JUDGE_VERDICT_INSTRUCTION}\n\nRequirement: ${rubric}\n\n` +
1258
1268
  context +
1259
1269
  `<reply>\n${shown}\n</reply>`
1260
1270
  );
@@ -1266,6 +1276,8 @@ async function judgeReply(
1266
1276
  work: string,
1267
1277
  env: Record<string, string | undefined>,
1268
1278
  signal?: AbortSignal,
1279
+ model = "haiku",
1280
+ effort?: Effort,
1269
1281
  ): Promise<string> {
1270
1282
  const texts: string[] = [];
1271
1283
  const abortController = new AbortController();
@@ -1275,7 +1287,8 @@ async function judgeReply(
1275
1287
  const q = query({
1276
1288
  prompt: judgePrompt(rubric, reply, work),
1277
1289
  options: {
1278
- model: "haiku",
1290
+ model,
1291
+ ...(effort ? { effort } : {}),
1279
1292
  systemPrompt: "You grade text against a stated requirement. Be strict and literal.",
1280
1293
  tools: [],
1281
1294
  settingSources: [],
package/src/store.ts CHANGED
@@ -31,7 +31,7 @@ import { platform } from "./platform.ts";
31
31
  import { toLF, asRaw } from "./eol.ts";
32
32
  import type { TestEffect } from "./test-mode.ts";
33
33
  import type { OperatorEvent } from "./operator.ts";
34
- import matter from "./matter.ts";
34
+ import matter, { frontmatterErrorText, configMatter } from "./matter.ts";
35
35
  import { readAgentsMdKeys } from "./agents-md.ts";
36
36
 
37
37
  /**
@@ -44,7 +44,7 @@ import { readAgentsMdKeys } from "./agents-md.ts";
44
44
  * the runtime, and there is nothing to fail to import.
45
45
  */
46
46
  export function readFrontmatter(file: string): Record<string, unknown> {
47
- return matter(fs.readFileSync(file, "utf8")).data as Record<string, unknown>;
47
+ return configMatter(fs.readFileSync(file, "utf8")).data as Record<string, unknown>;
48
48
  }
49
49
  import { ownToolNames, legacyUseNames, retiredToolNames } from "./tool-names.ts";
50
50
  import { refNames } from "./refs.ts";
@@ -1602,7 +1602,7 @@ export function listAgents(tenant: string, workspace: string): AgentInfo[] {
1602
1602
  let data: Record<string, any> = {};
1603
1603
  let frontmatterError: string | undefined;
1604
1604
  try {
1605
- ({ data } = matter(fs.readFileSync(path.join(dir, name, "agent.md"), "utf8")));
1605
+ ({ data } = configMatter(fs.readFileSync(path.join(dir, name, "agent.md"), "utf8")));
1606
1606
  } catch (err) {
1607
1607
  frontmatterError = frontmatterErrorText(err);
1608
1608
  }
@@ -1652,12 +1652,9 @@ export function listAgents(tenant: string, workspace: string): AgentInfo[] {
1652
1652
  });
1653
1653
  }
1654
1654
 
1655
- /** The first line of a YAML parse error, with where it is: what a person
1656
- * needs to fix the file, without the parser's excerpt. */
1657
- export function frontmatterErrorText(err: unknown): string {
1658
- const msg = err instanceof Error ? err.message : String(err);
1659
- return `frontmatter does not parse — ${msg.split("\n")[0].trim()}. Quote a value that contains a colon followed by a space.`;
1660
- }
1655
+ // Kept here for callers that import it from the store; it lives in matter.ts
1656
+ // so the scanner can use it without the store.
1657
+ export { frontmatterErrorText };
1661
1658
 
1662
1659
  /** The body of a file whose frontmatter would not parse: what follows the
1663
1660
  * closing fence, so a flow's steps can still be read and shown. */
@@ -1839,7 +1836,7 @@ export function parseFlow(file: string, raw: string): FlowInfo {
1839
1836
  let content: string;
1840
1837
  let frontError: string | null = null;
1841
1838
  try {
1842
- ({ data, content } = matter(raw));
1839
+ ({ data, content } = configMatter(raw));
1843
1840
  } catch (err) {
1844
1841
  frontError = frontmatterErrorText(err);
1845
1842
  content = bodyAfterFrontmatter(raw);
@@ -3297,6 +3294,15 @@ export function sumAttempts(tries: StepAttempt[]): Pick<StepAttempt, "costUsd" |
3297
3294
  export { type TokenUsage, type PromptLayerRecord, tokensOf, sumTokens, cacheHitRate, measuredHitRate, runTokens, promptLayersChanged, promptCacheNote } from "./tokens.ts";
3298
3295
  import { sumTokens, type TokenUsage, type PromptLayerRecord } from "./tokens.ts";
3299
3296
 
3297
+ /** The marker rescueStep puts in a rescue's instruction; how a rescue record
3298
+ * written before `rescueOf` existed is still recognised. */
3299
+ export const RESCUE_MARKER = "<rescue>\nYou are taking this step over:";
3300
+
3301
+ /** Is this record an on-fail rescue rather than a step of the flow? */
3302
+ export function isRescueRecord(step: { rescueOf?: string; instruction?: string | null }): boolean {
3303
+ return !!step.rescueOf || (step.instruction ?? "").includes(RESCUE_MARKER);
3304
+ }
3305
+
3300
3306
  export interface StepRecord {
3301
3307
  agent: string;
3302
3308
  instruction: string;
@@ -3450,6 +3456,13 @@ export interface StepRecord {
3450
3456
  * actually executed in. Carried steps provide context and are never
3451
3457
  * billed again — the original run's ledger line already paid for them. */
3452
3458
  carriedFrom?: string;
3459
+ /** Set on an on-fail rescue record (runner.ts rescueStep): the agent of the
3460
+ * failed step it took over. A rescue is not a step of the flow: a resumed
3461
+ * drive must not launch it as one of its group, and a re-run must not
3462
+ * copy it as work (21 Oct 2026: one rerun carried 77 fixers as pending
3463
+ * steps). Records written before this field are recognised by their
3464
+ * instruction's `<rescue>` block instead — see isRescueRecord. */
3465
+ rescueOf?: string;
3453
3466
  /**
3454
3467
  * Sandbox seconds this step rented, and how many of them were cold start.
3455
3468
  * Set only on the isolated path — an in-process step rents nothing, so