@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.
- package/README.md +7 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/src/account.d.ts +45 -0
- package/dist/src/account.js +130 -0
- package/dist/src/api-tools.d.ts +8 -0
- package/dist/src/api-tools.js +22 -4
- package/dist/src/browser-pod.d.ts +148 -0
- package/dist/src/browser-pod.js +333 -0
- package/dist/src/catalog.js +2 -1
- package/dist/src/clock.d.ts +49 -0
- package/dist/src/clock.js +121 -0
- package/dist/src/completions.js +17 -8
- package/dist/src/confine.d.ts +22 -0
- package/dist/src/confine.js +218 -3
- package/dist/src/container.d.ts +9 -0
- package/dist/src/container.js +115 -5
- package/dist/src/context-tools.d.ts +1 -1
- package/dist/src/deploy.d.ts +33 -0
- package/dist/src/deploy.js +145 -6
- package/dist/src/egress.d.ts +7 -0
- package/dist/src/egress.js +6 -0
- package/dist/src/eol.d.ts +2 -0
- package/dist/src/eol.js +7 -0
- package/dist/src/evals.d.ts +5 -0
- package/dist/src/evals.js +9 -2
- package/dist/src/flow-lint.d.ts +8 -0
- package/dist/src/flow-lint.js +223 -1
- package/dist/src/flow-patterns.d.ts +141 -0
- package/dist/src/flow-patterns.js +568 -0
- package/dist/src/gitrepo.d.ts +18 -0
- package/dist/src/gitrepo.js +49 -0
- package/dist/src/host-env.js +5 -0
- package/dist/src/kinds.js +1 -1
- package/dist/src/language.d.ts +17 -0
- package/dist/src/language.js +55 -0
- package/dist/src/layout.d.ts +69 -0
- package/dist/src/layout.js +200 -0
- package/dist/src/library.d.ts +9 -0
- package/dist/src/library.js +30 -3
- package/dist/src/limits.d.ts +84 -0
- package/dist/src/limits.js +287 -0
- package/dist/src/locale.d.ts +47 -0
- package/dist/src/locale.js +177 -0
- package/dist/src/mail.d.ts +88 -0
- package/dist/src/mail.js +247 -0
- package/dist/src/model-credential.d.ts +14 -0
- package/dist/src/model-credential.js +58 -0
- package/dist/src/model-probe.d.ts +3 -0
- package/dist/src/model-probe.js +1 -0
- package/dist/src/notify.d.ts +53 -7
- package/dist/src/notify.js +194 -126
- package/dist/src/openapi.js +5 -3
- package/dist/src/operator.d.ts +201 -0
- package/dist/src/operator.js +251 -0
- package/dist/src/paths.d.ts +28 -0
- package/dist/src/paths.js +55 -0
- package/dist/src/platform.d.ts +43 -3
- package/dist/src/platform.js +9 -1
- package/dist/src/provider-health.js +3 -2
- package/dist/src/providers.d.ts +481 -0
- package/dist/src/providers.js +1105 -6
- package/dist/src/refusal.d.ts +83 -0
- package/dist/src/refusal.js +281 -0
- package/dist/src/run-container.d.ts +149 -13
- package/dist/src/run-container.js +424 -43
- package/dist/src/runner.d.ts +177 -9
- package/dist/src/runner.js +1146 -154
- package/dist/src/runtime.d.ts +52 -0
- package/dist/src/runtime.js +493 -58
- package/dist/src/script-tools.d.ts +17 -2
- package/dist/src/script-tools.js +82 -10
- package/dist/src/secret-files.d.ts +6 -5
- package/dist/src/secret-files.js +24 -11
- package/dist/src/secret-health.d.ts +30 -6
- package/dist/src/secret-health.js +42 -20
- package/dist/src/secrets.d.ts +19 -0
- package/dist/src/secrets.js +155 -86
- package/dist/src/starter.d.ts +9 -0
- package/dist/src/starter.js +92 -11
- package/dist/src/step-exec.d.ts +72 -2
- package/dist/src/step-exec.js +431 -37
- package/dist/src/step-removal.d.ts +15 -0
- package/dist/src/step-removal.js +49 -0
- package/dist/src/storage.d.ts +23 -4
- package/dist/src/storage.js +125 -23
- package/dist/src/store.d.ts +222 -37
- package/dist/src/store.js +349 -62
- package/dist/src/subagents.d.ts +75 -0
- package/dist/src/subagents.js +149 -0
- package/dist/src/test-mode.d.ts +17 -2
- package/dist/src/test-mode.js +53 -13
- package/dist/src/tool-names.d.ts +13 -1
- package/dist/src/tool-names.js +38 -11
- package/dist/src/tool-programs.d.ts +26 -0
- package/dist/src/tool-programs.js +172 -0
- package/dist/src/tool-test.js +104 -12
- package/dist/src/translator.d.ts +8 -2
- package/dist/src/translator.js +69 -23
- package/dist/src/trigger-log.d.ts +1 -1
- package/dist/src/triggers.d.ts +3 -0
- package/dist/src/triggers.js +10 -2
- package/dist/src/untrusted-fetch.d.ts +11 -0
- package/dist/src/untrusted-fetch.js +77 -0
- package/dist/src/web-actions.d.ts +91 -0
- package/dist/src/web-actions.js +199 -0
- package/dist/src/webhook.d.ts +4 -0
- package/dist/src/webhook.js +39 -0
- package/index.ts +4 -0
- package/package.json +3 -3
- package/src/account.ts +149 -0
- package/src/api-tools.ts +32 -4
- package/src/browser-pod.ts +350 -0
- package/src/catalog.ts +2 -1
- package/src/clock.ts +149 -0
- package/src/completions.ts +17 -8
- package/src/confine.ts +218 -3
- package/src/container.ts +112 -5
- package/src/context-tools.ts +1 -1
- package/src/deploy.ts +137 -6
- package/src/egress.ts +13 -1
- package/src/eol.ts +7 -0
- package/src/evals.ts +14 -2
- package/src/flow-lint.ts +253 -1
- package/src/flow-patterns.ts +597 -0
- package/src/gitrepo.ts +48 -0
- package/src/host-env.ts +5 -0
- package/src/kinds.ts +1 -1
- package/src/language.ts +62 -0
- package/src/layout.ts +228 -0
- package/src/library.ts +37 -3
- package/src/limits.ts +290 -0
- package/src/locale.ts +217 -0
- package/src/mail.ts +318 -0
- package/src/model-credential.ts +71 -0
- package/src/model-probe.ts +4 -0
- package/src/notify.ts +189 -103
- package/src/openapi.ts +5 -3
- package/src/operator.ts +300 -0
- package/src/paths.ts +54 -0
- package/src/platform.ts +52 -3
- package/src/provider-health.ts +3 -2
- package/src/providers.ts +1368 -6
- package/src/refusal.ts +320 -0
- package/src/run-container.ts +460 -44
- package/src/runner.ts +1168 -155
- package/src/runtime.ts +487 -55
- package/src/script-tools.ts +81 -10
- package/src/secret-files.ts +22 -10
- package/src/secret-health.ts +64 -19
- package/src/secrets.ts +149 -87
- package/src/starter.ts +93 -11
- package/src/step-exec.ts +447 -38
- package/src/step-removal.ts +59 -0
- package/src/storage.ts +141 -22
- package/src/store.ts +506 -70
- package/src/subagents.ts +189 -0
- package/src/test-mode.ts +51 -11
- package/src/tool-names.ts +40 -11
- package/src/tool-programs.ts +179 -0
- package/src/tool-test.ts +110 -12
- package/src/translator.ts +72 -22
- package/src/trigger-log.ts +1 -0
- package/src/triggers.ts +10 -2
- package/src/untrusted-fetch.ts +88 -0
- package/src/web-actions.ts +237 -0
- package/src/webhook.ts +41 -0
- package/templates/blog-desk/agents/writer/agent.md +1 -1
- package/templates/hello/agents/curator/agent.md +4 -4
- 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
|
-
|
|
218
|
-
the
|
|
219
|
-
its Agent SDK to
|
|
220
|
-
|
|
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
|
+
}
|
package/dist/src/api-tools.d.ts
CHANGED
|
@@ -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. */
|
package/dist/src/api-tools.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|