@foldrun/core 0.4.0 → 0.6.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 (170) hide show
  1. package/README.md +7 -4
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.js +4 -0
  4. package/dist/src/account.d.ts +45 -0
  5. package/dist/src/account.js +130 -0
  6. package/dist/src/api-tools.d.ts +8 -0
  7. package/dist/src/api-tools.js +22 -4
  8. package/dist/src/browser-pod.d.ts +148 -0
  9. package/dist/src/browser-pod.js +333 -0
  10. package/dist/src/catalog.js +2 -1
  11. package/dist/src/clock.d.ts +49 -0
  12. package/dist/src/clock.js +121 -0
  13. package/dist/src/completions.js +17 -8
  14. package/dist/src/confine.d.ts +22 -0
  15. package/dist/src/confine.js +218 -3
  16. package/dist/src/container.d.ts +9 -0
  17. package/dist/src/container.js +115 -5
  18. package/dist/src/context-tools.d.ts +1 -1
  19. package/dist/src/deploy.d.ts +33 -0
  20. package/dist/src/deploy.js +145 -6
  21. package/dist/src/egress.d.ts +7 -0
  22. package/dist/src/egress.js +6 -0
  23. package/dist/src/eol.d.ts +2 -0
  24. package/dist/src/eol.js +7 -0
  25. package/dist/src/evals.d.ts +5 -0
  26. package/dist/src/evals.js +9 -2
  27. package/dist/src/flow-lint.d.ts +8 -0
  28. package/dist/src/flow-lint.js +223 -1
  29. package/dist/src/flow-patterns.d.ts +141 -0
  30. package/dist/src/flow-patterns.js +568 -0
  31. package/dist/src/gitrepo.d.ts +18 -0
  32. package/dist/src/gitrepo.js +49 -0
  33. package/dist/src/host-env.js +5 -0
  34. package/dist/src/kinds.js +1 -1
  35. package/dist/src/language.d.ts +17 -0
  36. package/dist/src/language.js +55 -0
  37. package/dist/src/layout.d.ts +69 -0
  38. package/dist/src/layout.js +200 -0
  39. package/dist/src/library.d.ts +9 -0
  40. package/dist/src/library.js +30 -3
  41. package/dist/src/limits.d.ts +84 -0
  42. package/dist/src/limits.js +287 -0
  43. package/dist/src/locale.d.ts +47 -0
  44. package/dist/src/locale.js +177 -0
  45. package/dist/src/mail.d.ts +88 -0
  46. package/dist/src/mail.js +247 -0
  47. package/dist/src/model-credential.d.ts +14 -0
  48. package/dist/src/model-credential.js +58 -0
  49. package/dist/src/model-probe.d.ts +3 -0
  50. package/dist/src/model-probe.js +1 -0
  51. package/dist/src/notify.d.ts +53 -7
  52. package/dist/src/notify.js +194 -126
  53. package/dist/src/openapi.js +5 -3
  54. package/dist/src/operator.d.ts +201 -0
  55. package/dist/src/operator.js +251 -0
  56. package/dist/src/paths.d.ts +28 -0
  57. package/dist/src/paths.js +55 -0
  58. package/dist/src/platform.d.ts +43 -3
  59. package/dist/src/platform.js +9 -1
  60. package/dist/src/provider-health.js +3 -2
  61. package/dist/src/providers.d.ts +481 -0
  62. package/dist/src/providers.js +1105 -6
  63. package/dist/src/refusal.d.ts +83 -0
  64. package/dist/src/refusal.js +281 -0
  65. package/dist/src/run-container.d.ts +149 -13
  66. package/dist/src/run-container.js +424 -43
  67. package/dist/src/runner.d.ts +177 -9
  68. package/dist/src/runner.js +1146 -154
  69. package/dist/src/runtime.d.ts +52 -0
  70. package/dist/src/runtime.js +493 -58
  71. package/dist/src/script-tools.d.ts +17 -2
  72. package/dist/src/script-tools.js +82 -10
  73. package/dist/src/secret-files.d.ts +6 -5
  74. package/dist/src/secret-files.js +24 -11
  75. package/dist/src/secret-health.d.ts +30 -6
  76. package/dist/src/secret-health.js +42 -20
  77. package/dist/src/secrets.d.ts +19 -0
  78. package/dist/src/secrets.js +155 -86
  79. package/dist/src/starter.d.ts +9 -0
  80. package/dist/src/starter.js +92 -11
  81. package/dist/src/step-exec.d.ts +72 -2
  82. package/dist/src/step-exec.js +431 -37
  83. package/dist/src/step-removal.d.ts +15 -0
  84. package/dist/src/step-removal.js +49 -0
  85. package/dist/src/storage.d.ts +23 -4
  86. package/dist/src/storage.js +125 -23
  87. package/dist/src/store.d.ts +222 -37
  88. package/dist/src/store.js +349 -62
  89. package/dist/src/subagents.d.ts +75 -0
  90. package/dist/src/subagents.js +149 -0
  91. package/dist/src/test-mode.d.ts +17 -2
  92. package/dist/src/test-mode.js +53 -13
  93. package/dist/src/tool-names.d.ts +13 -1
  94. package/dist/src/tool-names.js +38 -11
  95. package/dist/src/tool-programs.d.ts +26 -0
  96. package/dist/src/tool-programs.js +172 -0
  97. package/dist/src/tool-test.js +104 -12
  98. package/dist/src/translator.d.ts +8 -2
  99. package/dist/src/translator.js +69 -23
  100. package/dist/src/trigger-log.d.ts +1 -1
  101. package/dist/src/triggers.d.ts +3 -0
  102. package/dist/src/triggers.js +10 -2
  103. package/dist/src/untrusted-fetch.d.ts +11 -0
  104. package/dist/src/untrusted-fetch.js +77 -0
  105. package/dist/src/web-actions.d.ts +91 -0
  106. package/dist/src/web-actions.js +199 -0
  107. package/dist/src/webhook.d.ts +4 -0
  108. package/dist/src/webhook.js +39 -0
  109. package/index.ts +4 -0
  110. package/package.json +3 -3
  111. package/src/account.ts +149 -0
  112. package/src/api-tools.ts +32 -4
  113. package/src/browser-pod.ts +350 -0
  114. package/src/catalog.ts +2 -1
  115. package/src/clock.ts +149 -0
  116. package/src/completions.ts +17 -8
  117. package/src/confine.ts +218 -3
  118. package/src/container.ts +112 -5
  119. package/src/context-tools.ts +1 -1
  120. package/src/deploy.ts +137 -6
  121. package/src/egress.ts +13 -1
  122. package/src/eol.ts +7 -0
  123. package/src/evals.ts +14 -2
  124. package/src/flow-lint.ts +253 -1
  125. package/src/flow-patterns.ts +597 -0
  126. package/src/gitrepo.ts +48 -0
  127. package/src/host-env.ts +5 -0
  128. package/src/kinds.ts +1 -1
  129. package/src/language.ts +62 -0
  130. package/src/layout.ts +228 -0
  131. package/src/library.ts +37 -3
  132. package/src/limits.ts +290 -0
  133. package/src/locale.ts +217 -0
  134. package/src/mail.ts +318 -0
  135. package/src/model-credential.ts +71 -0
  136. package/src/model-probe.ts +4 -0
  137. package/src/notify.ts +189 -103
  138. package/src/openapi.ts +5 -3
  139. package/src/operator.ts +300 -0
  140. package/src/paths.ts +54 -0
  141. package/src/platform.ts +52 -3
  142. package/src/provider-health.ts +3 -2
  143. package/src/providers.ts +1368 -6
  144. package/src/refusal.ts +320 -0
  145. package/src/run-container.ts +460 -44
  146. package/src/runner.ts +1168 -155
  147. package/src/runtime.ts +487 -55
  148. package/src/script-tools.ts +81 -10
  149. package/src/secret-files.ts +22 -10
  150. package/src/secret-health.ts +64 -19
  151. package/src/secrets.ts +149 -87
  152. package/src/starter.ts +93 -11
  153. package/src/step-exec.ts +447 -38
  154. package/src/step-removal.ts +59 -0
  155. package/src/storage.ts +141 -22
  156. package/src/store.ts +506 -70
  157. package/src/subagents.ts +189 -0
  158. package/src/test-mode.ts +51 -11
  159. package/src/tool-names.ts +40 -11
  160. package/src/tool-programs.ts +179 -0
  161. package/src/tool-test.ts +110 -12
  162. package/src/translator.ts +72 -22
  163. package/src/trigger-log.ts +1 -0
  164. package/src/triggers.ts +10 -2
  165. package/src/untrusted-fetch.ts +88 -0
  166. package/src/web-actions.ts +237 -0
  167. package/src/webhook.ts +41 -0
  168. package/templates/blog-desk/agents/writer/agent.md +1 -1
  169. package/templates/hello/agents/curator/agent.md +4 -4
  170. package/templates/hello/agents/notetaker/agent.md +3 -3
