@foldrun/core 0.2.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 (166) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +239 -0
  3. package/dist/index.d.ts +32 -0
  4. package/dist/index.js +43 -0
  5. package/dist/src/agent-tools.d.ts +31 -0
  6. package/dist/src/agent-tools.js +99 -0
  7. package/dist/src/agents-md.d.ts +9 -0
  8. package/dist/src/agents-md.js +83 -0
  9. package/dist/src/api-tools.d.ts +38 -0
  10. package/dist/src/api-tools.js +313 -0
  11. package/dist/src/approvals.d.ts +39 -0
  12. package/dist/src/approvals.js +103 -0
  13. package/dist/src/arrange.d.ts +8 -0
  14. package/dist/src/arrange.js +40 -0
  15. package/dist/src/catalog.d.ts +108 -0
  16. package/dist/src/catalog.js +255 -0
  17. package/dist/src/completions.d.ts +61 -0
  18. package/dist/src/completions.js +578 -0
  19. package/dist/src/confine.d.ts +31 -0
  20. package/dist/src/confine.js +298 -0
  21. package/dist/src/container.d.ts +30 -0
  22. package/dist/src/container.js +172 -0
  23. package/dist/src/context-tools.d.ts +81 -0
  24. package/dist/src/context-tools.js +347 -0
  25. package/dist/src/deploy.d.ts +100 -0
  26. package/dist/src/deploy.js +377 -0
  27. package/dist/src/egress.d.ts +75 -0
  28. package/dist/src/egress.js +133 -0
  29. package/dist/src/evals.d.ts +150 -0
  30. package/dist/src/evals.js +651 -0
  31. package/dist/src/flow-lint.d.ts +16 -0
  32. package/dist/src/flow-lint.js +177 -0
  33. package/dist/src/git-hook.d.ts +1 -0
  34. package/dist/src/git-hook.js +70 -0
  35. package/dist/src/git.d.ts +37 -0
  36. package/dist/src/git.js +101 -0
  37. package/dist/src/gitrepo.d.ts +95 -0
  38. package/dist/src/gitrepo.js +355 -0
  39. package/dist/src/history.d.ts +82 -0
  40. package/dist/src/history.js +259 -0
  41. package/dist/src/kinds.d.ts +95 -0
  42. package/dist/src/kinds.js +421 -0
  43. package/dist/src/library.d.ts +94 -0
  44. package/dist/src/library.js +341 -0
  45. package/dist/src/linkable.d.ts +20 -0
  46. package/dist/src/linkable.js +113 -0
  47. package/dist/src/model-probe.d.ts +21 -0
  48. package/dist/src/model-probe.js +81 -0
  49. package/dist/src/notify.d.ts +61 -0
  50. package/dist/src/notify.js +252 -0
  51. package/dist/src/oauth-presets.d.ts +13 -0
  52. package/dist/src/oauth-presets.js +39 -0
  53. package/dist/src/okf.d.ts +220 -0
  54. package/dist/src/okf.js +656 -0
  55. package/dist/src/openapi.d.ts +66 -0
  56. package/dist/src/openapi.js +342 -0
  57. package/dist/src/paths.d.ts +33 -0
  58. package/dist/src/paths.js +78 -0
  59. package/dist/src/platform.d.ts +32 -0
  60. package/dist/src/platform.js +48 -0
  61. package/dist/src/providers.d.ts +47 -0
  62. package/dist/src/providers.js +114 -0
  63. package/dist/src/refs.d.ts +20 -0
  64. package/dist/src/refs.js +78 -0
  65. package/dist/src/run-container.d.ts +227 -0
  66. package/dist/src/run-container.js +760 -0
  67. package/dist/src/runner.d.ts +241 -0
  68. package/dist/src/runner.js +3215 -0
  69. package/dist/src/runtime.d.ts +41 -0
  70. package/dist/src/runtime.js +357 -0
  71. package/dist/src/script-tools.d.ts +62 -0
  72. package/dist/src/script-tools.js +296 -0
  73. package/dist/src/secret-files.d.ts +12 -0
  74. package/dist/src/secret-files.js +92 -0
  75. package/dist/src/secrets.d.ts +147 -0
  76. package/dist/src/secrets.js +626 -0
  77. package/dist/src/starter.d.ts +21 -0
  78. package/dist/src/starter.js +399 -0
  79. package/dist/src/step-exec.d.ts +136 -0
  80. package/dist/src/step-exec.js +391 -0
  81. package/dist/src/storage.d.ts +123 -0
  82. package/dist/src/storage.js +640 -0
  83. package/dist/src/store.d.ts +1052 -0
  84. package/dist/src/store.js +2378 -0
  85. package/dist/src/tar.d.ts +30 -0
  86. package/dist/src/tar.js +111 -0
  87. package/dist/src/tool-names.d.ts +39 -0
  88. package/dist/src/tool-names.js +79 -0
  89. package/dist/src/tool-programs.d.ts +18 -0
  90. package/dist/src/tool-programs.js +52 -0
  91. package/dist/src/tool-test.d.ts +19 -0
  92. package/dist/src/tool-test.js +335 -0
  93. package/dist/src/translator.d.ts +159 -0
  94. package/dist/src/translator.js +986 -0
  95. package/dist/src/triggers.d.ts +60 -0
  96. package/dist/src/triggers.js +270 -0
  97. package/dist/src/webhook.d.ts +47 -0
  98. package/dist/src/webhook.js +148 -0
  99. package/index.ts +44 -0
  100. package/package.json +64 -0
  101. package/src/agent-tools.ts +138 -0
  102. package/src/agents-md.ts +81 -0
  103. package/src/api-tools.ts +389 -0
  104. package/src/approvals.ts +129 -0
  105. package/src/arrange.ts +42 -0
  106. package/src/catalog.ts +321 -0
  107. package/src/completions.ts +652 -0
  108. package/src/confine.ts +350 -0
  109. package/src/container.ts +220 -0
  110. package/src/context-tools.ts +435 -0
  111. package/src/deploy.ts +458 -0
  112. package/src/egress.ts +176 -0
  113. package/src/evals.ts +812 -0
  114. package/src/flow-lint.ts +215 -0
  115. package/src/git-hook.ts +73 -0
  116. package/src/git.ts +117 -0
  117. package/src/gitrepo.ts +391 -0
  118. package/src/history.ts +329 -0
  119. package/src/kinds.ts +507 -0
  120. package/src/library.ts +419 -0
  121. package/src/linkable.ts +123 -0
  122. package/src/model-probe.ts +112 -0
  123. package/src/notify.ts +281 -0
  124. package/src/oauth-presets.ts +54 -0
  125. package/src/okf.ts +768 -0
  126. package/src/openapi.ts +383 -0
  127. package/src/paths.ts +79 -0
  128. package/src/platform.ts +87 -0
  129. package/src/providers.ts +147 -0
  130. package/src/refs.ts +83 -0
  131. package/src/run-container.ts +883 -0
  132. package/src/runner.ts +3446 -0
  133. package/src/runtime.ts +404 -0
  134. package/src/script-tools.ts +383 -0
  135. package/src/secret-files.ts +101 -0
  136. package/src/secrets.ts +799 -0
  137. package/src/starter.ts +415 -0
  138. package/src/step-exec.ts +498 -0
  139. package/src/storage.ts +771 -0
  140. package/src/store.ts +2985 -0
  141. package/src/tar.ts +124 -0
  142. package/src/tool-names.ts +91 -0
  143. package/src/tool-programs.ts +65 -0
  144. package/src/tool-test.ts +401 -0
  145. package/src/translator.ts +1029 -0
  146. package/src/triggers.ts +287 -0
  147. package/src/webhook.ts +183 -0
  148. package/templates/blog-desk/AGENTS.md +13 -0
  149. package/templates/blog-desk/agents/researcher/agent.md +11 -0
  150. package/templates/blog-desk/agents/writer/agent.md +8 -0
  151. package/templates/blog-desk/evals/writer-quality.md +10 -0
  152. package/templates/blog-desk/flows/publish.md +10 -0
  153. package/templates/blog-desk/knowledge/house-style.md +9 -0
  154. package/templates/blog-desk/knowledge/index.md +9 -0
  155. package/templates/hello/AGENTS.md +18 -0
  156. package/templates/hello/agents/curator/agent.md +32 -0
  157. package/templates/hello/agents/notetaker/agent.md +24 -0
  158. package/templates/hello/evals/house-style.md +16 -0
  159. package/templates/hello/flows/memory-review.md +7 -0
  160. package/templates/hello/flows/note.md +7 -0
  161. package/templates/hello/knowledge/house-style.md +15 -0
  162. package/templates/hello/knowledge/index.md +9 -0
  163. package/templates/hello/skills/plain-english/SKILL.md +11 -0
  164. package/templates/hello/state/publishing.md +7 -0
  165. package/templates/hello/tools/wordcount/run.py +13 -0
  166. package/templates/hello/tools/wordcount/tool.md +22 -0
