@foldrun/core 2.3.0 → 2.5.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 (135) hide show
  1. package/dist/src/agent-activity.d.ts +5 -0
  2. package/dist/src/agent-activity.js +31 -0
  3. package/dist/src/agent-import.js +1 -1
  4. package/dist/src/agent-tools.js +1 -1
  5. package/dist/src/agents-md.js +1 -1
  6. package/dist/src/alerts.d.ts +60 -0
  7. package/dist/src/alerts.js +161 -0
  8. package/dist/src/browser-pod.js +4 -0
  9. package/dist/src/capture.d.ts +23 -0
  10. package/dist/src/capture.js +80 -0
  11. package/dist/src/compare.d.ts +57 -0
  12. package/dist/src/compare.js +116 -0
  13. package/dist/src/completions.js +1 -0
  14. package/dist/src/deploy.d.ts +36 -1
  15. package/dist/src/deploy.js +170 -11
  16. package/dist/src/egress.d.ts +16 -1
  17. package/dist/src/evals.d.ts +5 -1
  18. package/dist/src/evals.js +26 -18
  19. package/dist/src/frontmatter-fields.js +9 -0
  20. package/dist/src/frontmatter-schema.js +3 -0
  21. package/dist/src/history.d.ts +4 -0
  22. package/dist/src/history.js +15 -0
  23. package/dist/src/hooks.js +1 -1
  24. package/dist/src/insights.d.ts +30 -0
  25. package/dist/src/insights.js +152 -0
  26. package/dist/src/issues.d.ts +140 -0
  27. package/dist/src/issues.js +302 -0
  28. package/dist/src/library.js +1 -1
  29. package/dist/src/limits.d.ts +8 -0
  30. package/dist/src/limits.js +36 -5
  31. package/dist/src/linkable.js +1 -1
  32. package/dist/src/matter.d.ts +8 -0
  33. package/dist/src/matter.js +28 -0
  34. package/dist/src/model-credential.d.ts +14 -0
  35. package/dist/src/model-credential.js +40 -0
  36. package/dist/src/model-env.d.ts +14 -2
  37. package/dist/src/model-env.js +34 -9
  38. package/dist/src/network.d.ts +12 -0
  39. package/dist/src/network.js +25 -1
  40. package/dist/src/notify.d.ts +16 -0
  41. package/dist/src/notify.js +17 -7
  42. package/dist/src/okf.js +1 -1
  43. package/dist/src/online-checks.d.ts +76 -0
  44. package/dist/src/online-checks.js +432 -0
  45. package/dist/src/operator.d.ts +277 -0
  46. package/dist/src/operator.js +176 -2
  47. package/dist/src/otel-config.d.ts +18 -0
  48. package/dist/src/otel-config.js +69 -0
  49. package/dist/src/otel.d.ts +38 -0
  50. package/dist/src/otel.js +85 -0
  51. package/dist/src/outcome.d.ts +40 -0
  52. package/dist/src/outcome.js +77 -0
  53. package/dist/src/package.js +1 -1
  54. package/dist/src/platform.d.ts +1 -1
  55. package/dist/src/prompt-layers.d.ts +30 -0
  56. package/dist/src/prompt-layers.js +77 -0
  57. package/dist/src/provider-health.js +1 -1
  58. package/dist/src/providers.d.ts +5 -0
  59. package/dist/src/providers.js +2 -2
  60. package/dist/src/run-container.d.ts +16 -2
  61. package/dist/src/run-container.js +57 -5
  62. package/dist/src/runner.d.ts +53 -2
  63. package/dist/src/runner.js +356 -70
  64. package/dist/src/runs-filter.d.ts +48 -0
  65. package/dist/src/runs-filter.js +73 -0
  66. package/dist/src/scan.d.ts +19 -1
  67. package/dist/src/scan.js +60 -7
  68. package/dist/src/starter.js +4 -2
  69. package/dist/src/step-exec.d.ts +29 -1
  70. package/dist/src/step-exec.js +106 -6
  71. package/dist/src/store.d.ts +130 -3
  72. package/dist/src/store.js +145 -6
  73. package/dist/src/tokens.d.ts +38 -0
  74. package/dist/src/tokens.js +57 -0
  75. package/dist/src/tool-names.js +1 -1
  76. package/dist/src/tool-programs.js +1 -1
  77. package/dist/src/trace-tree.d.ts +40 -0
  78. package/dist/src/trace-tree.js +156 -0
  79. package/dist/src/translator.d.ts +8 -0
  80. package/dist/src/translator.js +7 -2
  81. package/dist/src/trigger-log.d.ts +1 -1
  82. package/dist/src/workspace-check.d.ts +18 -0
  83. package/dist/src/workspace-check.js +34 -0
  84. package/package.json +2 -2
  85. package/src/agent-activity.ts +32 -0
  86. package/src/agent-import.ts +1 -1
  87. package/src/agent-tools.ts +1 -1
  88. package/src/agents-md.ts +1 -1
  89. package/src/alerts.ts +190 -0
  90. package/src/browser-pod.ts +3 -0
  91. package/src/capture.ts +88 -0
  92. package/src/compare.ts +146 -0
  93. package/src/completions.ts +1 -0
  94. package/src/deploy.ts +184 -10
  95. package/src/egress.ts +12 -2
  96. package/src/evals.ts +26 -18
  97. package/src/frontmatter-fields.ts +9 -0
  98. package/src/frontmatter-schema.ts +3 -0
  99. package/src/history.ts +13 -0
  100. package/src/hooks.ts +1 -1
  101. package/src/insights.ts +174 -0
  102. package/src/issues.ts +396 -0
  103. package/src/library.ts +1 -1
  104. package/src/limits.ts +38 -5
  105. package/src/linkable.ts +1 -1
  106. package/src/matter.ts +33 -0
  107. package/src/model-credential.ts +45 -0
  108. package/src/model-env.ts +42 -10
  109. package/src/network.ts +25 -1
  110. package/src/notify.ts +22 -7
  111. package/src/okf.ts +1 -1
  112. package/src/online-checks.ts +450 -0
  113. package/src/operator.ts +213 -3
  114. package/src/otel-config.ts +67 -0
  115. package/src/otel.ts +100 -0
  116. package/src/outcome.ts +90 -0
  117. package/src/package.ts +1 -1
  118. package/src/platform.ts +1 -1
  119. package/src/prompt-layers.ts +90 -0
  120. package/src/provider-health.ts +1 -1
  121. package/src/providers.ts +7 -2
  122. package/src/run-container.ts +66 -7
  123. package/src/runner.ts +363 -46
  124. package/src/runs-filter.ts +88 -0
  125. package/src/scan.ts +68 -7
  126. package/src/starter.ts +4 -2
  127. package/src/step-exec.ts +147 -9
  128. package/src/store.ts +254 -8
  129. package/src/tokens.ts +68 -0
  130. package/src/tool-names.ts +1 -1
  131. package/src/tool-programs.ts +1 -1
  132. package/src/trace-tree.ts +187 -0
  133. package/src/translator.ts +13 -2
  134. package/src/trigger-log.ts +1 -0
  135. package/src/workspace-check.ts +41 -0
