@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,259 @@
|
|
|
1
|
+
// Every change to a workspace, kept.
|
|
2
|
+
//
|
|
3
|
+
// A workspace is a few hundred kilobytes of markdown, and until now the only
|
|
4
|
+
// record of a change was the file after it. A save from the dashboard, a
|
|
5
|
+
// deploy from git, an agent writing memory — each overwrote what was there
|
|
6
|
+
// and nothing remembered the before. Two things were impossible as a result:
|
|
7
|
+
// answering "what did this look like yesterday", and attributing a change in
|
|
8
|
+
// an eval score to the change in a file that caused it.
|
|
9
|
+
//
|
|
10
|
+
// So every write records a REVISION: what changed, from what, to what, by
|
|
11
|
+
// whom, and why. A deploy from git records one revision for the whole push
|
|
12
|
+
// with the commit as its id; a dashboard save records one for the file with
|
|
13
|
+
// a local id. Observe attributes eval scores to revision ids, so the "this
|
|
14
|
+
// change made it worse" table works for a workspace that has never seen a
|
|
15
|
+
// git repository — and when one is connected later, its commits slot into
|
|
16
|
+
// the same series.
|
|
17
|
+
//
|
|
18
|
+
// No git binary, no working copy, no repository on the server. The platform
|
|
19
|
+
// image deliberately has none (deploy.ts explains why); this is a journal of
|
|
20
|
+
// content, which is all the questions above need. Connecting a repository
|
|
21
|
+
// is a separate step and pushes THIS history out, not the other way round.
|
|
22
|
+
//
|
|
23
|
+
// Storage: <data>/<tenant>/.history/<scope>/<id>.json, one file per revision,
|
|
24
|
+
// plus index.jsonl — one line each, newest last — so listing does not open
|
|
25
|
+
// every revision. Content is stored inline: the files are small, and a
|
|
26
|
+
// content-addressed store would be machinery in search of a problem.
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
import crypto from "node:crypto";
|
|
30
|
+
import { dataRoot } from "./paths.js";
|
|
31
|
+
import { commitChanges, gitAvailable, headSha, listCommits, readCommit, repoExists, } from "./gitrepo.js";
|
|
32
|
+
let readTree = () => null;
|
|
33
|
+
export function registerTreeReader(fn) {
|
|
34
|
+
const prev = readTree;
|
|
35
|
+
readTree = (tenant, scope) => fn(tenant, scope) ?? prev(tenant, scope);
|
|
36
|
+
}
|
|
37
|
+
const afterCommit = [];
|
|
38
|
+
export function registerAfterCommit(fn) {
|
|
39
|
+
afterCommit.push(fn);
|
|
40
|
+
}
|
|
41
|
+
export function notifyCommit(tenant, scope, sha) {
|
|
42
|
+
for (const fn of afterCommit) {
|
|
43
|
+
try {
|
|
44
|
+
fn(tenant, scope, sha);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// a mirror that fails must not fail the save that triggered it
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Journal or git? Git whenever the binary exists; the journal is the
|
|
52
|
+
* fallback for an install without one. An install that started on the
|
|
53
|
+
* journal keeps it readable — both are consulted on read. */
|
|
54
|
+
const useGit = () => process.env.FOLDRUN_HISTORY !== "journal" && gitAvailable();
|
|
55
|
+
// Not store's assertSafeName: store imports this module, and a cycle between
|
|
56
|
+
// the two is avoidable for one regex.
|
|
57
|
+
function assertSafeName(v, what) {
|
|
58
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._@-]{0,120}$/.test(v))
|
|
59
|
+
throw new Error(`illegal ${what}: ${v}`);
|
|
60
|
+
}
|
|
61
|
+
/** Anything over this is not a document and is recorded by name only. */
|
|
62
|
+
const MAX_INLINE = 256 * 1024;
|
|
63
|
+
const historyDir = (tenant, scope) => {
|
|
64
|
+
assertSafeName(tenant, "tenant");
|
|
65
|
+
// "@library" for the account shelf; a workspace name otherwise.
|
|
66
|
+
if (scope !== "@library")
|
|
67
|
+
assertSafeName(scope, "workspace");
|
|
68
|
+
return path.join(dataRoot(), tenant, ".history", scope);
|
|
69
|
+
};
|
|
70
|
+
/** A local revision id: sortable by time, unique enough, visibly not a sha. */
|
|
71
|
+
export function newRevisionId(now = new Date()) {
|
|
72
|
+
return `r-${now.getTime().toString(36)}-${crypto.randomBytes(3).toString("hex")}`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Record one change set. Called by the writers, never by hand.
|
|
76
|
+
*
|
|
77
|
+
* Returns null and records nothing when nothing actually changed — a save
|
|
78
|
+
* of identical content is not a revision, and a history full of "no change"
|
|
79
|
+
* entries is a history nobody reads.
|
|
80
|
+
*/
|
|
81
|
+
export function recordRevision(tenant, scope, files, meta = {}) {
|
|
82
|
+
const changed = files.filter((f) => f.before !== f.after);
|
|
83
|
+
if (changed.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
if (useGit()) {
|
|
86
|
+
// First commit ever for this scope: import the whole tree as it stands
|
|
87
|
+
// now (the writer has already written), so the repository is complete
|
|
88
|
+
// from its first commit rather than starting with one file.
|
|
89
|
+
const first = !headSha(tenant, scope);
|
|
90
|
+
// Generated bundle files (knowledge/index.md, memory/log.md) are rewritten
|
|
91
|
+
// by the same save that changed a concept, outside this call. Commit them
|
|
92
|
+
// as they now stand, or a clone's index disagrees with its own files —
|
|
93
|
+
// the exact drift the template tests exist to catch.
|
|
94
|
+
const generated = first
|
|
95
|
+
? []
|
|
96
|
+
: (readTree(tenant, scope) ?? []).filter((f) => /(^|\/)(knowledge|memory)\/(.*\/)?(index|log)\.md$/.test(f.path));
|
|
97
|
+
const toCommit = first ? (readTree(tenant, scope) ?? changed) : [...changed, ...generated];
|
|
98
|
+
const sha = commitChanges(tenant, scope, toCommit, {
|
|
99
|
+
by: meta.by,
|
|
100
|
+
// Named after what the caller changed, not the generated files that
|
|
101
|
+
// rode along — "edited knowledge/style.md", not "changed 3 files".
|
|
102
|
+
message: first ? `initial import${meta.message ? ` — ${meta.message}` : ""}` : (meta.message ?? describe(changed)),
|
|
103
|
+
});
|
|
104
|
+
if (!sha)
|
|
105
|
+
return null;
|
|
106
|
+
notifyCommit(tenant, scope, sha);
|
|
107
|
+
return {
|
|
108
|
+
id: meta.commit ?? sha,
|
|
109
|
+
at: new Date().toISOString(),
|
|
110
|
+
by: meta.by ?? "system",
|
|
111
|
+
message: meta.message ?? describe(changed),
|
|
112
|
+
commit: sha,
|
|
113
|
+
files: changed,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const dir = historyDir(tenant, scope);
|
|
117
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
118
|
+
const rev = {
|
|
119
|
+
id: meta.id ?? meta.commit ?? newRevisionId(),
|
|
120
|
+
at: new Date().toISOString(),
|
|
121
|
+
by: meta.by ?? "system",
|
|
122
|
+
message: meta.message ?? describe(changed),
|
|
123
|
+
commit: meta.commit ?? null,
|
|
124
|
+
files: changed.map((f) => ({
|
|
125
|
+
path: f.path,
|
|
126
|
+
before: clip(f.before),
|
|
127
|
+
after: clip(f.after),
|
|
128
|
+
})),
|
|
129
|
+
};
|
|
130
|
+
assertSafeName(rev.id, "revision id");
|
|
131
|
+
fs.writeFileSync(path.join(dir, `${rev.id}.json`), JSON.stringify(rev));
|
|
132
|
+
const line = {
|
|
133
|
+
id: rev.id,
|
|
134
|
+
at: rev.at,
|
|
135
|
+
by: rev.by,
|
|
136
|
+
message: rev.message,
|
|
137
|
+
commit: rev.commit,
|
|
138
|
+
paths: rev.files.map((f) => f.path),
|
|
139
|
+
};
|
|
140
|
+
fs.appendFileSync(path.join(dir, "index.jsonl"), `${JSON.stringify(line)}\n`);
|
|
141
|
+
return rev;
|
|
142
|
+
}
|
|
143
|
+
const clip = (s) => s === null ? null : s.length > MAX_INLINE ? `[${s.length} bytes — too large to keep inline]` : s;
|
|
144
|
+
/** "edited tools/x/run.py", "created 3 files", "deleted flows/old.md". */
|
|
145
|
+
function describe(files) {
|
|
146
|
+
if (files.length === 1) {
|
|
147
|
+
const f = files[0];
|
|
148
|
+
const verb = f.before === null ? "created" : f.after === null ? "deleted" : "edited";
|
|
149
|
+
return `${verb} ${f.path}`;
|
|
150
|
+
}
|
|
151
|
+
const created = files.filter((f) => f.before === null).length;
|
|
152
|
+
const deleted = files.filter((f) => f.after === null).length;
|
|
153
|
+
const edited = files.length - created - deleted;
|
|
154
|
+
return [edited && `edited ${edited}`, created && `created ${created}`, deleted && `deleted ${deleted}`]
|
|
155
|
+
.filter(Boolean)
|
|
156
|
+
.join(", ") + ` file${files.length === 1 ? "" : "s"}`;
|
|
157
|
+
}
|
|
158
|
+
/** Newest first. `forPath` narrows to revisions that touched one file. */
|
|
159
|
+
export function listRevisions(tenant, scope, opts = {}) {
|
|
160
|
+
if (useGit() && repoExists(tenant, scope)) {
|
|
161
|
+
const fromGit = listCommits(tenant, scope, opts);
|
|
162
|
+
// Anything the journal holds from before the repository existed comes
|
|
163
|
+
// after git's rows: older, and still worth showing.
|
|
164
|
+
return [...fromGit, ...journalRevisions(tenant, scope, opts)].slice(0, opts.limit ?? 200);
|
|
165
|
+
}
|
|
166
|
+
return journalRevisions(tenant, scope, opts);
|
|
167
|
+
}
|
|
168
|
+
function journalRevisions(tenant, scope, opts = {}) {
|
|
169
|
+
const file = path.join(historyDir(tenant, scope), "index.jsonl");
|
|
170
|
+
if (!fs.existsSync(file))
|
|
171
|
+
return [];
|
|
172
|
+
const all = fs
|
|
173
|
+
.readFileSync(file, "utf8")
|
|
174
|
+
.split("\n")
|
|
175
|
+
.filter(Boolean)
|
|
176
|
+
.flatMap((l) => {
|
|
177
|
+
try {
|
|
178
|
+
return [JSON.parse(l)];
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return []; // a torn line from a crash mid-append loses one entry, not the file
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.reverse();
|
|
185
|
+
const narrowed = opts.forPath ? all.filter((r) => r.paths.includes(opts.forPath)) : all;
|
|
186
|
+
return narrowed.slice(0, opts.limit ?? 200);
|
|
187
|
+
}
|
|
188
|
+
export function readRevision(tenant, scope, id, forPath) {
|
|
189
|
+
assertSafeName(id, "revision id");
|
|
190
|
+
if (useGit() && repoExists(tenant, scope) && /^[0-9a-f]{7,40}$/.test(id)) {
|
|
191
|
+
const fromGit = readCommit(tenant, scope, id, forPath);
|
|
192
|
+
if (fromGit)
|
|
193
|
+
return fromGit;
|
|
194
|
+
}
|
|
195
|
+
const file = path.join(historyDir(tenant, scope), `${id}.json`);
|
|
196
|
+
if (!fs.existsSync(file))
|
|
197
|
+
return null;
|
|
198
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Make sure a scope's repository has its initial import, so a clone made
|
|
202
|
+
* right now is complete. A no-op once any commit exists.
|
|
203
|
+
*/
|
|
204
|
+
export function ensureImported(tenant, scope) {
|
|
205
|
+
if (!useGit())
|
|
206
|
+
return null;
|
|
207
|
+
if (headSha(tenant, scope))
|
|
208
|
+
return headSha(tenant, scope);
|
|
209
|
+
const tree = readTree(tenant, scope);
|
|
210
|
+
if (!tree || tree.length === 0)
|
|
211
|
+
return null;
|
|
212
|
+
return commitChanges(tenant, scope, tree, { by: "foldrun", message: "initial import" });
|
|
213
|
+
}
|
|
214
|
+
/** The most recent revision id for a scope — what a change is attributed
|
|
215
|
+
* to when nothing more specific (a deployed commit) is on record. */
|
|
216
|
+
export function latestRevisionId(tenant, scope) {
|
|
217
|
+
return listRevisions(tenant, scope, { limit: 1 })[0]?.id ?? null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* A line diff, for showing a revision. Plain LCS — quadratic, and fine: the
|
|
221
|
+
* files are documents, not generated code, and a diff of two 300-line files
|
|
222
|
+
* is ninety thousand cells, which is nothing.
|
|
223
|
+
*/
|
|
224
|
+
export function diffLines(before, after) {
|
|
225
|
+
const a = before.split("\n");
|
|
226
|
+
const b = after.split("\n");
|
|
227
|
+
const n = a.length;
|
|
228
|
+
const m = b.length;
|
|
229
|
+
// lcs[i][j] = length of the LCS of a[i..] and b[j..]
|
|
230
|
+
const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
231
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
232
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
233
|
+
lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const out = [];
|
|
237
|
+
let i = 0;
|
|
238
|
+
let j = 0;
|
|
239
|
+
while (i < n && j < m) {
|
|
240
|
+
if (a[i] === b[j]) {
|
|
241
|
+
out.push({ kind: "same", text: a[i] });
|
|
242
|
+
i++;
|
|
243
|
+
j++;
|
|
244
|
+
}
|
|
245
|
+
else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
246
|
+
out.push({ kind: "del", text: a[i] });
|
|
247
|
+
i++;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
out.push({ kind: "add", text: b[j] });
|
|
251
|
+
j++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
while (i < n)
|
|
255
|
+
out.push({ kind: "del", text: a[i++] });
|
|
256
|
+
while (j < m)
|
|
257
|
+
out.push({ kind: "add", text: b[j++] });
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** Where a document can live. Nearest wins at read time. */
|
|
2
|
+
export type Scope = "account" | "workspace" | "agent";
|
|
3
|
+
export type Kind = "agents" | "flows" | "evals" | "tools" | "skills" | "memory" | "knowledge" | "scripts";
|
|
4
|
+
export interface KindMeta {
|
|
5
|
+
kind: Kind;
|
|
6
|
+
/** Singular noun, for buttons and prose. */
|
|
7
|
+
one: string;
|
|
8
|
+
/**
|
|
9
|
+
* The OKF concept type a new document of this kind starts with, for the two
|
|
10
|
+
* kinds that are OKF bundles. Null everywhere else: a kind is read from the
|
|
11
|
+
* path, so nothing else declares what it is.
|
|
12
|
+
*
|
|
13
|
+
* Only a starting point even where it is set — the vocabulary is open.
|
|
14
|
+
*/
|
|
15
|
+
docType: string | null;
|
|
16
|
+
/** The frontmatter key carrying `docType`. Always OKF's `type`, or null. */
|
|
17
|
+
docKey: "type" | null;
|
|
18
|
+
/** Scopes this kind can live at, nearest last. */
|
|
19
|
+
scopes: Scope[];
|
|
20
|
+
/** Shown in the name box. */
|
|
21
|
+
placeholder: string;
|
|
22
|
+
/** One line under the name box saying what you're about to make. */
|
|
23
|
+
hint: string;
|
|
24
|
+
/** Path relative to the scope root. */
|
|
25
|
+
file: (name: string) => string;
|
|
26
|
+
/** Starting content. */
|
|
27
|
+
template: (name: string, ctx?: TemplateContext) => string;
|
|
28
|
+
}
|
|
29
|
+
/** What the caller knows that the template can use. Only flows need it — a new
|
|
30
|
+
* flow that names a real agent is one edit closer to running than one that
|
|
31
|
+
* names a placeholder. */
|
|
32
|
+
export interface TemplateContext {
|
|
33
|
+
firstAgent?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const KINDS: Record<Kind, KindMeta>;
|
|
36
|
+
/**
|
|
37
|
+
* What a script tool can be written in.
|
|
38
|
+
*
|
|
39
|
+
* Three, not every language the runner can execute. The runner picks an
|
|
40
|
+
* interpreter from the file extension, so `.rb` and the rest keep working the
|
|
41
|
+
* moment someone renames a file — but a picker with nine entries is a quiz,
|
|
42
|
+
* and three covers what people actually reach for. The point of asking at all
|
|
43
|
+
* is that "script tool" silently meaning JavaScript was a thing you had to
|
|
44
|
+
* find out by reading the file it made.
|
|
45
|
+
*/
|
|
46
|
+
export declare const SCRIPT_LANGUAGES: readonly [{
|
|
47
|
+
readonly value: "javascript";
|
|
48
|
+
readonly label: "JavaScript";
|
|
49
|
+
readonly ext: ".mjs";
|
|
50
|
+
readonly hint: "node — the default";
|
|
51
|
+
}, {
|
|
52
|
+
readonly value: "python";
|
|
53
|
+
readonly label: "Python";
|
|
54
|
+
readonly ext: ".py";
|
|
55
|
+
readonly hint: "python3";
|
|
56
|
+
}, {
|
|
57
|
+
readonly value: "bash";
|
|
58
|
+
readonly label: "Shell";
|
|
59
|
+
readonly ext: ".sh";
|
|
60
|
+
readonly hint: "bash — for wrapping a command";
|
|
61
|
+
}];
|
|
62
|
+
export type ScriptLanguage = (typeof SCRIPT_LANGUAGES)[number]["value"];
|
|
63
|
+
/**
|
|
64
|
+
* A new tool, as the files it starts as.
|
|
65
|
+
*
|
|
66
|
+
* Three transports, two shapes. An API or an MCP server is a definition and
|
|
67
|
+
* nothing else, so it stays one flat file. A script tool is a definition and
|
|
68
|
+
* the code it runs, which cannot be separated without the tool silently
|
|
69
|
+
* resolving to nothing — so it starts as a folder holding both, with `run:`
|
|
70
|
+
* relative to itself and naming no scope.
|
|
71
|
+
*
|
|
72
|
+
* Returned as a list because "create a tool" is not always one write, and
|
|
73
|
+
* the callers that stamp templates should not have to know which case they
|
|
74
|
+
* are in.
|
|
75
|
+
*/
|
|
76
|
+
export declare function toolStarter(name: string, transport: "http" | "script" | "mcp", language?: ScriptLanguage): {
|
|
77
|
+
file: string;
|
|
78
|
+
content: string;
|
|
79
|
+
}[];
|
|
80
|
+
export declare const ALL_KINDS: Kind[];
|
|
81
|
+
/** The kinds that exist at a given scope. */
|
|
82
|
+
export declare const kindsAt: (scope: Scope) => Kind[];
|
|
83
|
+
/** The noun a kind declares, for asserting a file says what it is. */
|
|
84
|
+
export declare const docTypeOf: (kind: Kind) => string | null;
|
|
85
|
+
/** Which frontmatter key carries that noun — `kind:` for ours, `type:` for OKF's. */
|
|
86
|
+
export declare const docKeyOf: (kind: Kind) => "type" | null;
|
|
87
|
+
/**
|
|
88
|
+
* How a tool reaches the thing it calls.
|
|
89
|
+
*
|
|
90
|
+
* Read from `transport:`, falling back to a legacy `type: http|script|mcp`
|
|
91
|
+
* written before `type:` meant the document. Files written either way keep
|
|
92
|
+
* working; only one of them is what we generate now.
|
|
93
|
+
*/
|
|
94
|
+
export type Transport = "http" | "script" | "mcp";
|
|
95
|
+
export declare function readTransport(data: Record<string, unknown>): Transport | null;
|