@@ -0,0 +1,1052 @@
1
+ import { singleWorkspace } from "./paths.ts";
2
+ /**
3
+ * The frontmatter of one markdown file, as the runtime reads it. Exported
4
+ * for the CLI: `foldrun probe` used to import gray-matter itself, which is
5
+ * this package's dependency, not the CLI's — on an installed CLI the import
6
+ * failed, the catch swallowed it, and the workspace's provider block was
7
+ * silently ignored, so the probe went to Anthropic and reported a model
8
+ * that "may not exist". Reading through here, the CLI cannot drift from
9
+ * the runtime, and there is nothing to fail to import.
10
+ */
11
+ export declare function readFrontmatter(file: string): Record<string, unknown>;
12
+ import { type WireFormat, type AuthShape } from "./providers.ts";
13
+ /**
14
+ * Single-workspace mode. When set, the workspace IS this directory — no
15
+ * tenant, no projects/ nesting. That's the difference between a framework you
16
+ * run on a folder and a platform that hosts many, and it's the only thing the
17
+ * core needs to know about which one it's in.
18
+ */
19
+ export { singleWorkspace };
20
+ export declare const WORKSPACE_DIRS: readonly ["agents", "flows", "evals", "skills", "memory", "knowledge", "tools", "scripts", "state"];
21
+ export declare const STORAGE_DIR = "storage";
22
+ /** What this directory was called before. Read-only: nothing writes here any
23
+ * more, but installs created before the rename have bytes under it, and a
24
+ * workspace whose files vanished on upgrade is a data-loss bug however
25
+ * cosmetic the cause. `adoptLegacyFilesDir` moves one across, once. */
26
+ export declare const LEGACY_FILES_DIR = "files";
27
+ /**
28
+ * Move a pre-rename `files/` directory to `storage/`, once, in place.
29
+ *
30
+ * Called on the paths that resolve the store and the run mirror, so an
31
+ * existing install upgrades the first time either is touched rather than
32
+ * needing a migration step someone has to remember to run. A no-op when
33
+ * `storage/` already exists (the normal case after the first call) or when
34
+ * there is nothing to move (a fresh install).
35
+ */
36
+ export declare function adoptLegacyFilesDir(parent: string): void;
37
+ /**
38
+ * Rewrite a pre-rename `mdagent_version:` in an AGENTS.md to `foldrun_version:`,
39
+ * once, in place.
40
+ *
41
+ * The reader used to accept both spellings, which meant the old name lived on
42
+ * forever in files nobody would ever edit. Migrating on touch retires it: the
43
+ * first read of an account or workspace converts the file, and after that
44
+ * there is one name in the codebase and one name on disk.
45
+ *
46
+ * Only the key is touched — same line, same value, same everything else — so
47
+ * a file with comments or unusual formatting comes through unchanged.
48
+ */
49
+ export declare function adoptLegacyVersionKey(dir: string): void;
50
+ export declare function assertSafeName(name: string, kind: string): void;
51
+ export declare function workspaceDir(tenant: string, workspace: string): string;
52
+ /**
53
+ * The account root — where `library/`, account secrets and the account-wide
54
+ * AGENTS.md live.
55
+ *
56
+ * On a laptop there is no account, so it is the workspace's parent: `my-desk/`
57
+ * sits beside `library/` and an `AGENTS.md` covering everything there. Same
58
+ * rule libraryDir uses, so the two never disagree about where "up" is.
59
+ */
60
+ export declare function accountDir(tenant: string): string;
61
+ /**
62
+ * Create the account's AGENTS.md if it is missing. Returns what it wrote.
63
+ *
64
+ * Called from every path that brings a workspace into being, because there is
65
+ * no separate "create an account" step — the account root accumulates
66
+ * (`library/`, `secrets.json`) as things need it, and its AGENTS.md is one
67
+ * more of those things.
68
+ *
69
+ * Never overwrites, and treats the legacy `project.md` as already-present: the
70
+ * whole file is hand-authored config, and a deploy is not permission to touch
71
+ * a scope the deploy did not ship. `dir` is explicit for `foldrun init`, which
72
+ * writes a workspace to an arbitrary path before anything is pinned to it and
73
+ * so cannot ask accountDir where "up" is.
74
+ */
75
+ export declare function ensureAccountFiles(tenant: string, dir?: string): string[];
76
+ export interface DeployFile {
77
+ path: string;
78
+ content: string;
79
+ }
80
+ /**
81
+ * The format version this build understands.
82
+ *
83
+ * A workspace declares what it targets with `foldrun_version` in AGENTS.md.
84
+ * Without it, the first breaking change to the flow grammar or frontmatter
85
+ * silently changes what everyone's existing files mean — the one failure a
86
+ * format cannot recover from, because there is no error to notice.
87
+ *
88
+ * Missing is treated as "current": nobody's existing workspace breaks by
89
+ * having been written before this existed.
90
+ */
91
+ export declare const FORMAT_VERSION = "0.1";
92
+ export interface VersionCheck {
93
+ declared: string | null;
94
+ supported: string;
95
+ /** Set when the workspace targets something this build cannot promise. */
96
+ warning: string | null;
97
+ }
98
+ export declare function checkFormatVersion(declared: unknown): VersionCheck;
99
+ export declare function assertCanonicalCase(rel: string): void;
100
+ /**
101
+ * What the platform owns inside a workspace directory, in one place.
102
+ *
103
+ * Three separate mechanisms need this exact list and drifted apart while it
104
+ * lived in each of them: deploys must preserve these files, and the run
105
+ * executors (docker and k8s) must not copy them into a sandbox. A platform
106
+ * file added to one list and not the others is either silently wiped by the
107
+ * next deploy or silently handed to every container — both invisible until
108
+ * someone is bitten.
109
+ */
110
+ export declare const PLATFORM_FILES: string[];
111
+ /** True for anything the platform, not the author or the agent, writes. */
112
+ export declare function isPlatformPath(rel: string): boolean;
113
+ export declare function saveWorkspace(tenant: string, workspace: string, files: DeployFile[],
114
+ /** Attribution for the revision this save records. A deploy passes the
115
+ * commit, which becomes the revision's id. */
116
+ meta?: ChangeMeta & {
117
+ commit?: string | null;
118
+ }): {
119
+ preserved: number;
120
+ };
121
+ export declare function buildMemoryIndex(dir: string, prefix?: string): string | null;
122
+ export declare function workspaceMemoryIndex(tenant: string, workspace: string): string | null;
123
+ export declare function knowledgeIndex(dir: string, prefix?: string): string | null;
124
+ export declare function workspaceKnowledgeIndex(tenant: string, workspace: string): string | null;
125
+ export interface McpSpec {
126
+ name: string;
127
+ description: string;
128
+ command?: string;
129
+ args: string[];
130
+ env: Record<string, string>;
131
+ url?: string;
132
+ headers: Record<string, string>;
133
+ }
134
+ export type ToolDef = {
135
+ kind: "http";
136
+ name: string;
137
+ spec: ApiSpec;
138
+ } | {
139
+ kind: "script";
140
+ name: string;
141
+ spec: Record<string, unknown>;
142
+ } | {
143
+ kind: "mcp";
144
+ name: string;
145
+ spec: McpSpec;
146
+ };
147
+ /**
148
+ * The program inside a single-file script tool: the first fenced block whose
149
+ * language tag names a runnable language. By tag, not by position — a body
150
+ * legitimately opens with a \`\`\`yaml example of the tools: line, and running
151
+ * the documentation would be a memorable bug. Untagged fences never qualify:
152
+ * "which block is the program" must be answerable by reading, not guessing.
153
+ */
154
+ export declare function fencedCode(body: string): {
155
+ code: string;
156
+ ext: string;
157
+ } | null;
158
+ /**
159
+ * The same block, with where it sits — for the migration that lifts a
160
+ * single-file tool's program out into a file beside it. Extracting the code
161
+ * and then removing it from the body are the same question asked twice, and
162
+ * a second copy of the language table is how they would come to disagree.
163
+ */
164
+ export declare function fencedCodeBlock(body: string): {
165
+ code: string;
166
+ ext: string;
167
+ start: number;
168
+ end: number;
169
+ } | null;
170
+ export declare function parseToolDef(data: Record<string, unknown>, fallbackName: string, body?: string): ToolDef | null;
171
+ /**
172
+ * Tools in a directory, in both shapes.
173
+ *
174
+ * tools/email.md flat — a definition and nothing else
175
+ * tools/bounce-verify/ folder — tool.md plus the code it runs
176
+ * tool.md
177
+ * run.mjs
178
+ *
179
+ * The folder is the shape to write. A script tool is a definition and an
180
+ * executable that cannot be separated — point `run:` at the wrong scope and
181
+ * the tool silently resolves to nothing — so keeping them in one directory
182
+ * is what stops the two from ever disagreeing. It is also how skills already
183
+ * work, which is the pattern people have seen.
184
+ *
185
+ * A folder tool's `run:` is relative to its own folder, and rewritten here
186
+ * to the scope-qualified path the runner resolves. That is the point of the
187
+ * shape: the definition never names the scope it lives in, so the same
188
+ * folder works copied into a workspace or installed at the account.
189
+ *
190
+ * Flat files keep working unchanged — the shape is a migration, not a break.
191
+ */
192
+ declare function readToolDir(dir: string, scope?: "workspace" | "account"): Record<string, ToolDef>;
193
+ /** Definitions that exist on disk but could not be read, for the scopes an
194
+ * agent draws on. Empty when everything parsed. */
195
+ export declare function brokenToolReport(tenant: string, workspace: string): string[];
196
+ export declare function workspaceTools(tenant: string, workspace: string): Record<string, ToolDef>;
197
+ export { readToolDir };
198
+ export declare function agentScripts(tenant: string, workspace: string, agent: string): string[];
199
+ export interface ApiSpec {
200
+ name: string;
201
+ base: string;
202
+ description: string;
203
+ headers: Record<string, string>;
204
+ query: Record<string, string>;
205
+ methods: string[];
206
+ /** Seconds a call may take, when the tool.md says so. Unset: no clock. */
207
+ timeout?: number;
208
+ /** An OpenAPI 3.x document describing the API: a path relative to the
209
+ * workspace root (`tools/hubspot/openapi.json`, JSON or YAML) or an
210
+ * https URL. Each operation becomes its own typed tool. */
211
+ openapi?: string;
212
+ /** Which of the document's operations to expose — operationIds or
213
+ * `"METHOD /path"` strings. Absent: every operation, capped at 60.
214
+ * Present: only these, and the generic `call_<api>` tool is withheld. */
215
+ operations?: string[];
216
+ /** Calls per second this API may see from one step: `count` is the burst
217
+ * the bucket holds, `perSec` the refill rate. `"5/s"` → {5, 5};
218
+ * `"100/m"` → {100, 1.67}. A call over the limit waits, never fails. */
219
+ rate?: {
220
+ count: number;
221
+ perSec: number;
222
+ };
223
+ /** The operations the `openapi:` document yielded, filled in by
224
+ * attachOperations (openapi.ts) before the step is assembled. parseApis
225
+ * leaves it unset. Plain JSON: this crosses into the run container. */
226
+ resolvedOperations?: import("./openapi.ts").OperationSpec[];
227
+ }
228
+ export interface AgentInfo {
229
+ name: string;
230
+ description: string;
231
+ model: string;
232
+ /** The other half of model selection. listAgents has always returned it;
233
+ * the interface did not say so, so every consumer had to re-read the file
234
+ * to show it. */
235
+ effort: string | null;
236
+ tools: string[];
237
+ apis: ApiSpec[];
238
+ /** The author's own tools named in `tools:` — the subset that is not a
239
+ * built-in. Same list as `tools`, minus what the runtime provides. */
240
+ ownTools: string[];
241
+ /** Names still written under the removed `use:` key. Nothing is granted
242
+ * for them; `foldrun check` and the run log say what to write instead. */
243
+ legacyUse: string[];
244
+ /** Colleagues this agent may consult mid-run (`agents:`) — each becomes a
245
+ * consult_<name> tool. The team's edges, as the author drew them. */
246
+ consults: string[];
247
+ /** The `skills:` allowlist, or null when the field is absent — which is
248
+ * not the same thing: absent inherits every skill in scope, an empty
249
+ * list withholds all of them. */
250
+ skills: string[] | null;
251
+ secrets: string[];
252
+ }
253
+ export declare const MODEL_TIERS: Record<string, string>;
254
+ export type Tier = keyof typeof MODEL_TIERS;
255
+ /** Which tier a word names, or null if it names none. Exported because the
256
+ * provider block keys a remap table by tier and should accept every word
257
+ * `model:` does — one vocabulary, not two. */
258
+ export declare function resolveTier(value: unknown): Tier | null;
259
+ export declare function resolveModel(value: unknown): string;
260
+ export declare const EFFORT_LEVELS: readonly ["low", "medium", "high", "xhigh", "max"];
261
+ export type Effort = (typeof EFFORT_LEVELS)[number];
262
+ /**
263
+ * `null` for both "not set" and "not a level we know" — unlike a model, the
264
+ * set is closed, so passing an unrecognised word through would be a 400 from
265
+ * the API rather than someone's pinned choice. Callers that can report say
266
+ * which it was with `isEffortWord`.
267
+ */
268
+ export declare function resolveEffort(value: unknown): Effort | null;
269
+ /** True when a non-empty `effort:` was written but names no level — the
270
+ * one case worth a line in the run trace, because silence there looks
271
+ * exactly like the setting having been applied. */
272
+ export declare function isEffortWord(value: unknown): boolean;
273
+ /** `"5/s"`, `"100/m"`, `"1000/h"` → a bucket size and refill rate. Null for
274
+ * anything else: an unreadable limit is ignored rather than guessed. */
275
+ export declare function parseRate(value: unknown): {
276
+ count: number;
277
+ perSec: number;
278
+ } | null;
279
+ export declare function parseApis(raw: unknown): ApiSpec[];
280
+ export interface FlowStep {
281
+ agent: string;
282
+ instruction: string;
283
+ group: number;
284
+ optional: boolean;
285
+ /** Set when the step targets another flow (`[[flow:name]]`) instead of an agent. */
286
+ subflow?: string;
287
+ /** Overrides the agent's own model for this step only. */
288
+ model?: string;
289
+ /** Overrides the agent's own effort for this step only — the expensive
290
+ * step in an otherwise cheap flow is exactly what this is for. */
291
+ effort?: string;
292
+ /** Pause for a human before running this step. */
293
+ approve?: boolean;
294
+ /** Run only if the previous results contain this text (case-insensitive).
295
+ * Independent: every matching `when:` step in a group runs. */
296
+ when?: string;
297
+ /** Exclusive routing: of a group's `case:` steps, only the FIRST whose
298
+ * text appears in the previous results runs; the rest are routed past. */
299
+ case?: string;
300
+ /** The route of last resort: runs only when none of its group's `case:`
301
+ * steps matched. */
302
+ else?: boolean;
303
+ /** Attempts on failure, beyond the first. */
304
+ retry?: number;
305
+ /** Abandon the step after this many seconds. */
306
+ timeout?: number;
307
+ /** Another agent takes the step over when it fails: same instruction, the
308
+ * failure as context. Success continues the flow with the rescuer's
309
+ * result; a second failure fails the step as it always did. */
310
+ onFail?: string;
311
+ /** Seconds to hold before this step runs — "wait: 3d" parses to 259200.
312
+ * The run parks in the queue, not in a process. */
313
+ waitSecs?: number;
314
+ /** `wait: event` — hold until something outside POSTs the run's event
315
+ * URL. Same parking as an approval, released by a machine instead of a
316
+ * person; the body it sends reaches the step as its event payload. This
317
+ * is what lets a flow send a quote, wait for the reply, then follow up,
318
+ * without the platform becoming a chat product. */
319
+ waitFor?: "event";
320
+ /** For `each: rows` — the CSV whose rows fan the step out, agent-relative
321
+ * like every other path an instruction uses. */
322
+ eachPath?: string;
323
+ /** A question for a human. The step parks like an approval; the answer
324
+ * typed at the gate reaches the step's prompt. */
325
+ ask?: string;
326
+ /** What the gate shows before a person decides: paths under storage/,
327
+ * globs allowed (`draft/*.mdx, draft/images/*.webp`). Declared in the
328
+ * flow so the preview does not depend on the previous step remembering
329
+ * to name its files. Resolved against storage/ when the run parks. */
330
+ preview?: string[];
331
+ /** Model-led delegation, bounded by declaration: this step's agent picks
332
+ * which of THESE agents run next, one instruction each. The choice set
333
+ * is the file's, the picks are the model's, the record shows both. */
334
+ delegate?: string[];
335
+ /** Evaluator loop: when this step's result lacks `until:`'s marker, wind
336
+ * back to the previous group and go again — at most this many extra
337
+ * cycles. Bounded by declaration, so the worst-case cost is still
338
+ * readable off the file. */
339
+ loop?: number;
340
+ /** The marker (case-insensitive) whose presence in this step's result ends
341
+ * the loop early. Required for `loop:` to mean anything. */
342
+ until?: string;
343
+ /** Fan-out: run one instance of this step per item of the previous
344
+ * group's result. `lines` — one item per non-empty line of the prose;
345
+ * `rows` — one per CSV row of a file; `items` — one per element of the
346
+ * JSON array a previous `output: json` step returned. */
347
+ each?: "lines" | "rows" | "items";
348
+ /** Fan-out cap. Items beyond it are dropped, and the run log says so. */
349
+ max?: number;
350
+ /** A check run after the step; failing it fails the step. Either a shell
351
+ * command (non-zero exit fails) or one of the eval assertions —
352
+ * `contains: x`, `not-contains: x`, `matches: regex`, `file: path`,
353
+ * `judge: sentence` — so a flow and an eval share one vocabulary. */
354
+ verify?: string;
355
+ /** The step returns data, not just prose: its reply must end with one JSON
356
+ * value in a ```json block. The runner parses it, fails the step when it
357
+ * cannot, and hands the value to the next group losslessly — the narrow
358
+ * move the grammar ADR allowed instead of variables. */
359
+ output?: "json";
360
+ /** 1-indexed line in the flow file. Diagnostics without a line make you
361
+ * search; every real linter emits file:line. */
362
+ line?: number;
363
+ }
364
+ export interface FlowInfo {
365
+ name: string;
366
+ file: string;
367
+ trigger: string;
368
+ schedule: string | null;
369
+ timezone: string | null;
370
+ /** The model every step in this flow runs on, unless the step names its
371
+ * own. Nearest wins: step, then flow, then the agent's own frontmatter. */
372
+ model: string | null;
373
+ /** Same, for effort. */
374
+ effort: string | null;
375
+ /** What a new fire does while a run of this flow is still live:
376
+ * "skip" doesn't start one, "queue" starts it but the worker holds it
377
+ * until the live run finishes. Unset keeps today's behaviour (runs may
378
+ * overlap) — a default that changed underfoot would surprise every flow
379
+ * that legitimately runs in parallel. */
380
+ overlap: "skip" | "queue" | null;
381
+ /** The most one run of this flow may spend, in USD. A literal, never an
382
+ * expression — the cap is readable off the file, which is the property
383
+ * the whole grammar exists to keep. Checked between groups: the group
384
+ * that crosses it is the last one that runs. Null: no cap of its own
385
+ * (the workspace's monthly `budget:` still applies). */
386
+ budget: number | null;
387
+ /** `trigger: flow` — start when this other flow of the workspace finishes,
388
+ * with `on:` saying which endings count. The finished run's summary and
389
+ * result become this run's task. */
390
+ after: string | null;
391
+ on: "completed" | "failed" | "any";
392
+ /** `trigger: once` — an ISO 8601 instant; fires the first tick at or after
393
+ * it, once, then never again. */
394
+ at: string | null;
395
+ /** `trigger: watch` — a URL polled every `every:` seconds (default 15m);
396
+ * fires when its content changes, with the new content as the task. */
397
+ url: string | null;
398
+ every: number | null;
399
+ /** `trigger: webhook` may also demand a provider's signature on every
400
+ * delivery: GitHub (X-Hub-Signature-256), Stripe (Stripe-Signature), Slack
401
+ * (X-Slack-Signature, plus the url_verification handshake) or a plain
402
+ * `hmac` (X-Signature, hex SHA-256 of the body). `signingSecret` is the
403
+ * NAME of the vault secret holding the shared key, never its value. */
404
+ signature: "github" | "stripe" | "slack" | "hmac" | null;
405
+ signingSecret: string | null;
406
+ /** `trigger: storage` — a path prefix under storage/; a file landing
407
+ * there starts the flow with the path as its task. */
408
+ path: string | null;
409
+ steps: FlowStep[];
410
+ }
411
+ export declare const FLOW_TRIGGERS: readonly ["manual", "schedule", "webhook", "email", "flow", "once", "watch", "storage"];
412
+ export type FlowTrigger = (typeof FLOW_TRIGGERS)[number];
413
+ export interface AgentAssets {
414
+ skills: string[];
415
+ memory: string[];
416
+ }
417
+ export declare function agentAssets(tenant: string, workspace: string, agent: string): AgentAssets;
418
+ export declare function listAgents(tenant: string, workspace: string): AgentInfo[];
419
+ /** "90s", "30m", "4h", "3d" — or a bare number of seconds. Clamped to 30
420
+ * days: a wait is a pause in a flow, not a second scheduler. */
421
+ export declare function parseWait(value: string): number | undefined;
422
+ /** An ISO 8601 instant (YAML may already have parsed it into a Date), or
423
+ * null for anything that is not one — a `once` flow with an unreadable
424
+ * `at:` never fires, and `foldrun check` says so. */
425
+ export declare function parseInstant(raw: unknown): string | null;
426
+ /**
427
+ * Strip the quotes a person wrapped a whole value in, and only those.
428
+ *
429
+ * `schedule: "0 8 * * *"` is quoted so YAML-minded readers are comfortable,
430
+ * and the cron parser must not see the quotes. But the rule that did this
431
+ * removed a leading OR a trailing quote independently, so a value that
432
+ * merely *ended* with one lost it — and
433
+ *
434
+ * verify: test "$(cat ../../storage/draft/published.run)" = "$FOLDRUN_RUN_ID"
435
+ *
436
+ * reached bash unbalanced, which exited 2 with "unexpected EOF" on every
437
+ * run. The step could never pass, whatever the agent did, and nothing said
438
+ * why: the trace printed the command the parser had already broken.
439
+ *
440
+ * So a pair is only stripped when the value opens and closes with the same
441
+ * quote AND contains no other instance of it — which is what "the whole
442
+ * value is quoted" actually means. `"a" = "b"` keeps its quotes, because
443
+ * they are two pairs, not one wrapper.
444
+ */
445
+ export declare function unquote(raw: string): string;
446
+ export declare function parseFlow(file: string, raw: string): FlowInfo;
447
+ /** A live run of this flow — the fact overlap: decisions are made on. */
448
+ export declare function flowHasLiveRun(tenant: string, workspace: string, flowName: string): boolean;
449
+ /**
450
+ * Rewrite a flow's trigger in place — the dashboard's trigger picker.
451
+ *
452
+ * Line-level surgery on the frontmatter, not a YAML round-trip: everything
453
+ * the author wrote (comments, key order, unrelated keys) survives verbatim,
454
+ * and the diff is exactly the lines that changed. A flow with no
455
+ * frontmatter gains the smallest one that can carry the trigger.
456
+ */
457
+ export declare function setFlowTrigger(raw: string, opts: {
458
+ trigger: FlowTrigger;
459
+ schedule?: string;
460
+ timezone?: string;
461
+ }): string;
462
+ /**
463
+ * Rewrite one step's option lines — the board's step editor. Surgical, like
464
+ * every flow rewrite: only the managed option lines of block `index` change;
465
+ * the step line, its unmanaged options (approve stays the `!` marker's
466
+ * business), other steps, comments and frontmatter survive verbatim.
467
+ * `null` clears an option; values are clamped exactly as parseFlow reads
468
+ * them, so what the editor writes is what the runner will do.
469
+ */
470
+ /**
471
+ * Rewrite one step's instruction, and nothing else.
472
+ *
473
+ * The step line carries four things — the group number, the optional/approve
474
+ * marker, the wikilink, and the instruction — and only the last is being
475
+ * edited. Rebuilding the line from the parsed step would silently normalise
476
+ * the other three (a "2?" becoming "2.", an en dash becoming an em dash), so
477
+ * the prefix is taken verbatim from the file and only the tail replaced. A
478
+ * flow's markdown stays the author's.
479
+ */
480
+ export declare function updateFlowStepInstruction(raw: string, index: number, instruction: string): string;
481
+ export declare function updateFlowStep(raw: string, index: number, options: Partial<Record<"model" | "effort" | "retry" | "timeout" | "verify" | "when" | "case" | "else" | "loop" | "until" | "each" | "max", string | number | null>>): string;
482
+ export declare function reorderFlowSteps(raw: string, groups: number[][]): string;
483
+ /** Append a step as its own group at the end of the flow. */
484
+ export declare function addFlowStep(raw: string, step: {
485
+ target: string;
486
+ subflow?: boolean;
487
+ instruction?: string;
488
+ }): string;
489
+ /** Drop one step, by its index in the flow's parsed steps. */
490
+ export declare function removeFlowStep(raw: string, index: number): string;
491
+ export declare function listFlows(tenant: string, workspace: string): FlowInfo[];
492
+ export interface WorkspaceSummary {
493
+ name: string;
494
+ description: string;
495
+ agents: number;
496
+ flows: number;
497
+ deployedAt: string;
498
+ runCount: number;
499
+ /** Set when this workspace is a branch's preview of another (preview.ts):
500
+ * which one, which branch, which commit. Read from the marker beside the
501
+ * directory here rather than through preview.ts, which imports this file. */
502
+ preview?: {
503
+ source: string;
504
+ branch: string;
505
+ commit: string;
506
+ at: string;
507
+ };
508
+ /** The workspace's picture, as a URL, when the page that lists it looked
509
+ * one up (avatars.ts) — the store itself does not. */
510
+ avatar?: string | null;
511
+ }
512
+ /**
513
+ * Every account this installation holds.
514
+ *
515
+ * A tenant is a directory under the data root, but not every directory there
516
+ * is one — `.runtimes/` sits beside them, and so do keys.json and the secret
517
+ * key. What makes a tenant is that it holds workspaces, so that is the test.
518
+ *
519
+ * Single-workspace mode has no accounts at all: the workspace *is* the whole
520
+ * installation, and there is nothing above it to enumerate.
521
+ */
522
+ export declare function listTenants(): string[];
523
+ export declare function listWorkspaces(tenant: string): WorkspaceSummary[];
524
+ /**
525
+ * May this path be edited — and therefore, must it be listed?
526
+ *
527
+ * The listing and the writer used to answer this separately, and drifted
528
+ * twice. First `state/notes.md` could be written and never appeared; that was
529
+ * fixed by adding a state rule to the listing. Then folder tools arrived and
530
+ * `tools/x/run.mjs` — the program half of every script tool — was writable,
531
+ * readable, and invisible in the file tree, so the code you were told to edit
532
+ * could not be found.
533
+ *
534
+ * Writable and invisible is the worst of both. One rule decides now: the
535
+ * listing asks the writer, so a path that can be saved is a path that shows.
536
+ */
537
+ export declare function isEditablePath(rel: string): boolean;
538
+ export declare function accountFileSealed(rel: string): string | null;
539
+ /**
540
+ * The account directory as it actually is on disk.
541
+ *
542
+ * A faithful walk, with one rule for what it leaves out: show what a person
543
+ * authors or an agent reads. That keeps the path you see the path an agent
544
+ * types, which is the whole point of the tree — while sparing you rows that
545
+ * exist only to be refused.
546
+ *
547
+ * The vault is not an exception to that rule. Secrets are authored in Settings
548
+ * and reach an agent as environment variables — never as a file, deliberately,
549
+ * so that no single read can carry them all off. `secrets.json` is therefore
550
+ * neither authored here nor read by an agent, and a row that can only refuse
551
+ * the person who clicks it teaches nothing. The reader still guards it.
552
+ */
553
+ export declare function listAccountFiles(tenant: string, cap?: number): string[];
554
+ /** One file under the account directory, or the reason it will not be served. */
555
+ export declare function readAccountFile(tenant: string, rel: string): {
556
+ content: string;
557
+ } | {
558
+ sealed: string;
559
+ };
560
+ export declare function listWorkspaceFiles(tenant: string, workspace: string): string[];
561
+ export declare function readWorkspaceFile(tenant: string, workspace: string, rel: string): string;
562
+ /** Who made a change and why — recorded on its revision. */
563
+ export interface ChangeMeta {
564
+ by?: string;
565
+ message?: string;
566
+ }
567
+ export declare function writeWorkspaceFile(tenant: string, workspace: string, rel: string, content: string, meta?: ChangeMeta): void;
568
+ export declare function syncBundleFor(file: string, change?: "Creation" | "Update"): void;
569
+ /**
570
+ * Rename one editable file. Both ends pass the same gate as a write — a
571
+ * rename must not be able to move a file somewhere a write couldn't create
572
+ * it — and an existing target refuses rather than silently replacing
573
+ * someone's work. Bundle indexes on both sides are re-synced, because a
574
+ * memory file leaving a bundle is as much a change to its index as one
575
+ * arriving.
576
+ */
577
+ /**
578
+ * Why a path can never be moved or renamed, in a sentence — or null when it
579
+ * can. One vocabulary for the tree, the API and the tests, because "not an
580
+ * editable path" told the person *that* they were refused and made them
581
+ * guess at *why*, when the why is the whole lesson: in this format,
582
+ * location is meaning.
583
+ */
584
+ export declare function anchoredReason(rel: string): string | null;
585
+ export declare function renameWorkspaceFile(tenant: string, workspace: string, from: string, to: string, meta?: ChangeMeta): void;
586
+ export declare function deleteWorkspacePath(tenant: string, workspace: string, rel: string, meta?: ChangeMeta): void;
587
+ export declare function deleteWorkspace(tenant: string, workspace: string): void;
588
+ /**
589
+ * What a rename would break, so the caller can say so before doing it.
590
+ *
591
+ * The workspace name is not just a directory: it is in every webhook URL *and*
592
+ * in the token those URLs carry, which is derived from `tenant/workspace/flow`.
593
+ * So a rename invalidates both halves — an old hook doesn't 404, it fails the
594
+ * signature check, which reads like an attack rather than a rename. Anything
595
+ * calling those URLs has to be re-pointed by hand.
596
+ */
597
+ export declare function renameImpact(tenant: string, workspace: string): {
598
+ webhookFlows: string[];
599
+ };
600
+ /**
601
+ * Rename a workspace: move the directory, then update the `name:` in AGENTS.md
602
+ * so the file and its folder don't disagree.
603
+ *
604
+ * Runs, state and secrets travel with it — they live inside the directory, so
605
+ * there is nothing to migrate and nothing to lose.
606
+ */
607
+ export declare function renameWorkspace(tenant: string, from: string, to: string): void;
608
+ /** Set a workspace's description, in AGENTS.md, without touching anything else. */
609
+ export declare function setWorkspaceDescription(tenant: string, workspace: string, description: string): void;
610
+ /**
611
+ * Starter files for "+ New workspace" in the dashboard.
612
+ *
613
+ * Kept as a name because the API route and its callers use it; the content
614
+ * lives in starter.ts so this and `foldrun init` cannot drift. They did:
615
+ * this copy still said `type: Agent` after the CLI's had moved to `kind:`,
616
+ * so every workspace made from the dashboard was born in the old format.
617
+ */
618
+ export declare function templateFiles(workspace: string): DeployFile[];
619
+ export declare const AGENT_TEMPLATE: (name: string) => string;
620
+ export declare const FLOW_TEMPLATE: (name: string, firstAgent: string) => string;
621
+ export declare const FLOW_PATTERNS: readonly ["pipeline", "review-loop", "fan-out", "debate", "router"];
622
+ export type FlowPattern = (typeof FLOW_PATTERNS)[number];
623
+ export declare function flowPatternTemplate(pattern: FlowPattern, name: string, agents: string[]): string;
624
+ export interface RunEvent {
625
+ t: string;
626
+ type: "text" | "tool" | "error" | "info";
627
+ text: string;
628
+ /** Tool events come in pairs: the call, and its completion. Both carry
629
+ * the provider's tool_use id so a reader can pair them; the completion
630
+ * also carries how long the tool ran and whether it errored. The
631
+ * timeline draws a span from the pair; the trace shows one line. Absent
632
+ * on records from before this existed — then a tool is a tick, not a bar. */
633
+ call?: string;
634
+ ms?: number;
635
+ err?: boolean;
636
+ }
637
+ export interface StepRecord {
638
+ agent: string;
639
+ instruction: string;
640
+ group: number;
641
+ optional: boolean;
642
+ /** Carried from the flow — see FlowStep. Both are resolved at run
643
+ * time rather than stored resolved, so a record stays readable as
644
+ * what its author wrote. */
645
+ model?: string;
646
+ effort?: string;
647
+ approve?: boolean;
648
+ when?: string;
649
+ case?: string;
650
+ else?: boolean;
651
+ retry?: number;
652
+ timeout?: number;
653
+ verify?: string;
654
+ /** Attempts made so far, for the run trace. */
655
+ attempts?: number;
656
+ /** When the step actually started and finished executing — not when the
657
+ * run reached it. A step's events approximate this (first and last), but
658
+ * a model can think for minutes before its first tool call, and a step
659
+ * parked at a gate has events long before it runs. Kept across retries:
660
+ * startedAt is the first attempt's, finishedAt the last's. Absent on
661
+ * records from before this existed; readers fall back to the events. */
662
+ startedAt?: string;
663
+ finishedAt?: string;
664
+ /** Why a step was skipped, shown in the trace. */
665
+ skipReason?: string;
666
+ /** Evaluator loop, carried from the flow — see FlowStep. `loopRemaining`
667
+ * counts down on the record so a resumed run knows how many cycles are
668
+ * left rather than starting the budget over. */
669
+ loop?: number;
670
+ until?: string;
671
+ loopRemaining?: number;
672
+ /** Fan-out, carried from the flow — see FlowStep. */
673
+ each?: "lines" | "rows" | "items";
674
+ eachPath?: string;
675
+ max?: number;
676
+ /** Carried from the flow — see FlowStep. */
677
+ output?: "json";
678
+ /** The parsed JSON an `output: json` step returned. Kept beside the prose
679
+ * result rather than instead of it: the reply is still what a person
680
+ * reads on the run page, and the data is what the next step computes on. */
681
+ data?: unknown;
682
+ /** Set on the instances a fan-out step expanded into: the item this one
683
+ * works on. The template step itself becomes status "expanded". */
684
+ item?: string;
685
+ /** Carried from the flow — see FlowStep for each. */
686
+ onFail?: string;
687
+ waitSecs?: number;
688
+ /** Stamped when the run reaches the step; the queue holds it until then.
689
+ * On the record so a restart resumes the same deadline, not a new one. */
690
+ waitUntil?: string;
691
+ /** Carried from the flow — see FlowStep. */
692
+ waitFor?: "event";
693
+ /** What the outside world POSTed to release a `wait: event` step. Rides
694
+ * into the step's prompt the way an approval note does. */
695
+ eventPayload?: string;
696
+ ask?: string;
697
+ /** Carried from the flow — see FlowStep. */
698
+ preview?: string[];
699
+ /** `preview:` resolved against storage/ at the moment the run parked —
700
+ * the files the gate renders, relative to storage/. */
701
+ previewFiles?: string[];
702
+ delegate?: string[];
703
+ /** Set once a delegate step's picks have been expanded — the guard that
704
+ * a loop rewind cannot fan the same choice out twice. */
705
+ delegated?: boolean;
706
+ /**
707
+ * When a person approved this step. Set by the approval API and never
708
+ * cleared, because approval is a fact about the past.
709
+ *
710
+ * Status alone cannot carry it: approving flips the step back to `pending`,
711
+ * which is indistinguishable from never having been asked. A run that was
712
+ * approved and then abandoned could not be resumed without asking the same
713
+ * person the same question again.
714
+ */
715
+ approvedAt?: string;
716
+ /** What the approver said while approving — carried into the step's prompt
717
+ * as operator guidance. This turns the gate from a yes/no button into a
718
+ * place to steer: "approve, but skip the Sydney batch". */
719
+ approvalNote?: string;
720
+ status: "pending" | "awaiting-approval" | "running" | "completed" | "failed" | "skipped";
721
+ events: RunEvent[];
722
+ result: string | null;
723
+ /** The model's final text block — its answer, without the narration that
724
+ * preceded the tool calls. Absent on steps recorded before this existed,
725
+ * which is why every reader falls back to `result`. */
726
+ conclusion?: string | null;
727
+ costUsd: number | null;
728
+ /** Set on steps copied into a re-run from an earlier run: the run they
729
+ * actually executed in. Carried steps provide context and are never
730
+ * billed again — the original run's ledger line already paid for them. */
731
+ carriedFrom?: string;
732
+ /**
733
+ * Sandbox seconds this step rented, and how many of them were cold start.
734
+ * Set only on the isolated path — an in-process step rents nothing, so
735
+ * `null` is the honest value and billing must read it as zero, not as
736
+ * "unknown, charge something".
737
+ *
738
+ * Wall time is deliberately not a substitute: a step parked at an approval
739
+ * gate has no pod, and billing a customer for a person taking the weekend
740
+ * to click approve is charging them for their own deliberation.
741
+ */
742
+ computeSecs?: number | null;
743
+ startupSecs?: number | null;
744
+ /** Which reservation class the sandbox held (small | large), and the
745
+ * actual limits it held, as numbers. Facts of the step: a price is per
746
+ * core-second, so what was reserved must come from the record, never
747
+ * from what the config says when the bill is computed. Absent on steps
748
+ * from before sizes existed — billing falls back to the flat rate. */
749
+ size?: "small" | "large" | "heavy";
750
+ reserved?: {
751
+ cpus: number;
752
+ memGiB: number;
753
+ } | null;
754
+ /** Token counts behind costUsd, kept so usage reports can say "how many"
755
+ * and not only "how much" — the price of a token changes, the count is
756
+ * the fact. Absent on steps recorded before this existed. */
757
+ tokens?: {
758
+ input: number;
759
+ output: number;
760
+ } | null;
761
+ /** What the sandbox actually touched, against what it reserved: CPU
762
+ * busy-seconds and peak memory from the pod's own cgroup, bytes on the
763
+ * wire from /proc/net/dev. Null per-field where the kernel interface
764
+ * wasn't readable in the sandbox — never estimated. */
765
+ actual?: {
766
+ busyCpuSecs: number | null;
767
+ peakMemBytes: number | null;
768
+ rxBytes: number | null;
769
+ txBytes: number | null;
770
+ } | null;
771
+ }
772
+ export interface RunRecord {
773
+ id: string;
774
+ flow: string;
775
+ /** Tags supplied when the run started — skills can require one. */
776
+ tags?: string[];
777
+ status: "queued" | "running" | "awaiting-approval" | "completed" | "failed";
778
+ startedAt: string;
779
+ finishedAt: string | null;
780
+ /** Set when a worker parked this run at an approval gate and returned its
781
+ * slot to the queue. The approval API re-enqueues exactly when this is
782
+ * set — without the marker it would also re-enqueue runs whose starting
783
+ * process is still alive and polling, and two drivers would race. */
784
+ parkedAt?: string | null;
785
+ /** Set when a person stopped this run. Kept on the record rather than
786
+ * expressed only as a status, because "failed" and "someone stopped it"
787
+ * are different facts and the trace should not conflate them. */
788
+ stopRequested?: boolean;
789
+ /**
790
+ * What this run concluded, in one line — the first line of the last step
791
+ * that produced a result, captured when the run finishes.
792
+ *
793
+ * A run has always recorded its status and its cost but never its outcome,
794
+ * so "what did this actually do" could only be answered by opening it and
795
+ * reading. That does not scale past a couple of flows, and the sentence was
796
+ * already sitting in the record: agents are asked to lead with it.
797
+ *
798
+ * Deliberately NOT a schema the author has to fill in. Nothing new goes in
799
+ * the folder, nothing new is learned, and an agent that writes no headline
800
+ * simply gets its first line — which is still better than a status.
801
+ */
802
+ summary?: string | null;
803
+ /** The flow's per-run `budget:` at the time this run started, so the cap
804
+ * a run was held to is on its record rather than read from a file that
805
+ * may since have changed. Null: none. */
806
+ budgetUsd?: number | null;
807
+ /** Memory files this run created or changed, workspace-relative — what
808
+ * the agents decided was worth remembering, listed so a person can review
809
+ * the lesson without diffing the folder. */
810
+ memoryWrites?: string[];
811
+ steps: StepRecord[];
812
+ }
813
+ export declare function runFilePath(tenant: string, workspace: string, runId: string): string;
814
+ /**
815
+ * Erase a run from history: its record and the outputs archived under its
816
+ * id.
817
+ *
818
+ * The ledger line stays, and that is settled rather than provisional: if
819
+ * deleting a run voided its charge, deletion would be a refund button — run
820
+ * the expensive extraction, read the result, delete, never pay. Money is
821
+ * not deleted because a trace was.
822
+ *
823
+ * What deletion *does* owe the books is an explanation. A charge pointing
824
+ * at a run that no longer exists reads as unexplained forever, so the
825
+ * deletion writes itself down: a zero-value adjustment naming the run, on
826
+ * the same append-only file the charge lives in. The balance is untouched;
827
+ * the story is complete.
828
+ */
829
+ export declare function deleteRun(tenant: string, workspace: string, runId: string): boolean;
830
+ /** ledger.ts registers here — store cannot import it without a cycle, and
831
+ * money must not depend on one. */
832
+ declare let onRunDeleted: ((tenant: string, workspace: string, runId: string) => void) | undefined;
833
+ export declare function registerRunDeletionListener(fn: typeof onRunDeleted): void;
834
+ export interface RunOutputFile {
835
+ agent: string;
836
+ /** Relative to that agent's outputs/ — the name the agent wrote. */
837
+ path: string;
838
+ bytes: number;
839
+ }
840
+ /**
841
+ * What a run archived: every file its agents left in outputs/, copied under
842
+ * runs/<id>/outputs/<agent>/ when the run finished (runner.ts, archive()).
843
+ *
844
+ * Per agent, not per step — the archive is a copy of each agent's folder,
845
+ * and the record cannot say which step wrote which file. An agent that
846
+ * steps twice in one run shows the same files against both steps.
847
+ */
848
+ export declare function listRunOutputs(tenant: string, workspace: string, runId: string): RunOutputFile[];
849
+ /** The absolute path of one archived output, or null when the name escapes
850
+ * the archive or names nothing — the API serves only what this returns. */
851
+ export declare function runOutputPath(tenant: string, workspace: string, runId: string, agent: string, rel: string): string | null;
852
+ export declare function readRun(tenant: string, workspace: string, runId: string): RunRecord | null;
853
+ export declare function writeRun(tenant: string, workspace: string, run: RunRecord): void;
854
+ export interface RunWithWorkspace extends RunRecord {
855
+ workspace: string;
856
+ }
857
+ export declare function listAllRuns(tenant: string): RunWithWorkspace[];
858
+ /**
859
+ * The status a person should see. "failed" and "someone stopped it" are
860
+ * different facts — the record keeps them apart (stopRequested), and the
861
+ * display must too: a run a person chose to end is not a run that broke,
862
+ * and showing it red teaches people that stopping things is dangerous.
863
+ */
864
+ export declare function runDisplayStatus(run: Pick<RunRecord, "status" | "stopRequested">): string;
865
+ export declare function runCost(run: RunRecord): number;
866
+ /**
867
+ * What a finished run consumed, in the units the format itself has — the
868
+ * shape ledger.ts prices. Structural, not an import, because ledger.ts
869
+ * imports this module and money must not depend on a cycle.
870
+ *
871
+ * Only steps that actually ran count. A pending step never happened, a
872
+ * skipped one was skipped by the flow's own `when`, and an `expanded`
873
+ * fan-out template is a row in the trace rather than work — billing any of
874
+ * them would charge for steps the customer can see never executed, which is
875
+ * the fastest way to lose an argument about an invoice.
876
+ */
877
+ export declare function runMeter(run: RunRecord): {
878
+ tokenCostUsd: number;
879
+ steps: number;
880
+ computeSecs: number;
881
+ /** The subset of computeSecs held at the small reservation. Steps from
882
+ * before sizes existed count as large — they held the large limits. */
883
+ smallSecs: number;
884
+ netBytes: number;
885
+ };
886
+ /**
887
+ * The compute meter for hold+work pricing: core-seconds and GiB-seconds
888
+ * actually reserved (from each step's own recorded reservation), and CPU
889
+ * seconds actually burned (from the sandbox's cgroup, where readable).
890
+ * Steps with no recorded reservation contribute plain seconds only — the
891
+ * flat-rate fallback prices those.
892
+ */
893
+ export declare function runComputeMeter(run: RunRecord): {
894
+ coreSecs: number;
895
+ gibSecs: number;
896
+ busyCpuSecs: number;
897
+ flatSecs: number;
898
+ };
899
+ export declare function runDurationSecs(run: RunRecord): number | null;
900
+ /**
901
+ * Why a failed run failed, said in one line: the failing step and its last
902
+ * error event. The journal has the whole story; a runs *list* needs the
903
+ * sentence — "failed" with no reason makes every diagnosis start by opening
904
+ * the run, and the answer was sitting in the record all along.
905
+ */
906
+ export declare function runFailure(run: RunRecord): {
907
+ agent: string;
908
+ reason: string;
909
+ } | null;
910
+ /**
911
+ * The one-line outcome, pulled out of the reply that already says it.
912
+ *
913
+ * The last step with a result, because that is the one that concluded —
914
+ * a reporter at the end of a flow, or on a failed run whatever got furthest,
915
+ * which is the most informative line available either way.
916
+ *
917
+ * Markdown decoration is stripped rather than rendered: this lands in an
918
+ * email subject and a table cell, where `## ` and `**` are noise. Sibling of
919
+ * runFailure, which puts the reason for a failure on the list for the same
920
+ * reason — the sentence was in the record all along.
921
+ */
922
+ export declare function runSummary(run: RunRecord): string | null;
923
+ /**
924
+ * What one step concluded, in a line.
925
+ *
926
+ * The model's ANSWER first, falling back to the whole reply. `result` is every
927
+ * text block joined, and a model that narrates before calling a tool puts
928
+ * "Now let me read the outputs…" at the top of it — so reading the joined
929
+ * text reported the plan of every step that used a tool, which is most of
930
+ * them. Steps recorded before `conclusion` existed have only `result`, and
931
+ * for those this is exactly what it always was.
932
+ */
933
+ export declare function stepHeadline(step: StepRecord): string | null;
934
+ /**
935
+ * The first line of a reply that a person would recognise as its point.
936
+ *
937
+ * Markdown decoration is stripped rather than skipped, so a reply that opens
938
+ * with `## Done: 4 findings` still has a headline. A line that is only
939
+ * punctuation — a bare `#`, a rule, a row of asterisks — is decoration, and a
940
+ * code fence is the shape of an `output: json` reply, not its headline.
941
+ *
942
+ * Exported because a run's summary and one agent's step both want it, and two
943
+ * copies would be two answers to the same question.
944
+ */
945
+ export declare function headlineOf(result: string | null | undefined): string | null;
946
+ /** One step this agent ran, with the run it belongs to. */
947
+ export interface AgentStep {
948
+ runId: string;
949
+ flow: string;
950
+ status: RunRecord["status"];
951
+ startedAt: string;
952
+ /** The step's own first line — what THIS agent concluded, which is not
953
+ * the run's summary unless it happened to run last. */
954
+ headline: string | null;
955
+ instruction: string;
956
+ stepStatus: StepRecord["status"];
957
+ costUsd: number | null;
958
+ /** Set on a fan-out instance: the item this one worked on. */
959
+ item?: string;
960
+ /** Path refusals in this step — see deniedPaths. */
961
+ denied: number;
962
+ }
963
+ /**
964
+ * Everything one agent has done, newest first.
965
+ *
966
+ * The runs list answers "what ran"; it is keyed by flow, so an agent that
967
+ * appears in four flows has its work scattered across four filters and an
968
+ * agent used by none is invisible unless someone remembers the `adhoc:`
969
+ * prefix. This is the other axis, and the one you want when the question is
970
+ * "is this agent any good" rather than "did Monday's flow finish".
971
+ */
972
+ /** Path refusals in a step — see deniedPaths in the dashboard's
973
+ * step-io-model, which is the pure copy the client can import. */
974
+ export declare function deniedPaths(step: Pick<StepRecord, "events">): number;
975
+ export declare function listAgentSteps(tenant: string, workspace: string, agent: string): AgentStep[];
976
+ export declare function listRuns(tenant: string, workspace: string): RunRecord[];
977
+ /**
978
+ * `provider:` — which endpoint the model calls run against, and the two
979
+ * knobs that make a non-Anthropic gateway usable rather than merely
980
+ * reachable.
981
+ *
982
+ * provider:
983
+ * base_url: https://openrouter.ai/api
984
+ * token: ${OPENROUTER_API_KEY}
985
+ * models:
986
+ * fast: google/gemini-2.5-flash
987
+ * max: anthropic/claude-opus-4.1
988
+ * headers:
989
+ * X-Title: foldrun
990
+ *
991
+ * `models:` is what makes tiers portable rather than merely renamed-proof:
992
+ * without it `model: fast` means the literal string "haiku" on every
993
+ * endpoint, which is only true on one of them. `headers:` is the escape
994
+ * hatch for whatever a gateway wants that our schema will never have —
995
+ * routing preferences, attribution, tags. Both are the gateway's business,
996
+ * so neither grows a per-vendor branch in here.
997
+ */
998
+ export interface ProviderSpec {
999
+ /** The preset this block was built from, when `name:` named one. */
1000
+ name: string | null;
1001
+ /** What the endpoint speaks. `anthropic` is reached directly; `openai`
1002
+ * through the runtime's own translator on localhost (translator.ts). */
1003
+ format: WireFormat;
1004
+ /** Which header carries the key on an Anthropic-format endpoint. */
1005
+ auth: AuthShape;
1006
+ baseUrl: string;
1007
+ /** May still contain `${SECRET}` — resolving needs a tenant. */
1008
+ token: string;
1009
+ /** Tier → the id this gateway calls that tier. Absent tiers keep ours. */
1010
+ models: Partial<Record<Tier, string>>;
1011
+ /** Header name → value; values may contain `${SECRET}`. */
1012
+ headers: Record<string, string>;
1013
+ /** Extra request fields for a translated (`format: openai`) endpoint,
1014
+ * merged into the outgoing body verbatim. `null` removes a field the
1015
+ * translation would otherwise send. The escape hatch for every knob this
1016
+ * format deliberately does not model. */
1017
+ params: Record<string, unknown>;
1018
+ /** Things wrong enough to say out loud but not to fail a run over. */
1019
+ warnings: string[];
1020
+ /** A second endpoint, tried once when the first refuses over money, auth
1021
+ * or limits (not when the work itself fails). Same shape, one level deep
1022
+ * — a fallback with its own fallback is a routing table, and that lives
1023
+ * in a gateway, not in frontmatter. */
1024
+ fallback?: ProviderSpec | null;
1025
+ }
1026
+ export declare function parseProvider(raw: unknown): ProviderSpec | null;
1027
+ /**
1028
+ * The whole of a provider block as the env the Agent SDK reads. The one
1029
+ * place our format meets the SDK's, so the mapping is testable without a
1030
+ * vault: the caller resolves `${SECRET}` first and passes values.
1031
+ *
1032
+ * The token goes to ANTHROPIC_AUTH_TOKEN (a bearer credential) and
1033
+ * ANTHROPIC_API_KEY is explicitly blanked. Both matter. A key left in the
1034
+ * environment is sent as `x-api-key` and treated as a direct-Anthropic
1035
+ * credential, so a gateway that wants `Authorization: Bearer` gets the
1036
+ * wrong header — and worse, an *unset* key lets the SDK fall back to
1037
+ * authenticating against Anthropic directly, which fails somewhere far from
1038
+ * the file that caused it. A gateway that genuinely wants the key header
1039
+ * asks for it by name: `headers: { x-api-key: ${TOKEN} }`.
1040
+ */
1041
+ export declare function providerEnvFor(spec: {
1042
+ baseUrl?: string;
1043
+ /** Already secret-substituted. */
1044
+ token?: string;
1045
+ /** Which header the key rides in. Bearer unless the endpoint says
1046
+ * otherwise: the SDK sends ANTHROPIC_API_KEY as x-api-key and
1047
+ * ANTHROPIC_AUTH_TOKEN as a bearer, and blanks the other so it cannot
1048
+ * fall back to Anthropic's own endpoint with the wrong credential. */
1049
+ auth?: AuthShape;
1050
+ models: Partial<Record<Tier, string>>;
1051
+ headers: Record<string, string>;
1052
+ }): Record<string, string>;