package/src/providers.ts CHANGED
@@ -54,6 +54,11 @@ export interface ProviderPreset {
54
54
  * Off means the translator drops thinking rather than send a parameter
55
55
  * the endpoint would reject. */
56
56
  reasoningEffort?: boolean;
57
+ /** Whether the endpoint takes `prompt_cache_key` — a hint that requests
58
+ * with the same key share a prompt prefix, so its cache can be routed
59
+ * to. Only set where the parameter is documented; an endpoint that does
60
+ * not know it may refuse the request. */
61
+ promptCacheKey?: boolean;
57
62
  /** One line a person should read before relying on it. */
58
63
  note?: string;
59
64
  /** A tool loop was driven through this endpoint from this runtime. */
@@ -427,7 +432,7 @@ export const PROVIDERS: readonly ProviderPreset[] = [
427
432
 
428
433
  // ------------------------------------------- Chat-Completions, translated
429
434
  { name: "openai", title: "OpenAI", format: "openai", baseUrl: "https://api.openai.com/v1", auth: "bearer", verified: true,
430
- maxTokensParam: "max_completion_tokens", reasoningEffort: true,
435
+ maxTokensParam: "max_completion_tokens", reasoningEffort: true, promptCacheKey: true,
431
436
  note: "Reached over Chat Completions, which OpenAI keeps supporting; the Responses API is not spoken here, so a reasoning model's reasoning does not carry across tool calls." , search: "openai" },
432
437
  { name: "gemini", title: "Google Gemini", format: "openai", baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai", auth: "bearer",
433
438
  note: "Google's OpenAI-compatible route. Unknown parameters are ignored silently; reasoning cannot be switched off on the newest models." },
@@ -435,7 +440,7 @@ export const PROVIDERS: readonly ProviderPreset[] = [
435
440
  maxTokensParam: "max_completion_tokens", reasoningEffort: true },
436
441
  { name: "groq", title: "Groq", format: "openai", baseUrl: "https://api.groq.com/openai/v1", auth: "bearer",
437
442
  maxTokensParam: "max_completion_tokens", reasoningEffort: true },
438
- { name: "mistral", title: "Mistral", format: "openai", baseUrl: "https://api.mistral.ai/v1", auth: "bearer" },
443
+ { name: "mistral", title: "Mistral", format: "openai", baseUrl: "https://api.mistral.ai/v1", auth: "bearer", promptCacheKey: true },
439
444
  { name: "together", title: "Together AI", format: "openai", baseUrl: "https://api.together.ai/v1", auth: "bearer" },
440
445
  { name: "cerebras", title: "Cerebras", format: "openai", baseUrl: "https://api.cerebras.ai/v1", auth: "bearer",
441
446
  maxTokensParam: "max_completion_tokens", reasoningEffort: true },
@@ -18,6 +18,7 @@
18
18
  // The runner image bundles @foldrun/core itself, so the driver runs the same
19
19
  // executeStep the server would — one implementation, two homes.
20
20
 
21
+ import type { PeerInfo } from "./operator.ts";
21
22
  import { isHookRecord, type HookRecord } from "./hooks.ts";
22
23
  import fs from "node:fs";
23
24
  import os from "node:os";
@@ -25,9 +26,9 @@ import path from "node:path";
25
26
  import crypto from "node:crypto";
26
27
  import { spawn, spawnSync } from "node:child_process";
27
28
  import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
28
- import { isPlatformPath, type ApiSpec, type Effort } from "./store.ts";
29
+ import { CAPTURE_CAP, isPlatformPath, type ApiSpec, type Effort, type ModelCall } from "./store.ts";
29
30
  import { divertedSummary, isDivertedPath, overlayTestWrites } from "./test-mode.ts";
30
- import type { EventExtra, ExecUsage } from "./step-exec.ts";
31
+ import type { CallIo, EventExtra, ExecUsage } from "./step-exec.ts";
31
32
  import type { TokenPrice } from "./catalog.ts";
32
33
  import { isFileValue, fileContent } from "./secrets.ts";
33
34
  import { safeTenantSegment, systemInstallSh, SYSTEM_FROM_INPUT_SH, INSTALL_SOCKET, type RuntimeSpec } from "./runtime.ts";
@@ -77,6 +78,8 @@ export interface ContainerStepInput {
77
78
  /** `schema:` and `max_turns:`, resolved host-side — see ExecOptions. */
78
79
  schema?: Record<string, unknown> | boolean;
79
80
  maxTurns?: number;
81
+ /** `capture:` resolved host-side (FOL-54 step 4) — see ExecOptions. */
82
+ capture?: "metadata" | "full";
80
83
  /** `limits:` resolved host-side, and the SDK-name → foldrun-name map the
81
84
  * count reads (limits.ts) — enforced in the pod by the same hook. */
82
85
  limits?: Record<string, number>;
@@ -93,6 +96,10 @@ export interface ContainerStepInput {
93
96
  desks?: RunDigest[];
94
97
  /** tools: [ask] — ask_person, and how long it may wait for an answer. */
95
98
  ask?: { timeoutSec: number };
99
+ /** tools: [peers] — this step's agent and the other steps of its
100
+ * parallel group, gathered host-side at launch. The driver rebuilds the
101
+ * colleagues' tools in here over the lease's /peers door. */
102
+ peers?: { self: string; siblings: PeerInfo[] };
96
103
  /** A Chat-Completions provider: the driver starts the runtime's translator
97
104
  * on loopback and points the SDK at it. Null or absent: the env's
98
105
  * ANTHROPIC_BASE_URL is spoken to directly. */
@@ -614,7 +621,7 @@ try {
614
621
  const { buildScriptTools } = await import("@foldrun/core/script-tools");
615
622
  const { buildConsultTools } = await import("@foldrun/core/agent-tools");
616
623
  const { buildSearchTools, buildHistoryTools, buildDeskTools } = await import("@foldrun/core/context-tools");
617
- const { httpChannel, buildAskTool, inboxReader } = await import("@foldrun/core/operator");
624
+ const { httpChannel, buildAskTool, buildPeerTools, inboxReader } = await import("@foldrun/core/operator");
618
625
  const { startTranslator } = await import("@foldrun/core/translator");
619
626
  const { prepareRuntime } = await import("@foldrun/core/runtime");
620
627
  const { materializeFileSecrets } = await import("@foldrun/core/secret-files");
@@ -678,11 +685,18 @@ try {
678
685
  // A long result is kept in a file only when the agent can read it back.
679
686
  // (tool-output.ts canReadBack, inlined: the driver imports core by subpath.)
680
687
  const keepIn = { dir: agentDir, readable: () => (input.allowed ?? []).some((t) => t === "Read" || t === "Grep") };
681
- const api = buildApiTools("", input.apis, undefined, { env, missing: [] }, keepIn);
688
+ // A declared secret named like a model variable crosses as
689
+ // FOLDRUN_DECLARED_<NAME> (runner.ts, FOL-19): the model keeps its own
690
+ // settings, and the agent's API and script tools and verify: get the value
691
+ // back under the name it declared. Built here, after the runtime and any
692
+ // mid-step install have added their paths to env.
693
+ const toolEnv = { ...env };
694
+ for (const [k, v] of Object.entries(env)) if (k.startsWith("FOLDRUN_DECLARED_")) toolEnv[k.slice("FOLDRUN_DECLARED_".length)] = v;
695
+ const api = buildApiTools("", input.apis, undefined, { env: toolEnv, missing: [] }, keepIn);
682
696
  const script = buildScriptTools(
683
697
  agentDir,
684
698
  input.scripts,
685
- { ...env, ...runtime.env },
699
+ toolEnv,
686
700
  "/library/scripts",
687
701
  runtime.interpreters,
688
702
  null,
@@ -705,6 +719,7 @@ try {
705
719
  // ask_person when tools: [ask], and the run's inbox after every tool call.
706
720
  const channel = httpChannel(env.FOLDRUN_EGRESS);
707
721
  const ask = input.ask ? buildAskTool({ channel, timeoutSec: input.ask.timeoutSec, emit }) : { server: null };
722
+ const peers = input.peers ? buildPeerTools({ channel, self: input.peers.self, siblings: input.peers.siblings, emit }) : { server: null };
708
723
  const inbox = channel ? inboxReader(channel, emit) : undefined;
709
724
 
710
725
  mark("tools");
@@ -726,6 +741,7 @@ try {
726
741
  ...(history.server ? { foldrun_history: history.server } : {}),
727
742
  ...(desks.server ? { foldrun_desks: desks.server } : {}),
728
743
  ...(ask.server ? { foldrun_ask: ask.server } : {}),
744
+ ...(peers.server ? { foldrun_peers: peers.server } : {}),
729
745
  ...(install.server ? { foldrun_install: install.server } : {}),
730
746
  ...input.mcpServers,
731
747
  },
@@ -737,10 +753,11 @@ try {
737
753
  budgetNote: input.budgetNote,
738
754
  price: input.price,
739
755
  verify: input.verify,
740
- verifyEnv: env,
756
+ verifyEnv: toolEnv,
741
757
  output: input.output,
742
758
  schema: input.schema,
743
759
  maxTurns: input.maxTurns,
760
+ ...(input.capture === "metadata" || input.capture === "full" ? { capture: input.capture } : {}),
744
761
  ...(input.limits && Object.keys(input.limits).length ? { limits: input.limits, toolOwners: input.toolOwners ?? {} } : {}),
745
762
  ...(input.hookNames?.length ? { hookNames: input.hookNames, hookBase: input.hookBase ?? {} } : {}),
746
763
  // Slim browsing: the platform put this step on the slim image to browse
@@ -1190,9 +1207,47 @@ export function buildFailureSummary(log: string, opts: { tail?: number; maxLines
1190
1207
 
1191
1208
  // ---------- running one step ----------
1192
1209
 
1210
+ const num = (v: unknown) => (typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0);
1211
+
1212
+ /** A tool call's content from the sandbox, as far as it is well-formed and
1213
+ * within the cap; anything else is dropped (FOL-54 step 4). */
1214
+ function callIoOf(v: unknown): CallIo | null {
1215
+ if (!v || typeof v !== "object") return null;
1216
+ const o = v as Record<string, unknown>;
1217
+ // The cap plus the slack the runner masks inside before it cuts.
1218
+ const text = (x: unknown) => (typeof x === "string" ? x.slice(0, CAPTURE_CAP + 4096) : undefined);
1219
+ const io: CallIo = {
1220
+ ...(text(o.input) !== undefined ? { input: text(o.input) } : {}),
1221
+ ...(text(o.output) !== undefined ? { output: text(o.output) } : {}),
1222
+ ...(typeof o.inputChars === "number" ? { inputChars: num(o.inputChars) } : {}),
1223
+ ...(typeof o.outputChars === "number" ? { outputChars: num(o.outputChars) } : {}),
1224
+ ...(o.truncated === true ? { truncated: true } : {}),
1225
+ };
1226
+ return io.input !== undefined || io.output !== undefined ? io : null;
1227
+ }
1228
+
1229
+ /** One model call from the sandbox, well-formed or dropped. */
1230
+ function modelCallOf(v: unknown): ModelCall | null {
1231
+ if (!v || typeof v !== "object") return null;
1232
+ const o = v as Record<string, unknown>;
1233
+ const t = (o.tokens ?? {}) as Record<string, unknown>;
1234
+ if (typeof o.id !== "string" || !o.id || o.id.length > 128 || typeof o.at !== "string" || Number.isNaN(Date.parse(o.at))) return null;
1235
+ return {
1236
+ id: o.id,
1237
+ n: Math.max(1, Math.floor(num(o.n))),
1238
+ at: o.at,
1239
+ ms: num(o.ms),
1240
+ ...(typeof o.model === "string" ? { model: o.model.slice(0, 128) } : {}),
1241
+ ...(typeof o.stop === "string" ? { stop: o.stop.slice(0, 64) } : {}),
1242
+ tokens: { input: num(t.input), output: num(t.output), cacheRead: num(t.cacheRead), cacheWrite: num(t.cacheWrite) },
1243
+ tools: Math.floor(num(o.tools)),
1244
+ ...(typeof o.subagent === "string" && /^[a-z0-9-]{1,64}$/.test(o.subagent) ? { subagent: o.subagent } : {}),
1245
+ };
1246
+ }
1247
+
1193
1248
  export function parseDriverLine(
1194
1249
  line: string,
1195
- ): { e: "event"; type: "text" | "tool" | "info" | "error"; text: string; call?: string; ms?: number; err?: boolean; bytes?: number; operator?: OperatorEvent; subagent?: string; check?: boolean; hook?: HookRecord } | { e: "done" } & ContainerStepOutcome | null {
1250
+ ): { e: "event"; type: "text" | "tool" | "info" | "error"; text: string; call?: string; ms?: number; err?: boolean; bytes?: number; operator?: OperatorEvent; subagent?: string; check?: boolean; hook?: HookRecord; io?: CallIo; turn?: ModelCall; inCall?: string; responding?: true } | { e: "done" } & ContainerStepOutcome | null {
1196
1251
  const trimmed = line.trim();
1197
1252
  if (!trimmed.startsWith("{")) return null;
1198
1253
  try {
@@ -1210,6 +1265,10 @@ export function parseDriverLine(
1210
1265
  ...(typeof parsed.subagent === "string" && /^[a-z0-9-]{1,64}$/.test(parsed.subagent) ? { subagent: parsed.subagent } : {}),
1211
1266
  ...(parsed.check === true ? { check: true } : {}),
1212
1267
  ...(isHookRecord(parsed.hook) ? { hook: parsed.hook } : {}),
1268
+ ...(callIoOf(parsed.io) ? { io: callIoOf(parsed.io)! } : {}),
1269
+ ...(typeof parsed.inCall === "string" && parsed.inCall.length <= 128 ? { inCall: parsed.inCall } : {}),
1270
+ ...(modelCallOf(parsed.turn) ? { turn: modelCallOf(parsed.turn)! } : {}),
1271
+ ...(parsed.responding === true ? { responding: true as const } : {}),
1213
1272
  };
1214
1273
  }
1215
1274
  if (parsed.e === "done") {