@foldrun/core 7.0.2 → 8.0.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.
package/dist/src/mail.js CHANGED
@@ -197,7 +197,7 @@ export async function mailWanted(tenant, email, category, workspace = null) {
197
197
  // on dev let anyone send ~86k invites a day from hello@foldrun.io).
198
198
  const UNCAPPED = new Set(["security", "billing", "account"]);
199
199
  const PLATFORM_MAX_RECIPIENTS = 10;
200
- const platformDayCap = (category) => Number(process.env[category === "invites" ? "FOLDRUN_PLATFORM_INVITES_PER_DAY" : "FOLDRUN_PLATFORM_MAIL_PER_DAY"]) ||
200
+ const platformDayCap = (category) => Number(platform.setting(category === "invites" ? "FOLDRUN_PLATFORM_INVITES_PER_DAY" : "FOLDRUN_PLATFORM_MAIL_PER_DAY")) ||
201
201
  (category === "invites" ? 25 : 200);
202
202
  const platformSent = new Map();
203
203
  /** Recipients this account may still send to today on the platform's key. */
@@ -58,7 +58,7 @@ export function platformMail(tenant) {
58
58
  return own;
59
59
  const key = process.env.FOLDRUN_RESEND_API_KEY;
60
60
  if (key)
61
- return { key, from: process.env.FOLDRUN_EMAIL_FROM || "foldrun <hello@foldrun.io>" };
61
+ return { key, from: platform.setting("FOLDRUN_EMAIL_FROM") || "foldrun <hello@foldrun.io>" };
62
62
  return own;
63
63
  }
64
64
  /** The account's own Resend key and sender, or null when it has none. */
@@ -142,6 +142,15 @@ export interface PlatformHooks {
142
142
  * operator's accounts do so; every customer brings their own API key.
143
143
  * Default: yes — a laptop or a self-hosted install is its own operator. */
144
144
  platformKeyAllowed(tenant: string): boolean;
145
+ /**
146
+ * An operator setting, by the environment variable that used to hold it
147
+ * (30 Oct 2026): the platform answers with the operator's value where one
148
+ * is set (its settings store) and the variable otherwise, spelled as the
149
+ * variable would be. Read where core reads FOLDRUN_EMAIL_FROM, the
150
+ * platform mail caps and the step sizes. Default: the variable itself —
151
+ * a laptop or the CLI reads its environment as before.
152
+ */
153
+ setting(env: string): string | undefined;
145
154
  /**
146
155
  * Where the platform's built-in tools sit on disk, materialised at boot —
147
156
  * a shelf every account reads, beneath its own library. A name found in
@@ -42,6 +42,7 @@ const local = {
42
42
  workspaceChanged() { },
43
43
  sandboxResumable: () => false,
44
44
  platformKeyAllowed: () => true,
45
+ setting: (env) => process.env[env],
45
46
  galleryDir: () => null,
46
47
  noteOAuthRefresh: async () => undefined,
47
48
  fetchUntrusted: Object.assign((url, init) => fetch(url, init), { [LAPTOP_FETCH]: 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
  import { isHookRecord } from "./hooks.js";
21
+ import { platform } from "./platform.js";
21
22
  import fs from "node:fs";
22
23
  import os from "node:os";
23
24
  import path from "node:path";
@@ -1451,8 +1452,8 @@ export function k8sMemory(value) {
1451
1452
  export function sizeLimits(size) {
1452
1453
  if (size === "small") {
1453
1454
  return {
1454
- cpus: process.env.FOLDRUN_RUNNER_CPUS_SMALL ?? "1",
1455
- memory: process.env.FOLDRUN_RUNNER_MEMORY_SMALL ?? "1Gi",
1455
+ cpus: platform.setting("FOLDRUN_RUNNER_CPUS_SMALL") ?? "1",
1456
+ memory: platform.setting("FOLDRUN_RUNNER_MEMORY_SMALL") ?? "1Gi",
1456
1457
  };
1457
1458
  }
1458
1459
  if (size === "heavy") {
@@ -1462,13 +1463,13 @@ export function sizeLimits(size) {
1462
1463
  // cpus figure is both the rate its seconds are priced at and, since
1463
1464
  // 26 Oct 2026, the cores the sandbox is capped at.
1464
1465
  return {
1465
- cpus: process.env.FOLDRUN_RUNNER_CPUS_HEAVY ?? "4",
1466
- memory: process.env.FOLDRUN_RUNNER_MEMORY_HEAVY ?? "8Gi",
1466
+ cpus: platform.setting("FOLDRUN_RUNNER_CPUS_HEAVY") ?? "4",
1467
+ memory: platform.setting("FOLDRUN_RUNNER_MEMORY_HEAVY") ?? "8Gi",
1467
1468
  };
1468
1469
  }
1469
1470
  return {
1470
- cpus: process.env.FOLDRUN_RUNNER_CPUS ?? "2",
1471
- memory: process.env.FOLDRUN_RUNNER_MEMORY ?? "2g",
1471
+ cpus: platform.setting("FOLDRUN_RUNNER_CPUS") ?? "2",
1472
+ memory: platform.setting("FOLDRUN_RUNNER_MEMORY") ?? "2g",
1472
1473
  };
1473
1474
  }
1474
1475
  /**
@@ -1397,6 +1397,13 @@ export interface StepRecord {
1397
1397
  txBytes: number | null;
1398
1398
  } | null;
1399
1399
  }
1400
+ /** A run's prices as they stood when it was admitted: the operator
1401
+ * settings' version and the figures themselves, by name. */
1402
+ export interface PriceSnapshot {
1403
+ version: number;
1404
+ at: string;
1405
+ values: Record<string, number>;
1406
+ }
1400
1407
  export interface RunRecord {
1401
1408
  id: string;
1402
1409
  flow: string;
@@ -1405,6 +1412,12 @@ export interface RunRecord {
1405
1412
  status: "queued" | "running" | "awaiting-approval" | "completed" | "failed";
1406
1413
  startedAt: string;
1407
1414
  finishedAt: string | null;
1415
+ /** The prices the run was admitted under — the platform's run-start
1416
+ * snapshot of its operator settings (30 Oct 2026). Its settlement and
1417
+ * its `budget:` read these, never a price changed while it ran. Opaque
1418
+ * to core; absent on a laptop, and on runs from before snapshots (those
1419
+ * price at the live figures). */
1420
+ pricing?: PriceSnapshot | null;
1408
1421
  /** Set when a worker parked this run at an approval gate and returned its
1409
1422
  * slot to the queue. The approval API re-enqueues exactly when this is
1410
1423
  * set — without the marker it would also re-enqueue runs whose starting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldrun/core",
3
- "version": "7.0.2",
3
+ "version": "8.0.0",
4
4
  "description": "Agents are just folders. The format, the runner, the checks.",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",
package/src/mail.ts CHANGED
@@ -268,7 +268,7 @@ export async function mailWanted(tenant: string, email: string, category: MailCa
268
268
  const UNCAPPED = new Set<string>(["security", "billing", "account"]);
269
269
  const PLATFORM_MAX_RECIPIENTS = 10;
270
270
  const platformDayCap = (category: string) =>
271
- Number(process.env[category === "invites" ? "FOLDRUN_PLATFORM_INVITES_PER_DAY" : "FOLDRUN_PLATFORM_MAIL_PER_DAY"]) ||
271
+ Number(platform.setting(category === "invites" ? "FOLDRUN_PLATFORM_INVITES_PER_DAY" : "FOLDRUN_PLATFORM_MAIL_PER_DAY")) ||
272
272
  (category === "invites" ? 25 : 200);
273
273
  const platformSent = new Map<string, number>();
274
274
 
package/src/notify.ts CHANGED
@@ -58,7 +58,7 @@ export function platformMail(tenant: string): { key: string; from: string } | nu
58
58
  const own = accountMail(tenant);
59
59
  if (own && getSecret(tenant, "EMAIL_FROM")?.value?.trim()) return own;
60
60
  const key = process.env.FOLDRUN_RESEND_API_KEY;
61
- if (key) return { key, from: process.env.FOLDRUN_EMAIL_FROM || "foldrun <hello@foldrun.io>" };
61
+ if (key) return { key, from: platform.setting("FOLDRUN_EMAIL_FROM") || "foldrun <hello@foldrun.io>" };
62
62
  return own;
63
63
  }
64
64
 
package/src/platform.ts CHANGED
@@ -149,6 +149,15 @@ export interface PlatformHooks {
149
149
  * operator's accounts do so; every customer brings their own API key.
150
150
  * Default: yes — a laptop or a self-hosted install is its own operator. */
151
151
  platformKeyAllowed(tenant: string): boolean;
152
+ /**
153
+ * An operator setting, by the environment variable that used to hold it
154
+ * (30 Oct 2026): the platform answers with the operator's value where one
155
+ * is set (its settings store) and the variable otherwise, spelled as the
156
+ * variable would be. Read where core reads FOLDRUN_EMAIL_FROM, the
157
+ * platform mail caps and the step sizes. Default: the variable itself —
158
+ * a laptop or the CLI reads its environment as before.
159
+ */
160
+ setting(env: string): string | undefined;
152
161
  /**
153
162
  * Where the platform's built-in tools sit on disk, materialised at boot —
154
163
  * a shelf every account reads, beneath its own library. A name found in
@@ -250,6 +259,7 @@ const local: PlatformHooks = {
250
259
  workspaceChanged() {},
251
260
  sandboxResumable: () => false,
252
261
  platformKeyAllowed: () => true,
262
+ setting: (env) => process.env[env],
253
263
  galleryDir: () => null,
254
264
  noteOAuthRefresh: async () => undefined,
255
265
  fetchUntrusted: Object.assign((url: string, init?: RequestInit) => fetch(url, init), { [LAPTOP_FETCH]: true }),
@@ -21,6 +21,7 @@
21
21
  import type { RoomLevel } from "./store.ts";
22
22
  import type { PeerInfo } from "./operator.ts";
23
23
  import { isHookRecord, type HookRecord } from "./hooks.ts";
24
+ import { platform } from "./platform.ts";
24
25
  import fs from "node:fs";
25
26
  import os from "node:os";
26
27
  import path from "node:path";
@@ -1730,8 +1731,8 @@ export function k8sMemory(value: string): string {
1730
1731
  export function sizeLimits(size?: "small" | "large" | "heavy"): { cpus: string; memory: string } {
1731
1732
  if (size === "small") {
1732
1733
  return {
1733
- cpus: process.env.FOLDRUN_RUNNER_CPUS_SMALL ?? "1",
1734
- memory: process.env.FOLDRUN_RUNNER_MEMORY_SMALL ?? "1Gi",
1734
+ cpus: platform.setting("FOLDRUN_RUNNER_CPUS_SMALL") ?? "1",
1735
+ memory: platform.setting("FOLDRUN_RUNNER_MEMORY_SMALL") ?? "1Gi",
1735
1736
  };
1736
1737
  }
1737
1738
  if (size === "heavy") {
@@ -1741,13 +1742,13 @@ export function sizeLimits(size?: "small" | "large" | "heavy"): { cpus: string;
1741
1742
  // cpus figure is both the rate its seconds are priced at and, since
1742
1743
  // 26 Oct 2026, the cores the sandbox is capped at.
1743
1744
  return {
1744
- cpus: process.env.FOLDRUN_RUNNER_CPUS_HEAVY ?? "4",
1745
- memory: process.env.FOLDRUN_RUNNER_MEMORY_HEAVY ?? "8Gi",
1745
+ cpus: platform.setting("FOLDRUN_RUNNER_CPUS_HEAVY") ?? "4",
1746
+ memory: platform.setting("FOLDRUN_RUNNER_MEMORY_HEAVY") ?? "8Gi",
1746
1747
  };
1747
1748
  }
1748
1749
  return {
1749
- cpus: process.env.FOLDRUN_RUNNER_CPUS ?? "2",
1750
- memory: process.env.FOLDRUN_RUNNER_MEMORY ?? "2g",
1750
+ cpus: platform.setting("FOLDRUN_RUNNER_CPUS") ?? "2",
1751
+ memory: platform.setting("FOLDRUN_RUNNER_MEMORY") ?? "2g",
1751
1752
  };
1752
1753
  }
1753
1754
 
package/src/store.ts CHANGED
@@ -3674,6 +3674,14 @@ export interface StepRecord {
3674
3674
  } | null;
3675
3675
  }
3676
3676
 
3677
+ /** A run's prices as they stood when it was admitted: the operator
3678
+ * settings' version and the figures themselves, by name. */
3679
+ export interface PriceSnapshot {
3680
+ version: number;
3681
+ at: string;
3682
+ values: Record<string, number>;
3683
+ }
3684
+
3677
3685
  export interface RunRecord {
3678
3686
  id: string;
3679
3687
  flow: string; // flow name, or "adhoc:<agent>" for direct agent runs
@@ -3682,6 +3690,12 @@ export interface RunRecord {
3682
3690
  status: "queued" | "running" | "awaiting-approval" | "completed" | "failed";
3683
3691
  startedAt: string;
3684
3692
  finishedAt: string | null;
3693
+ /** The prices the run was admitted under — the platform's run-start
3694
+ * snapshot of its operator settings (30 Oct 2026). Its settlement and
3695
+ * its `budget:` read these, never a price changed while it ran. Opaque
3696
+ * to core; absent on a laptop, and on runs from before snapshots (those
3697
+ * price at the live figures). */
3698
+ pricing?: PriceSnapshot | null;
3685
3699
  /** Set when a worker parked this run at an approval gate and returned its
3686
3700
  * slot to the queue. The approval API re-enqueues exactly when this is
3687
3701
  * set — without the marker it would also re-enqueue runs whose starting