@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.
- package/LICENSE +202 -0
- package/README.md +239 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +43 -0
- package/dist/src/agent-tools.d.ts +31 -0
- package/dist/src/agent-tools.js +99 -0
- package/dist/src/agents-md.d.ts +9 -0
- package/dist/src/agents-md.js +83 -0
- package/dist/src/api-tools.d.ts +38 -0
- package/dist/src/api-tools.js +313 -0
- package/dist/src/approvals.d.ts +39 -0
- package/dist/src/approvals.js +103 -0
- package/dist/src/arrange.d.ts +8 -0
- package/dist/src/arrange.js +40 -0
- package/dist/src/catalog.d.ts +108 -0
- package/dist/src/catalog.js +255 -0
- package/dist/src/completions.d.ts +61 -0
- package/dist/src/completions.js +578 -0
- package/dist/src/confine.d.ts +31 -0
- package/dist/src/confine.js +298 -0
- package/dist/src/container.d.ts +30 -0
- package/dist/src/container.js +172 -0
- package/dist/src/context-tools.d.ts +81 -0
- package/dist/src/context-tools.js +347 -0
- package/dist/src/deploy.d.ts +100 -0
- package/dist/src/deploy.js +377 -0
- package/dist/src/egress.d.ts +75 -0
- package/dist/src/egress.js +133 -0
- package/dist/src/evals.d.ts +150 -0
- package/dist/src/evals.js +651 -0
- package/dist/src/flow-lint.d.ts +16 -0
- package/dist/src/flow-lint.js +177 -0
- package/dist/src/git-hook.d.ts +1 -0
- package/dist/src/git-hook.js +70 -0
- package/dist/src/git.d.ts +37 -0
- package/dist/src/git.js +101 -0
- package/dist/src/gitrepo.d.ts +95 -0
- package/dist/src/gitrepo.js +355 -0
- package/dist/src/history.d.ts +82 -0
- package/dist/src/history.js +259 -0
- package/dist/src/kinds.d.ts +95 -0
- package/dist/src/kinds.js +421 -0
- package/dist/src/library.d.ts +94 -0
- package/dist/src/library.js +341 -0
- package/dist/src/linkable.d.ts +20 -0
- package/dist/src/linkable.js +113 -0
- package/dist/src/model-probe.d.ts +21 -0
- package/dist/src/model-probe.js +81 -0
- package/dist/src/notify.d.ts +61 -0
- package/dist/src/notify.js +252 -0
- package/dist/src/oauth-presets.d.ts +13 -0
- package/dist/src/oauth-presets.js +39 -0
- package/dist/src/okf.d.ts +220 -0
- package/dist/src/okf.js +656 -0
- package/dist/src/openapi.d.ts +66 -0
- package/dist/src/openapi.js +342 -0
- package/dist/src/paths.d.ts +33 -0
- package/dist/src/paths.js +78 -0
- package/dist/src/platform.d.ts +32 -0
- package/dist/src/platform.js +48 -0
- package/dist/src/providers.d.ts +47 -0
- package/dist/src/providers.js +114 -0
- package/dist/src/refs.d.ts +20 -0
- package/dist/src/refs.js +78 -0
- package/dist/src/run-container.d.ts +227 -0
- package/dist/src/run-container.js +760 -0
- package/dist/src/runner.d.ts +241 -0
- package/dist/src/runner.js +3215 -0
- package/dist/src/runtime.d.ts +41 -0
- package/dist/src/runtime.js +357 -0
- package/dist/src/script-tools.d.ts +62 -0
- package/dist/src/script-tools.js +296 -0
- package/dist/src/secret-files.d.ts +12 -0
- package/dist/src/secret-files.js +92 -0
- package/dist/src/secrets.d.ts +147 -0
- package/dist/src/secrets.js +626 -0
- package/dist/src/starter.d.ts +21 -0
- package/dist/src/starter.js +399 -0
- package/dist/src/step-exec.d.ts +136 -0
- package/dist/src/step-exec.js +391 -0
- package/dist/src/storage.d.ts +123 -0
- package/dist/src/storage.js +640 -0
- package/dist/src/store.d.ts +1052 -0
- package/dist/src/store.js +2378 -0
- package/dist/src/tar.d.ts +30 -0
- package/dist/src/tar.js +111 -0
- package/dist/src/tool-names.d.ts +39 -0
- package/dist/src/tool-names.js +79 -0
- package/dist/src/tool-programs.d.ts +18 -0
- package/dist/src/tool-programs.js +52 -0
- package/dist/src/tool-test.d.ts +19 -0
- package/dist/src/tool-test.js +335 -0
- package/dist/src/translator.d.ts +159 -0
- package/dist/src/translator.js +986 -0
- package/dist/src/triggers.d.ts +60 -0
- package/dist/src/triggers.js +270 -0
- package/dist/src/webhook.d.ts +47 -0
- package/dist/src/webhook.js +148 -0
- package/index.ts +44 -0
- package/package.json +64 -0
- package/src/agent-tools.ts +138 -0
- package/src/agents-md.ts +81 -0
- package/src/api-tools.ts +389 -0
- package/src/approvals.ts +129 -0
- package/src/arrange.ts +42 -0
- package/src/catalog.ts +321 -0
- package/src/completions.ts +652 -0
- package/src/confine.ts +350 -0
- package/src/container.ts +220 -0
- package/src/context-tools.ts +435 -0
- package/src/deploy.ts +458 -0
- package/src/egress.ts +176 -0
- package/src/evals.ts +812 -0
- package/src/flow-lint.ts +215 -0
- package/src/git-hook.ts +73 -0
- package/src/git.ts +117 -0
- package/src/gitrepo.ts +391 -0
- package/src/history.ts +329 -0
- package/src/kinds.ts +507 -0
- package/src/library.ts +419 -0
- package/src/linkable.ts +123 -0
- package/src/model-probe.ts +112 -0
- package/src/notify.ts +281 -0
- package/src/oauth-presets.ts +54 -0
- package/src/okf.ts +768 -0
- package/src/openapi.ts +383 -0
- package/src/paths.ts +79 -0
- package/src/platform.ts +87 -0
- package/src/providers.ts +147 -0
- package/src/refs.ts +83 -0
- package/src/run-container.ts +883 -0
- package/src/runner.ts +3446 -0
- package/src/runtime.ts +404 -0
- package/src/script-tools.ts +383 -0
- package/src/secret-files.ts +101 -0
- package/src/secrets.ts +799 -0
- package/src/starter.ts +415 -0
- package/src/step-exec.ts +498 -0
- package/src/storage.ts +771 -0
- package/src/store.ts +2985 -0
- package/src/tar.ts +124 -0
- package/src/tool-names.ts +91 -0
- package/src/tool-programs.ts +65 -0
- package/src/tool-test.ts +401 -0
- package/src/translator.ts +1029 -0
- package/src/triggers.ts +287 -0
- package/src/webhook.ts +183 -0
- package/templates/blog-desk/AGENTS.md +13 -0
- package/templates/blog-desk/agents/researcher/agent.md +11 -0
- package/templates/blog-desk/agents/writer/agent.md +8 -0
- package/templates/blog-desk/evals/writer-quality.md +10 -0
- package/templates/blog-desk/flows/publish.md +10 -0
- package/templates/blog-desk/knowledge/house-style.md +9 -0
- package/templates/blog-desk/knowledge/index.md +9 -0
- package/templates/hello/AGENTS.md +18 -0
- package/templates/hello/agents/curator/agent.md +32 -0
- package/templates/hello/agents/notetaker/agent.md +24 -0
- package/templates/hello/evals/house-style.md +16 -0
- package/templates/hello/flows/memory-review.md +7 -0
- package/templates/hello/flows/note.md +7 -0
- package/templates/hello/knowledge/house-style.md +15 -0
- package/templates/hello/knowledge/index.md +9 -0
- package/templates/hello/skills/plain-english/SKILL.md +11 -0
- package/templates/hello/state/publishing.md +7 -0
- package/templates/hello/tools/wordcount/run.py +13 -0
- package/templates/hello/tools/wordcount/tool.md +22 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface TarEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
content: Buffer;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Every regular file in a gzipped tarball.
|
|
7
|
+
*
|
|
8
|
+
* Directories, symlinks, and pax/global headers are skipped: a workspace is
|
|
9
|
+
* files, and following a link out of the archive is exactly the thing a deploy
|
|
10
|
+
* from an untrusted repository must not do.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readTarGz(gz: Buffer): TarEntry[];
|
|
13
|
+
/**
|
|
14
|
+
* Drop the single wrapper directory an archive is rooted at.
|
|
15
|
+
*
|
|
16
|
+
* GitHub tarballs are rooted at `owner-repo-<sha>/`, which is not part of
|
|
17
|
+
* anyone's workspace. Stripped only when *every* entry shares one first
|
|
18
|
+
* segment, so an archive that is already flat is left alone.
|
|
19
|
+
*/
|
|
20
|
+
export declare function stripRoot(entries: TarEntry[]): TarEntry[];
|
|
21
|
+
/**
|
|
22
|
+
* Read a tarball as the file list a deploy ships.
|
|
23
|
+
*
|
|
24
|
+
* `subdir` picks a workspace living inside a larger repository — the common
|
|
25
|
+
* case, since a repo usually holds more than one thing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function filesFromTarball(gz: Buffer, subdir?: string): {
|
|
28
|
+
path: string;
|
|
29
|
+
content: string;
|
|
30
|
+
}[];
|
package/dist/src/tar.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Reading a .tar.gz, so a deploy can come from a GitHub tarball.
|
|
2
|
+
//
|
|
3
|
+
// `GET /repos/{owner}/{repo}/tarball/{sha}` is one HTTP call and needs no git
|
|
4
|
+
// binary, no clone, and no working copy on the server — which is the whole
|
|
5
|
+
// reason a markdown workspace can deploy from a webhook in a few hundred
|
|
6
|
+
// milliseconds. What it returns is a tar.gz, and Node has gzip but not tar.
|
|
7
|
+
//
|
|
8
|
+
// Deliberately dependency-free, for the same reason the cron parser is: tar is
|
|
9
|
+
// 512-byte blocks with an octal size field, and a reader is shorter than the
|
|
10
|
+
// argument about which library to add.
|
|
11
|
+
import zlib from "node:zlib";
|
|
12
|
+
const BLOCK = 512;
|
|
13
|
+
const str = (b, off, len) => {
|
|
14
|
+
const raw = b.subarray(off, off + len);
|
|
15
|
+
const end = raw.indexOf(0);
|
|
16
|
+
return raw.subarray(0, end === -1 ? raw.length : end).toString("utf8");
|
|
17
|
+
};
|
|
18
|
+
// Sizes are octal, space- or NUL-padded. GNU also writes base-256 for large
|
|
19
|
+
// files, flagged by the high bit — irrelevant for markdown, but a silent
|
|
20
|
+
// misparse would be worse than a clear refusal.
|
|
21
|
+
function size(b, off) {
|
|
22
|
+
if (b[off] & 0x80)
|
|
23
|
+
throw new Error("tar: base-256 size field is not supported");
|
|
24
|
+
const raw = str(b, off, 12).trim();
|
|
25
|
+
return raw ? parseInt(raw, 8) : 0;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Every regular file in a gzipped tarball.
|
|
29
|
+
*
|
|
30
|
+
* Directories, symlinks, and pax/global headers are skipped: a workspace is
|
|
31
|
+
* files, and following a link out of the archive is exactly the thing a deploy
|
|
32
|
+
* from an untrusted repository must not do.
|
|
33
|
+
*/
|
|
34
|
+
export function readTarGz(gz) {
|
|
35
|
+
const buf = zlib.gunzipSync(gz);
|
|
36
|
+
const out = [];
|
|
37
|
+
let longName = null;
|
|
38
|
+
for (let off = 0; off + BLOCK <= buf.length;) {
|
|
39
|
+
const header = buf.subarray(off, off + BLOCK);
|
|
40
|
+
// Two consecutive zero blocks end the archive; one is enough to stop.
|
|
41
|
+
if (header.every((b) => b === 0))
|
|
42
|
+
break;
|
|
43
|
+
const name = str(header, 0, 100);
|
|
44
|
+
const bytes = size(header, 124);
|
|
45
|
+
const type = String.fromCharCode(header[156]) || "0";
|
|
46
|
+
const prefix = str(header, 345, 155);
|
|
47
|
+
off += BLOCK;
|
|
48
|
+
const body = buf.subarray(off, off + bytes);
|
|
49
|
+
off += Math.ceil(bytes / BLOCK) * BLOCK;
|
|
50
|
+
// GNU long name: the next header's name lives in this entry's body.
|
|
51
|
+
if (type === "L") {
|
|
52
|
+
longName = body.toString("utf8").replace(/\0+$/, "");
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (type !== "0" && type !== "\0") {
|
|
56
|
+
longName = null;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const full = longName ?? (prefix ? `${prefix}/${name}` : name);
|
|
60
|
+
longName = null;
|
|
61
|
+
if (full)
|
|
62
|
+
out.push({ path: full, content: Buffer.from(body) });
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Drop the single wrapper directory an archive is rooted at.
|
|
68
|
+
*
|
|
69
|
+
* GitHub tarballs are rooted at `owner-repo-<sha>/`, which is not part of
|
|
70
|
+
* anyone's workspace. Stripped only when *every* entry shares one first
|
|
71
|
+
* segment, so an archive that is already flat is left alone.
|
|
72
|
+
*/
|
|
73
|
+
export function stripRoot(entries) {
|
|
74
|
+
if (entries.length === 0)
|
|
75
|
+
return entries;
|
|
76
|
+
const first = entries[0].path.split("/")[0];
|
|
77
|
+
if (!entries.every((e) => e.path.split("/")[0] === first && e.path.includes("/"))) {
|
|
78
|
+
return entries;
|
|
79
|
+
}
|
|
80
|
+
return entries.map((e) => ({ ...e, path: e.path.slice(first.length + 1) }));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Read a tarball as the file list a deploy ships.
|
|
84
|
+
*
|
|
85
|
+
* `subdir` picks a workspace living inside a larger repository — the common
|
|
86
|
+
* case, since a repo usually holds more than one thing.
|
|
87
|
+
*/
|
|
88
|
+
export function filesFromTarball(gz, subdir = "") {
|
|
89
|
+
const prefix = subdir.replace(/^\/+|\/+$/g, "");
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const entry of stripRoot(readTarGz(gz))) {
|
|
92
|
+
let rel = entry.path;
|
|
93
|
+
if (prefix) {
|
|
94
|
+
if (!rel.startsWith(`${prefix}/`))
|
|
95
|
+
continue;
|
|
96
|
+
rel = rel.slice(prefix.length + 1);
|
|
97
|
+
}
|
|
98
|
+
// A tarball is untrusted input: an entry can name anything at all, and
|
|
99
|
+
// `../` in an archive is the oldest way to write outside the destination.
|
|
100
|
+
if (rel.split("/").includes("..") || rel.startsWith("/"))
|
|
101
|
+
continue;
|
|
102
|
+
// AppleDouble sidecars. macOS `tar` writes a `._name` beside every file to
|
|
103
|
+
// carry metadata no other system wants; they are not part of anyone's
|
|
104
|
+
// workspace, and letting one through means a deploy rejected for shipping
|
|
105
|
+
// a file the author never wrote.
|
|
106
|
+
if ((rel.split("/").pop() ?? "").startsWith("._"))
|
|
107
|
+
continue;
|
|
108
|
+
out.push({ path: rel, content: entry.content.toString("utf8") });
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Ref } from "./refs.ts";
|
|
2
|
+
/** Groups: one word an author can hold in their head, expanded to SDK names. */
|
|
3
|
+
export declare const TOOL_MAP: Record<string, string[]>;
|
|
4
|
+
/** Exact SDK tool names, accepted alongside the group aliases so a Claude Code
|
|
5
|
+
* subagent's `tools: Read, Grep` works unchanged. The aliases exist because
|
|
6
|
+
* vendors rename tools; `web` survives a rename that `WebSearch` would not. */
|
|
7
|
+
export declare const BUILTIN_TOOLS: Set<string>;
|
|
8
|
+
/** The platform's own groups, served in-process rather than by the SDK. */
|
|
9
|
+
export declare const PLATFORM_GROUPS: Set<string>;
|
|
10
|
+
/** Would this name resolve to something the runtime provides? Built-ins win a
|
|
11
|
+
* clash, so a tool file of the same name is shadowed rather than granted. */
|
|
12
|
+
export declare function isRuntimeTool(name: string): boolean;
|
|
13
|
+
/** The name an entry grants. `tools:` also takes the map form for approval
|
|
14
|
+
* mode — `{Bash: ask}` — where the key is the tool and the value the mode. */
|
|
15
|
+
export declare function toolEntryName(entry: unknown): string;
|
|
16
|
+
type ToolFrontmatter = {
|
|
17
|
+
tools?: unknown;
|
|
18
|
+
use?: unknown;
|
|
19
|
+
};
|
|
20
|
+
/** Every entry of `tools:`, with whether it was written as a `[[link]]`. */
|
|
21
|
+
export declare function toolRefs(front: ToolFrontmatter): Ref[];
|
|
22
|
+
/** Is this entry one of the author's own tools? A `[[link]]` always is —
|
|
23
|
+
* the brackets say "my file", so `[[search]]` is yours even though `search`
|
|
24
|
+
* is a runtime group. A bare name is the author's only when no built-in
|
|
25
|
+
* claims it. */
|
|
26
|
+
export declare function isOwnToolRef(ref: Ref): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Every name in `tools:` that can only mean one of the author's own tools.
|
|
29
|
+
* Deduped, order preserved. Names that resolve to nothing are still
|
|
30
|
+
* returned: the caller reports them, and "you asked for a tool that isn't
|
|
31
|
+
* there" is a better error than silence.
|
|
32
|
+
*/
|
|
33
|
+
export declare function ownToolNames(front: ToolFrontmatter): string[];
|
|
34
|
+
/** What a file still says under the removed `use:` key — nothing is granted
|
|
35
|
+
* for these; they exist so the error can quote the exact `tools:` line. */
|
|
36
|
+
export declare function legacyUseNames(front: ToolFrontmatter): string[];
|
|
37
|
+
/** The one sentence every reader of a `use:` key says. */
|
|
38
|
+
export declare function legacyUseError(names: string[]): string;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The one place that knows which names are the runtime's and which are the
|
|
2
|
+
// author's.
|
|
3
|
+
//
|
|
4
|
+
// `tools:` is Claude Code's field, kept compatible, and grants anything: a
|
|
5
|
+
// built-in group, an exact SDK tool name, or a tool this workspace or account
|
|
6
|
+
// defines. There used to be a second spelling, `use:`, that meant only the
|
|
7
|
+
// author's own tools. It is gone: two keys for one grant meant two places to
|
|
8
|
+
// look for an agent's blast radius, and three readers — the container's
|
|
9
|
+
// egress flag, the library's used-by list, Tool Test's agent picker — read
|
|
10
|
+
// one and forgot the other. A file that still says `use:` gets an error
|
|
11
|
+
// naming the rewrite, and scripts/migrate-use-to-tools.mjs makes it.
|
|
12
|
+
import { refList, refNames } from "./refs.js";
|
|
13
|
+
/** Groups: one word an author can hold in their head, expanded to SDK names. */
|
|
14
|
+
export const TOOL_MAP = {
|
|
15
|
+
// `web` includes Anthropic's server-side WebSearch, the one built-in tool
|
|
16
|
+
// that runs off the box and bills per call. `fetch` is the local half only:
|
|
17
|
+
// an agent that searches through the account's own `websearch` tool and
|
|
18
|
+
// reads pages with WebFetch never leaves the box for anything but tokens.
|
|
19
|
+
web: ["WebSearch", "WebFetch"],
|
|
20
|
+
fetch: ["WebFetch"],
|
|
21
|
+
// `read` is deliberately separate from `files`: an agent that may inspect a
|
|
22
|
+
// repository but must never modify it is a real and common design.
|
|
23
|
+
read: ["Read", "Glob", "Grep"],
|
|
24
|
+
files: ["Read", "Write", "Edit", "Glob", "Grep"],
|
|
25
|
+
bash: ["Bash"],
|
|
26
|
+
};
|
|
27
|
+
/** Exact SDK tool names, accepted alongside the group aliases so a Claude Code
|
|
28
|
+
* subagent's `tools: Read, Grep` works unchanged. The aliases exist because
|
|
29
|
+
* vendors rename tools; `web` survives a rename that `WebSearch` would not. */
|
|
30
|
+
export const BUILTIN_TOOLS = new Set([
|
|
31
|
+
"Read", "Write", "Edit", "MultiEdit", "Glob", "Grep", "Bash",
|
|
32
|
+
"WebSearch", "WebFetch", "NotebookEdit", "TodoWrite",
|
|
33
|
+
]);
|
|
34
|
+
/** The platform's own groups, served in-process rather than by the SDK. */
|
|
35
|
+
export const PLATFORM_GROUPS = new Set(["search", "history", "desks"]);
|
|
36
|
+
/** Would this name resolve to something the runtime provides? Built-ins win a
|
|
37
|
+
* clash, so a tool file of the same name is shadowed rather than granted. */
|
|
38
|
+
export function isRuntimeTool(name) {
|
|
39
|
+
return Boolean(TOOL_MAP[name]) || PLATFORM_GROUPS.has(name) || BUILTIN_TOOLS.has(name);
|
|
40
|
+
}
|
|
41
|
+
/** The name an entry grants. `tools:` also takes the map form for approval
|
|
42
|
+
* mode — `{Bash: ask}` — where the key is the tool and the value the mode. */
|
|
43
|
+
export function toolEntryName(entry) {
|
|
44
|
+
return refList([entry])[0]?.name ?? "";
|
|
45
|
+
}
|
|
46
|
+
/** Every entry of `tools:`, with whether it was written as a `[[link]]`. */
|
|
47
|
+
export function toolRefs(front) {
|
|
48
|
+
return refList(front.tools);
|
|
49
|
+
}
|
|
50
|
+
/** Is this entry one of the author's own tools? A `[[link]]` always is —
|
|
51
|
+
* the brackets say "my file", so `[[search]]` is yours even though `search`
|
|
52
|
+
* is a runtime group. A bare name is the author's only when no built-in
|
|
53
|
+
* claims it. */
|
|
54
|
+
export function isOwnToolRef(ref) {
|
|
55
|
+
return ref.linked || !isRuntimeTool(ref.name);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Every name in `tools:` that can only mean one of the author's own tools.
|
|
59
|
+
* Deduped, order preserved. Names that resolve to nothing are still
|
|
60
|
+
* returned: the caller reports them, and "you asked for a tool that isn't
|
|
61
|
+
* there" is a better error than silence.
|
|
62
|
+
*/
|
|
63
|
+
export function ownToolNames(front) {
|
|
64
|
+
const out = [];
|
|
65
|
+
for (const ref of toolRefs(front)) {
|
|
66
|
+
if (isOwnToolRef(ref) && !out.includes(ref.name))
|
|
67
|
+
out.push(ref.name);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** What a file still says under the removed `use:` key — nothing is granted
|
|
72
|
+
* for these; they exist so the error can quote the exact `tools:` line. */
|
|
73
|
+
export function legacyUseNames(front) {
|
|
74
|
+
return refNames(front.use);
|
|
75
|
+
}
|
|
76
|
+
/** The one sentence every reader of a `use:` key says. */
|
|
77
|
+
export function legacyUseError(names) {
|
|
78
|
+
return `\`use:\` is no longer read — write \`tools: [${names.join(", ")}]\` instead (scripts/migrate-use-to-tools.mjs rewrites every agent). Nothing was granted for it.`;
|
|
79
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface MissingProgram {
|
|
2
|
+
/** The tool's name, as an agent would write it in `tools:`. */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Which shelf it came off — the two resolve differently. */
|
|
5
|
+
scope: "workspace" | "account";
|
|
6
|
+
/** The `run:` as the definition spells it, after folder qualification. */
|
|
7
|
+
run: string;
|
|
8
|
+
/** The absolute path that was looked for, so the message can be acted on. */
|
|
9
|
+
looked: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Every script tool an agent here could call whose program is not on disk.
|
|
13
|
+
*
|
|
14
|
+
* Both shelves, because a grant reaches both and the runtime resolves
|
|
15
|
+
* nearest-wins across them — a checker that only read the workspace would
|
|
16
|
+
* call a working account tool broken.
|
|
17
|
+
*/
|
|
18
|
+
export declare function missingToolPrograms(tenant: string, workspace: string): MissingProgram[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Does a script tool's `run:` actually point at a program?
|
|
2
|
+
//
|
|
3
|
+
// The failure this exists for is the quietest one the format has. A script
|
|
4
|
+
// tool whose `run:` resolves to nothing does not raise an error at load: the
|
|
5
|
+
// definition parses, the tool is counted, the agent is told it has the
|
|
6
|
+
// capability — and then the call fails inside a turn as a tool error the
|
|
7
|
+
// model paraphrases into something plausible. `foldrun check` counted such a
|
|
8
|
+
// tool among the workspace's tools and printed "no problems", which is the
|
|
9
|
+
// one report that makes a typo invisible.
|
|
10
|
+
//
|
|
11
|
+
// The resolution here is the runtime's, not a second copy of it. That matters
|
|
12
|
+
// more than it looks: `run:` has six accepted spellings across three scopes,
|
|
13
|
+
// and a checker that reimplemented them would eventually disagree with the
|
|
14
|
+
// runner — passing a tool that cannot run, or failing one that can.
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { workspaceDir, workspaceTools } from "./store.js";
|
|
18
|
+
import { libraryDir, libraryTools } from "./library.js";
|
|
19
|
+
import { resolveRunPath } from "./script-tools.js";
|
|
20
|
+
/**
|
|
21
|
+
* Every script tool an agent here could call whose program is not on disk.
|
|
22
|
+
*
|
|
23
|
+
* Both shelves, because a grant reaches both and the runtime resolves
|
|
24
|
+
* nearest-wins across them — a checker that only read the workspace would
|
|
25
|
+
* call a working account tool broken.
|
|
26
|
+
*/
|
|
27
|
+
export function missingToolPrograms(tenant, workspace) {
|
|
28
|
+
const dir = workspaceDir(tenant, workspace);
|
|
29
|
+
// Tools are workspace- or account-scoped, so resolve as if from an agent
|
|
30
|
+
// directory one level down — the same base the runtime and the tool tester
|
|
31
|
+
// both use.
|
|
32
|
+
const from = path.join(dir, "agents", "_probe");
|
|
33
|
+
const libScripts = libraryDir(tenant, "scripts");
|
|
34
|
+
const out = [];
|
|
35
|
+
const scan = (defs, scope) => {
|
|
36
|
+
for (const [name, def] of Object.entries(defs)) {
|
|
37
|
+
if (def.kind !== "script")
|
|
38
|
+
continue;
|
|
39
|
+
const run = String(def.spec.run ?? "");
|
|
40
|
+
// A single-file tool carries its program in the body and has no path to
|
|
41
|
+
// resolve. It is a shape we still accept, not a missing program.
|
|
42
|
+
if (!run)
|
|
43
|
+
continue;
|
|
44
|
+
const looked = resolveRunPath(from, run, libScripts);
|
|
45
|
+
if (!fs.existsSync(looked))
|
|
46
|
+
out.push({ name, scope, run, looked });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
scan(libraryTools(tenant), "account");
|
|
50
|
+
scan(workspaceTools(tenant, workspace), "workspace");
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ToolDef } from "./store.ts";
|
|
2
|
+
export interface ToolTestResult {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
transport: "http" | "script" | "mcp";
|
|
5
|
+
/** One line, safe to show next to the button. */
|
|
6
|
+
summary: string;
|
|
7
|
+
/** The response body, stdout, or the server's tool list. Truncated. */
|
|
8
|
+
detail: string;
|
|
9
|
+
ms: number;
|
|
10
|
+
/** Names only — never values. */
|
|
11
|
+
missingSecrets: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface ToolTestInput {
|
|
14
|
+
/** http: the path to try, appended to `base:`. */
|
|
15
|
+
path?: string;
|
|
16
|
+
/** script: values for the tool's declared `args:`. */
|
|
17
|
+
args?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export declare function testTool(tenant: string, workspace: string, def: ToolDef, input?: ToolTestInput): Promise<ToolTestResult>;
|