package/README.md CHANGED
@@ -208,16 +208,19 @@ To *run* an agent you need one of:
208
208
 
209
209
  - **`ANTHROPIC_API_KEY`** — an API key from
210
210
  [console.anthropic.com](https://console.anthropic.com).
211
+ - **`CLAUDE_CODE_OAUTH_TOKEN`** — a Claude login token, from
212
+ `claude setup-token`. Used when no API key is set.
211
213
  - **Your own provider.** An agent that names a `provider:` needs no
212
214
  Anthropic key: 29 endpoints are known by name, including `ollama`,
213
215
  `lmstudio` and `vllm`, so a local model is a complete answer. Anything
214
216
  not in that list works by spelling out `base_url:`, `format:` and
215
217
  `auth:`. See [providers](https://docs.foldrun.io/providers).
216
218
 
217
- A **Claude Code or claude.ai subscription login is not one of them**, and
218
- the CLI will not look for one. Anthropic does not permit products built on
219
- its Agent SDK to run on consumer subscriptions, so using that login here
220
- would put you outside their terms — an API key is the supported path.
219
+ The first one set is used, and every step records which. The login Claude
220
+ Code keeps on the machine is never picked up by itself. Anthropic does not
221
+ allow products built on its Agent SDK to offer claude.ai login unless
222
+ Anthropic has approved them, so check that the terms cover your use before
223
+ you rely on a login token.
221
224
 
222
225
  ## Status
223
226
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ export * from "./src/layout.ts";
1
2
  export * from "./src/paths.ts";
2
3
  export * from "./src/store.ts";
4
+ export * from "./src/model-credential.ts";
3
5
  export * from "./src/starter.ts";
4
6
  export * from "./src/runner.ts";
5
7
  export * from "./src/okf.ts";
@@ -10,6 +12,7 @@ export * from "./src/model-probe.ts";
10
12
  export * from "./src/providers.ts";
11
13
  export * from "./src/translator.ts";
12
14
  export * from "./src/arrange.ts";
15
+ export * from "./src/flow-patterns.ts";
13
16
  export * from "./src/completions.ts";
14
17
  export * from "./src/confine.ts";
15
18
  export * from "./src/platform.ts";
@@ -28,5 +31,6 @@ export * from "./src/notify.ts";
28
31
  export * from "./src/webhook.ts";
29
32
  export * from "./src/approvals.ts";
30
33
  export * from "./src/deploy.ts";
34
+ export * from "./src/account.ts";
31
35
  export * from "./src/tar.ts";
32
36
  export * from "./src/git.ts";
package/dist/index.js CHANGED
@@ -9,8 +9,10 @@
9
9
  // its worker, metering and billing, pods on a cluster, per-account keys in a
10
10
  // database, previews, share links, tenancy — lives in its own private package
11
11
  // and plugs in through platform.ts. Nothing here imports it.
12
+ export * from "./src/layout.js";
12
13
  export * from "./src/paths.js";
13
14
  export * from "./src/store.js";
15
+ export * from "./src/model-credential.js";
14
16
  export * from "./src/starter.js";
15
17
  export * from "./src/runner.js";
16
18
  export * from "./src/okf.js";
@@ -21,6 +23,7 @@ export * from "./src/model-probe.js";
21
23
  export * from "./src/providers.js";
22
24
  export * from "./src/translator.js";
23
25
  export * from "./src/arrange.js";
26
+ export * from "./src/flow-patterns.js";
24
27
  export * from "./src/completions.js";
25
28
  export * from "./src/confine.js";
26
29
  export * from "./src/platform.js";
@@ -39,5 +42,6 @@ export * from "./src/notify.js";
39
42
  export * from "./src/webhook.js";
40
43
  export * from "./src/approvals.js";
41
44
  export * from "./src/deploy.js";
45
+ export * from "./src/account.js";
42
46
  export * from "./src/tar.js";
43
47
  export * from "./src/git.js";
@@ -0,0 +1,45 @@
1
+ import { type LibraryKind } from "./library.ts";
2
+ import { type Layout } from "./layout.ts";
3
+ import type { DeployFile } from "./store.ts";
4
+ export interface AccountWorkspace {
5
+ name: string;
6
+ dir: string;
7
+ files: DeployFile[];
8
+ }
9
+ export interface LibraryFile {
10
+ kind: LibraryKind;
11
+ /** Relative to the kind's directory — what the library API calls `path`. */
12
+ path: string;
13
+ content: string;
14
+ }
15
+ export interface AccountTree {
16
+ root: string;
17
+ /** The account-scope AGENTS.md, or null when there is none. */
18
+ agentsMd: string | null;
19
+ library: LibraryFile[];
20
+ workspaces: AccountWorkspace[];
21
+ }
22
+ /** The shared library under an account root, flattened to one list. */
23
+ export declare function readLibraryTree(accountRoot: string): LibraryFile[];
24
+ /**
25
+ * Everything an account folder would push: its AGENTS.md, its library, and
26
+ * every workspace under it.
27
+ *
28
+ * `only` narrows it to one workspace — `foldrun deploy <name>` — and still
29
+ * carries the account scope, because a workspace deployed without the library
30
+ * its agents name is a workspace that fails on its first run.
31
+ */
32
+ export declare function readAccountTree(accountRoot: string, only?: string): AccountTree;
33
+ /**
34
+ * The account tree for wherever you are standing — an account root, a
35
+ * workspace inside one, or a flat workspace.
36
+ *
37
+ * A flat workspace is reported as an account of one whose root is its parent,
38
+ * which is exactly what `accountDir` and `libraryDir` already believe about
39
+ * it. So the deploy path has no branch for the old layout: it pushes one
40
+ * workspace and whatever library sits beside it, which is what it did before.
41
+ */
42
+ export declare function accountTreeFrom(from: string, only?: string): {
43
+ layout: Layout;
44
+ tree: AccountTree;
45
+ };
@@ -0,0 +1,130 @@
1
+ // An account folder, read as the thing a deploy pushes.
2
+ //
3
+ // `readTree` answers the same question for one workspace: what, of the files
4
+ // on disk, is source? This is that question one scope up — the account's own
5
+ // AGENTS.md, the shared `library/`, and every workspace under `workspaces/` —
6
+ // so `foldrun deploy` at an account root has one list to walk and one shape to
7
+ // report, rather than three special cases written at the call site.
8
+ //
9
+ // Nothing here talks to a platform. It reads a directory and says what is in
10
+ // it; who gets sent where is the CLI's business, because the endpoints differ
11
+ // per part (a workspace deploys whole, a library file writes one at a time)
12
+ // and that asymmetry belongs next to the fetch calls, not in here.
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { readTree } from "./deploy.js";
16
+ import { LIBRARY_KINDS } from "./library.js";
17
+ import { detectLayout, listWorkspaceDirs } from "./layout.js";
18
+ /** Anything that is bookkeeping rather than source, at either scope. */
19
+ const NOT_SOURCE = new Set([".foldrun", ".git", "node_modules", "runs", "outputs", ".DS_Store"]);
20
+ function walkFiles(root, rel = "") {
21
+ const out = [];
22
+ let entries;
23
+ try {
24
+ entries = fs.readdirSync(root).sort();
25
+ }
26
+ catch {
27
+ return out;
28
+ }
29
+ for (const entry of entries) {
30
+ if (NOT_SOURCE.has(entry))
31
+ continue;
32
+ const full = path.join(root, entry);
33
+ const next = rel ? `${rel}/${entry}` : entry;
34
+ try {
35
+ if (fs.lstatSync(full).isSymbolicLink())
36
+ continue;
37
+ if (fs.statSync(full).isDirectory())
38
+ out.push(...walkFiles(full, next));
39
+ else
40
+ out.push({ path: next, content: fs.readFileSync(full, "utf8") });
41
+ }
42
+ catch {
43
+ /* unreadable: not source */
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+ /** The shared library under an account root, flattened to one list. */
49
+ export function readLibraryTree(accountRoot) {
50
+ const out = [];
51
+ for (const kind of LIBRARY_KINDS) {
52
+ const dir = path.join(accountRoot, "library", kind);
53
+ for (const f of walkFiles(dir)) {
54
+ // `.gitkeep` and friends keep an empty library directory in git and are
55
+ // not library entries: the write endpoint refuses a path with no known
56
+ // extension, so shipping one would fail the deploy over a placeholder.
57
+ if (f.path.split("/").some((seg) => seg.startsWith(".")))
58
+ continue;
59
+ out.push({ kind, path: f.path, content: f.content });
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+ /**
65
+ * Everything an account folder would push: its AGENTS.md, its library, and
66
+ * every workspace under it.
67
+ *
68
+ * `only` narrows it to one workspace — `foldrun deploy <name>` — and still
69
+ * carries the account scope, because a workspace deployed without the library
70
+ * its agents name is a workspace that fails on its first run.
71
+ */
72
+ export function readAccountTree(accountRoot, only) {
73
+ const root = path.resolve(accountRoot);
74
+ const agentsMdFile = path.join(root, "AGENTS.md");
75
+ let agentsMd = null;
76
+ try {
77
+ agentsMd = fs.readFileSync(agentsMdFile, "utf8");
78
+ }
79
+ catch {
80
+ /* an account with no AGENTS.md pushes none */
81
+ }
82
+ const names = listWorkspaceDirs(root).filter((n) => (only ? n === only : true));
83
+ if (only && names.length === 0) {
84
+ throw new Error(`no workspace "${only}" in ${root} — have: ${listWorkspaceDirs(root).join(", ") || "none"}`);
85
+ }
86
+ const wsRoot = path.join(root, "workspaces");
87
+ const legacy = path.join(root, "projects");
88
+ const base = fs.existsSync(wsRoot) ? wsRoot : legacy;
89
+ return {
90
+ root,
91
+ agentsMd,
92
+ library: readLibraryTree(root),
93
+ workspaces: names.map((name) => {
94
+ const dir = path.join(base, name);
95
+ return { name, dir, files: readTree(dir) };
96
+ }),
97
+ };
98
+ }
99
+ /**
100
+ * The account tree for wherever you are standing — an account root, a
101
+ * workspace inside one, or a flat workspace.
102
+ *
103
+ * A flat workspace is reported as an account of one whose root is its parent,
104
+ * which is exactly what `accountDir` and `libraryDir` already believe about
105
+ * it. So the deploy path has no branch for the old layout: it pushes one
106
+ * workspace and whatever library sits beside it, which is what it did before.
107
+ */
108
+ export function accountTreeFrom(from, only) {
109
+ const layout = detectLayout(from);
110
+ if (layout.kind === "flat" || layout.kind === "empty") {
111
+ const dir = layout.workspaceDir;
112
+ const name = path.basename(dir);
113
+ if (only && only !== name)
114
+ throw new Error(`${dir} is a single workspace called "${name}", not "${only}"`);
115
+ return {
116
+ layout,
117
+ tree: {
118
+ root: layout.accountRoot,
119
+ // A flat workspace's AGENTS.md belongs to the workspace, not the
120
+ // account — pushing it as account scope would apply one desk's rules
121
+ // to every other desk in the installation.
122
+ agentsMd: null,
123
+ library: readLibraryTree(layout.accountRoot),
124
+ workspaces: [{ name, dir, files: readTree(dir) }],
125
+ },
126
+ };
127
+ }
128
+ const only2 = only ?? (layout.kind === "workspace-in-account" ? layout.workspace : undefined);
129
+ return { layout, tree: readAccountTree(layout.accountRoot, only2) };
130
+ }
@@ -25,6 +25,14 @@ export interface ApiToolResult {
25
25
  server: ReturnType<typeof createSdkMcpServer> | null;
26
26
  /** Tool names to allow, e.g. mcp__foldrun_apis__call_google_ads. */
27
27
  toolNames: string[];
28
+ /** SDK tool name → the API it was built for, recorded as each tool is
29
+ * made: `crm`'s operation `admin_list` is `crm_admin_list` beside an API
30
+ * named `crm_admin`, so the name alone cannot say whose it is. */
31
+ owners: Record<string, string>;
32
+ /** SDK tool name → the HTTP method of a typed operation. A generic
33
+ * call_<api> carries its method in each call's input instead. Read by the
34
+ * browser-pod fallback (browser-pod.ts) to tell a GET from a write. */
35
+ methods: Record<string, string>;
28
36
  /** Secrets referenced but not set — surfaced as run warnings. */
29
37
  missingSecrets: string[];
30
38
  /** Human-readable lines describing each API, appended to the system prompt. */
@@ -258,7 +258,7 @@ export function buildApiTools(tenant, apis, workspace,
258
258
  // crossed the boundary, and there is no vault in there to ask.
259
259
  resolved) {
260
260
  if (apis.length === 0) {
261
- return { server: null, toolNames: [], missingSecrets: [], promptLines: [], drainLog: () => [] };
261
+ return { server: null, toolNames: [], owners: {}, methods: {}, missingSecrets: [], promptLines: [], drainLog: () => [] };
262
262
  }
263
263
  const neededSecrets = [...new Set(apis.flatMap(secretsUsedByApi))];
264
264
  const { env, missing } = resolved
@@ -271,23 +271,39 @@ resolved) {
271
271
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the SDK types its own list this way
272
272
  const tools = [];
273
273
  const toolNames = [];
274
+ const owners = {};
275
+ const methods = {};
274
276
  const promptLines = [];
275
277
  for (const api of apis) {
276
278
  // One bucket per API per step: the limit is the vendor's, the scope is
277
279
  // this process. Two steps on two workers each get their own.
278
280
  const bucket = api.rate ? new TokenBucket(api.rate.count, api.rate.perSec) : null;
279
281
  const ops = api.resolvedOperations ?? [];
280
- // An explicit `operations:` list means "only these": no escape hatch.
281
- const withGeneric = !(api.operations && ops.length);
282
+ // An explicit `operations:` list means "only these": no escape hatch,
283
+ // ever. It fails closed. If the document did not load, or yielded none of
284
+ // the listed operations, the API gets no tools this run rather than the
285
+ // generic one: an allowlist that opens when a vendor renames an
286
+ // operation or a URL moves is not an allowlist.
287
+ const allowlisted = !!api.operations?.length;
288
+ const withGeneric = !allowlisted;
289
+ if (allowlisted && !ops.length) {
290
+ promptLines.push(`- **${api.name}** — unavailable this run: its \`operations:\` allowlist resolved no operations, ` +
291
+ `so no tool is exposed. Report it as blocked; do not reach this API another way.`);
292
+ continue;
293
+ }
282
294
  const typedNames = [];
283
295
  for (const op of ops) {
284
296
  tools.push(typedTool(api, op, env, bucket, log));
285
297
  typedNames.push(typedToolName(api.name, op.id));
298
+ methods[`mcp__foldrun_apis__${typedToolName(api.name, op.id)}`] = String(op.method ?? "").toUpperCase();
286
299
  }
287
300
  if (withGeneric)
288
301
  tools.push(genericTool(api, env, bucket, log));
289
302
  const names = [...typedNames, ...(withGeneric ? [`call_${api.name}`] : [])];
290
- toolNames.push(...names.map((n) => `mcp__foldrun_apis__${n}`));
303
+ for (const n of names) {
304
+ toolNames.push(`mcp__foldrun_apis__${n}`);
305
+ owners[`mcp__foldrun_apis__${n}`] = api.name;
306
+ }
291
307
  const rateLine = api.rate ? ` Rate limit ${api.rate.count}/${Math.round(api.rate.count / api.rate.perSec)}s — over it, calls wait.` : "";
292
308
  if (ops.length) {
293
309
  const listed = ops.slice(0, PROMPT_LIST_CAP).map((op) => ` - \`${typedToolName(api.name, op.id)}\` — ${op.summary}`);
@@ -306,6 +322,8 @@ resolved) {
306
322
  return {
307
323
  server,
308
324
  toolNames,
325
+ owners,
326
+ methods,
309
327
  missingSecrets: missing,
310
328
  promptLines,
311
329
  drainLog: () => log.splice(0, log.length),
@@ -0,0 +1,148 @@
1
+ /** Set by the platform on a step it put on the slim image to browse
2
+ * through the pod. The web tool then never launches a browser of its own. */
3
+ export declare const POD_ONLY_ENV = "FOLDRUN_BROWSER_POD_ONLY";
4
+ /** Where the web tool appends one JSON line per reconnect, and the line
5
+ * that says the pod is lost or the call needs the full image. */
6
+ export declare const POD_EVENTS_ENV = "FOLDRUN_BROWSER_POD_EVENTS";
7
+ /** The file, inside the step's sandbox. */
8
+ export declare const POD_EVENTS_FILE = "/tmp/foldrun-browser-pod.jsonl";
9
+ /** The note the run carries when the fallback was taken. Worded once. */
10
+ export declare const RERAN_ON_FULL = "browser pod lost; re-ran on full";
11
+ /**
12
+ * Browse actions that can change something beyond the page in front of the
13
+ * model: anything that types, picks, submits, signs in, uploads, runs script
14
+ * or pays. A click is here because a click can submit and nothing before
15
+ * the click can tell. The web tool's run.mjs carries a copy (it decides
16
+ * whether a dropped call may be repeated); the gallery test holds the two
17
+ * equal.
18
+ */
19
+ export declare const BROWSE_WRITE_ACTIONS: readonly ["click", "dblclick", "rightclick", "tap", "swipe", "mouse", "drag", "fill", "type", "insert", "paste", "press", "keyboard", "keydown", "keyup", "clear", "select", "check", "uncheck", "upload", "dialog", "login", "eval", "webmcp", "solve"];
20
+ export type CallKind = {
21
+ write: false;
22
+ } | {
23
+ write: true;
24
+ what: string;
25
+ };
26
+ export interface ClassifyContext {
27
+ /** SDK tool name → HTTP method, for an API's typed operations (api-tools
28
+ * builds the map; a generic call_<api> carries its method in the input). */
29
+ methods?: Record<string, string>;
30
+ /** Web actions a paid provider answers this step — from the env's
31
+ * FOLDRUN_WEB_<ACTION>_VIA. */
32
+ paidWeb?: Record<string, string>;
33
+ /** Page scripts the agent's web.browse block runs in every page — the
34
+ * env's FOLDRUN_BROWSER_INIT. They make every browse call a write. */
35
+ browseInit?: string;
36
+ }
37
+ /**
38
+ * The browse actions a call asked for, nested `if` branches included.
39
+ *
40
+ * Read the way the web tool reads them (platform gallery.ts, browse.mjs
41
+ * checkSteps): `actions` is an array of steps, a step's action is one of its
42
+ * OWN keys (the first the tool knows), and the only steps inside a step are
43
+ * an `if`'s `then` and `else`. Every own key of a step is returned — the
44
+ * caller keeps the ones it cares about, and a step holding a write key beside
45
+ * a read key counts as the write. What sits inside an option's value — an
46
+ * extract's `fields` (column names), a mock's `json` body, a webmcp `input` —
47
+ * is data, not an action: `{"fields": {"type": "td.kind"}}` is no typing.
48
+ *
49
+ * A shape the tool would refuse (an object where the array belongs, a step
50
+ * that is not an object, `then` that is not a list) is walked key by key at
51
+ * every depth, as before; nesting past MAX_DEPTH is unreadable, so a write
52
+ * buried deep is never taken for a read.
53
+ */
54
+ export declare function browseActionNames(actions: unknown): string[];
55
+ /** What a browse call's arguments would write, or null when it only reads. */
56
+ export declare function browseWrites(input: Record<string, unknown>): string | null;
57
+ /**
58
+ * Whether one tool call can have changed anything outside the model's head,
59
+ * and if so, a few words saying what. Pure.
60
+ */
61
+ export declare function classifyCall(tool: string, input: Record<string, unknown> | undefined, ctx?: ClassifyContext): CallKind;
62
+ /** Paid web actions, from a step's env (FOLDRUN_WEB_<ACTION>_VIA=provider). */
63
+ export declare function paidWebActions(env: Record<string, string | undefined>): Record<string, string>;
64
+ /** One line of the web tool's pod log. */
65
+ export interface PodEvent {
66
+ kind: "reconnect" | "lost" | "needs-full";
67
+ /** reconnect: which try, of how many, and whether it got through. */
68
+ attempt?: number;
69
+ of?: number;
70
+ ok?: boolean;
71
+ /** lost: whether the call ran on the pod at all before it was lost — a
72
+ * call that never connected changed nothing, whatever it asked for. */
73
+ ran?: boolean;
74
+ error?: string;
75
+ why?: string;
76
+ /** When the web tool wrote the line (ISO). */
77
+ at?: string;
78
+ }
79
+ /** ", at 10:15:15Z" from a pod log line's `at`; empty when it has none. */
80
+ export declare function podAt(at: unknown): string;
81
+ /** Lines of the pod log from byte `offset` on, and where the next read
82
+ * starts. A torn last line waits for the next read. */
83
+ export declare function readPodEvents(text: string, offset: number): {
84
+ events: PodEvent[];
85
+ next: number;
86
+ };
87
+ /** What crosses back from a step that browsed through the pod. */
88
+ export interface BrowserPodOutcome {
89
+ /** Reconnect tries the web tool made, and how many got through: the pod
90
+ * answered and the call went on, rather than dropping again at once. */
91
+ reconnects: number;
92
+ reconnected: number;
93
+ /** Reconnects that reached the pod, after which the call dropped again at
94
+ * once — a pod still answering while it shut down. Never "got through". */
95
+ closedAgain?: number;
96
+ /** Set when the step stopped because the pod was gone (or a call needed
97
+ * a browser in the step, which slim does not have). */
98
+ lost?: {
99
+ cause: "lost" | "needs-full";
100
+ detail: string;
101
+ };
102
+ /** What the step had written by then, worded — empty means reads only. */
103
+ writes?: string[];
104
+ }
105
+ /** "a.md (Write), crm POST and 2 more". */
106
+ export declare function describeWrites(writes: string[]): string;
107
+ /** The decision, once the pod is gone: re-run on full, or fail and say why. */
108
+ export declare function podLossDecision(pod: BrowserPodOutcome): {
109
+ rerun: true;
110
+ note: string;
111
+ } | {
112
+ rerun: false;
113
+ message: string;
114
+ };
115
+ /**
116
+ * Why a browsing step must have a browser in its own sandbox, from what the
117
+ * step start can see (the agent's `web.browse` block, as env), or null when
118
+ * the account's pod can serve every call it could make by default. A call
119
+ * that picks an in-step engine itself is caught at the call (needs-full).
120
+ */
121
+ export declare function slimBrowseBlocker(env: Record<string, string | undefined>): string | null;
122
+ /** The outcome as it crosses back from the sandbox, checked field by field
123
+ * (the done line is the pod's word); null when absent or not this shape. */
124
+ export declare function parseBrowserPod(v: unknown): BrowserPodOutcome | null;
125
+ /** One line for a person: "2 reconnects (2 got through)", "browser pod
126
+ * lost; re-ran on full", or the failure. What the run page, `foldrun
127
+ * report` and an attempt row say. A pod that was lost in the end is never
128
+ * said to have let a reconnect through — "1 reconnect (1 got through); pod
129
+ * lost" read live as a contradiction — and a reconnect that reached the pod
130
+ * only for the call to drop again says that. */
131
+ export declare function browserPodLine(p: BrowserPodOutcome & {
132
+ fallback?: string;
133
+ failure?: string;
134
+ }): string;
135
+ /** What podTriesLine reads of a StepAttempt (store.ts) — structural, so
136
+ * this module stays free of the store's imports for the CLI and the web. */
137
+ export interface TryRow {
138
+ n: number;
139
+ status: string;
140
+ image?: string;
141
+ costUsd: number | null;
142
+ }
143
+ /** A step's tries in one line when the browser pod was lost under one of
144
+ * them — "slim · lost · $0.0040 → full · completed · $0.0060" — so the run
145
+ * page and `foldrun report` show the slim go and the full re-run apart.
146
+ * `#n` leads each try when the step had more than one attempt. Null when
147
+ * no try was lost. */
148
+ export declare function podTriesLine(tries: readonly TryRow[] | null | undefined): string | null;