@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,578 @@
|
|
|
1
|
+
// Editor completions.
|
|
2
|
+
//
|
|
3
|
+
// `[[` already suggested agents in flows. Everything else — methods, auth
|
|
4
|
+
// headers, tool names, secrets, cron — you had to remember or copy from
|
|
5
|
+
// another file. That's the same failure as an undocumented directory layout:
|
|
6
|
+
// the platform knows the answer and doesn't offer it.
|
|
7
|
+
//
|
|
8
|
+
// This is pure logic: given a path, the text, and the cursor, say what could
|
|
9
|
+
// go here. No React, no fetch — so the rules are testable and one component
|
|
10
|
+
// renders them.
|
|
11
|
+
import { PROVIDERS } from "./providers.js";
|
|
12
|
+
const MODELS = [
|
|
13
|
+
{ label: "fast", hint: "haiku — cheap, for checks and triage" },
|
|
14
|
+
{ label: "default", hint: "sonnet — the working default" },
|
|
15
|
+
{ label: "max", hint: "opus — the most capable" },
|
|
16
|
+
];
|
|
17
|
+
// Effort is the other knob, and the one people miss: it decides how long the
|
|
18
|
+
// model thinks, not which model thinks. `fast` + `max` is a real pairing —
|
|
19
|
+
// the cheap model, told to take its time — and no single tier can say it.
|
|
20
|
+
const EFFORTS = [
|
|
21
|
+
{ label: "low", hint: "minimal thinking, fastest" },
|
|
22
|
+
{ label: "medium", hint: "moderate" },
|
|
23
|
+
{ label: "high", hint: "deep reasoning — the default" },
|
|
24
|
+
{ label: "xhigh", hint: "deeper than high, where the model has it" },
|
|
25
|
+
{ label: "max", hint: "think hardest — correctness over cost" },
|
|
26
|
+
];
|
|
27
|
+
// `tools:` grants what the runtime provides. Group aliases first — they
|
|
28
|
+
// survive vendor renames — then the exact SDK names, accepted so a Claude Code
|
|
29
|
+
// subagent runs here unchanged.
|
|
30
|
+
const TOOL_GROUPS = [
|
|
31
|
+
{ label: "read", hint: "Read, Glob, Grep — may look, never write" },
|
|
32
|
+
{ label: "files", hint: "Read, Write, Edit, Glob, Grep" },
|
|
33
|
+
{ label: "web", hint: "WebSearch (Anthropic's servers, billed per search), WebFetch" },
|
|
34
|
+
{ label: "fetch", hint: "WebFetch only — local; pair with the websearch tool" },
|
|
35
|
+
{ label: "bash", hint: "Bash — arbitrary commands" },
|
|
36
|
+
];
|
|
37
|
+
const SDK_TOOL_NAMES = [
|
|
38
|
+
{ label: "Read", hint: "exact SDK name" },
|
|
39
|
+
{ label: "Write", hint: "exact SDK name" },
|
|
40
|
+
{ label: "Edit", hint: "exact SDK name" },
|
|
41
|
+
{ label: "Glob", hint: "exact SDK name" },
|
|
42
|
+
{ label: "Grep", hint: "exact SDK name" },
|
|
43
|
+
{ label: "Bash", hint: "exact SDK name" },
|
|
44
|
+
{ label: "WebSearch", hint: "exact SDK name" },
|
|
45
|
+
{ label: "WebFetch", hint: "exact SDK name" },
|
|
46
|
+
];
|
|
47
|
+
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"].map((m) => ({ label: m }));
|
|
48
|
+
const TRIGGERS = [
|
|
49
|
+
{ label: "manual", hint: "a person presses Run" },
|
|
50
|
+
{ label: "schedule", hint: "cron — needs schedule: and timezone:" },
|
|
51
|
+
{ label: "webhook", hint: "fires on POST to its hook URL" },
|
|
52
|
+
];
|
|
53
|
+
const OKF_TYPES = [
|
|
54
|
+
{ label: "Fact" },
|
|
55
|
+
{ label: "Decision" },
|
|
56
|
+
{ label: "Preference" },
|
|
57
|
+
{ label: "Reference" },
|
|
58
|
+
{ label: "Runbook" },
|
|
59
|
+
{ label: "Price List" },
|
|
60
|
+
{ label: "Policy" },
|
|
61
|
+
];
|
|
62
|
+
const TRANSPORTS = [
|
|
63
|
+
{ label: "http", hint: "an API — needs base:" },
|
|
64
|
+
{ label: "script", hint: "an executable — needs run:" },
|
|
65
|
+
{ label: "mcp", hint: "an MCP server — needs command: or url:" },
|
|
66
|
+
];
|
|
67
|
+
const CRON = [
|
|
68
|
+
{ label: "0 6 * * MON", hint: "Mondays at 6am" },
|
|
69
|
+
{ label: "0 * * * *", hint: "hourly" },
|
|
70
|
+
{ label: "0 9 * * *", hint: "daily at 9am" },
|
|
71
|
+
{ label: "*/15 * * * *", hint: "every 15 minutes" },
|
|
72
|
+
];
|
|
73
|
+
const TIMEZONES = [
|
|
74
|
+
"Australia/Sydney",
|
|
75
|
+
"Australia/Perth",
|
|
76
|
+
"UTC",
|
|
77
|
+
"Europe/London",
|
|
78
|
+
"America/New_York",
|
|
79
|
+
].map((t) => ({ label: t }));
|
|
80
|
+
// Which frontmatter fields belong to which kind of file. Offering a field is
|
|
81
|
+
// the cheapest documentation there is: you learn the format by writing it.
|
|
82
|
+
const FIELDS = {
|
|
83
|
+
agent: [
|
|
84
|
+
{ label: "name", insert: "name: ", hint: "required" },
|
|
85
|
+
{ label: "description", insert: "description: ", hint: "required — what it does, and when" },
|
|
86
|
+
{ label: "model", insert: "model: default" },
|
|
87
|
+
{ label: "effort", insert: "effort: high", hint: "how hard it thinks" },
|
|
88
|
+
{ label: "tools", insert: "tools:\n - files", hint: "what the runtime gives it" },
|
|
89
|
+
{ label: "secrets", insert: "secrets:\n - ", hint: "credentials it may spend" },
|
|
90
|
+
{ label: "skills", insert: "skills:\n - ", hint: "allowlist; omit to inherit all" },
|
|
91
|
+
{ label: "scripts", insert: "scripts:\n - name: \n run: \n description: " },
|
|
92
|
+
{ label: "runtime", insert: "runtime:\n python: \"3\"\n packages: []" },
|
|
93
|
+
{ label: "mcpServers", insert: "mcpServers:\n " },
|
|
94
|
+
{ label: "permissionMode", insert: "permissionMode: plan", hint: "plan = read-only" },
|
|
95
|
+
{ label: "disallowedTools", insert: "disallowedTools:\n - " },
|
|
96
|
+
{
|
|
97
|
+
label: "provider",
|
|
98
|
+
insert: "provider:\n name: \n token: ${PROVIDER_TOKEN}",
|
|
99
|
+
hint: "an Anthropic-compatible endpoint — omit for Anthropic",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
label: "provider.models",
|
|
103
|
+
insert: "provider:\n base_url: https://\n token: ${PROVIDER_TOKEN}\n models:\n fast: \n default: \n max: ",
|
|
104
|
+
hint: "what this gateway calls each tier",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
label: "provider.headers",
|
|
108
|
+
insert: "provider:\n base_url: https://\n token: ${PROVIDER_TOKEN}\n headers:\n X-Title: foldrun",
|
|
109
|
+
hint: "gateway-specific settings — ${SECRET} resolves server-side",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
tool: [
|
|
113
|
+
{ label: "name", insert: "name: " },
|
|
114
|
+
{ label: "description", insert: "description: " },
|
|
115
|
+
{ label: "transport", insert: "transport: http", hint: "http | script | mcp" },
|
|
116
|
+
{ label: "base", insert: "base: https://", hint: "http tools" },
|
|
117
|
+
{ label: "methods", insert: "methods: [GET]" },
|
|
118
|
+
{
|
|
119
|
+
label: "headers",
|
|
120
|
+
insert: "headers:\n Authorization: Bearer ${TOKEN_NAME}",
|
|
121
|
+
hint: "bearer token — ${SECRET} resolves server-side",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
label: "headers (api key)",
|
|
125
|
+
insert: "headers:\n X-API-Key: ${API_KEY}",
|
|
126
|
+
hint: "api key in a header",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
label: "headers (basic)",
|
|
130
|
+
insert: "headers:\n Authorization: Basic ${BASIC_CREDENTIALS}",
|
|
131
|
+
hint: "store base64(user:pass) as the secret",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
label: "query (api key)",
|
|
135
|
+
insert: "query:\n api_key: ${API_KEY}",
|
|
136
|
+
hint: "api key as a query parameter",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
label: "graphql",
|
|
140
|
+
insert: "base: https://api.example.com\nmethods: [POST]\nheaders:\n Authorization: Bearer ${TOKEN_NAME}",
|
|
141
|
+
hint: "POST the query as the body to /graphql",
|
|
142
|
+
},
|
|
143
|
+
{ label: "query", insert: "query:\n key: ${API_KEY}" },
|
|
144
|
+
{ label: "run", insert: "run: workspace/scripts/", hint: "script tools" },
|
|
145
|
+
{ label: "args", insert: "args:\n name: what it is" },
|
|
146
|
+
{ label: "command", insert: "command: npx", hint: "mcp over stdio" },
|
|
147
|
+
{ label: "url", insert: "url: https://", hint: "mcp over http" },
|
|
148
|
+
],
|
|
149
|
+
flow: [
|
|
150
|
+
{ label: "name", insert: "name: " },
|
|
151
|
+
{ label: "trigger", insert: "trigger: manual" },
|
|
152
|
+
{ label: "schedule", insert: "schedule: \"0 6 * * MON\"" },
|
|
153
|
+
{ label: "timezone", insert: "timezone: Australia/Sydney" },
|
|
154
|
+
{ label: "model", insert: "model: fast", hint: "every step, unless the step says" },
|
|
155
|
+
{ label: "effort", insert: "effort: high", hint: "every step, unless the step says" },
|
|
156
|
+
],
|
|
157
|
+
evalFile: [
|
|
158
|
+
{ label: "name", insert: "name: " },
|
|
159
|
+
{ label: "agent", insert: "agent: " },
|
|
160
|
+
{ label: "flow", insert: "flow: ", hint: "instead of agent:" },
|
|
161
|
+
{ label: "model", insert: "model: fast", hint: "the judge's model" },
|
|
162
|
+
{ label: "effort", insert: "effort: low", hint: "the judge's effort" },
|
|
163
|
+
],
|
|
164
|
+
// memory/ and knowledge/ are OKF bundles — `type` is the spec's one
|
|
165
|
+
// required field, the rest are its v0.2 provenance and lifecycle signals.
|
|
166
|
+
note: [
|
|
167
|
+
{ label: "type", insert: "type: Fact", hint: "required by OKF" },
|
|
168
|
+
// `title`, not `name`: a bundle carries the format's fields and no
|
|
169
|
+
// dialect of ours. `name` is still read from older files, never written.
|
|
170
|
+
{ label: "title", insert: "title: ", hint: "the human-readable label" },
|
|
171
|
+
{ label: "description", insert: "description: " },
|
|
172
|
+
{ label: "resource", insert: "resource: ", hint: "URI of the thing this describes" },
|
|
173
|
+
{ label: "status", insert: "status: stable", hint: "draft | stable | deprecated" },
|
|
174
|
+
{ label: "stale_after", insert: "stale_after: ", hint: "YYYY-MM-DD — stale on or after" },
|
|
175
|
+
{ label: "tags", insert: "tags: []" },
|
|
176
|
+
{
|
|
177
|
+
label: "verified",
|
|
178
|
+
insert: "verified:\n - by: human:\n at: ",
|
|
179
|
+
hint: "human: makes it human-reviewed",
|
|
180
|
+
},
|
|
181
|
+
{ label: "sources", insert: "sources:\n - resource: ", hint: "resource: is required in an entry" },
|
|
182
|
+
{
|
|
183
|
+
label: "usage_window",
|
|
184
|
+
insert: "usage_window:\n from: \n to: ",
|
|
185
|
+
hint: "frames every usage_count in sources",
|
|
186
|
+
},
|
|
187
|
+
{ label: "generated", insert: "generated:\n by: human:\n at: " },
|
|
188
|
+
],
|
|
189
|
+
skill: [
|
|
190
|
+
{ label: "name", insert: "name: " },
|
|
191
|
+
{ label: "description", insert: "description: " },
|
|
192
|
+
{ label: "when", insert: "when:\n - ", hint: "tags — load only on matching runs" },
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
export function fileKind(path) {
|
|
196
|
+
// AGENTS.md configures the whole workspace, so it takes the agent fields.
|
|
197
|
+
if (/(^|\/)AGENTS\.md$/.test(path) || path === "project.md")
|
|
198
|
+
return "agent";
|
|
199
|
+
if (/(^|\/)agent\.md$/.test(path))
|
|
200
|
+
return "agent";
|
|
201
|
+
if (/(^|\/)SKILL\.md$/.test(path) || /^skills\//.test(path))
|
|
202
|
+
return "skill";
|
|
203
|
+
if (/(^|\/)tools\//.test(path))
|
|
204
|
+
return "tool";
|
|
205
|
+
if (/^flows\//.test(path))
|
|
206
|
+
return "flow";
|
|
207
|
+
if (/^evals\//.test(path))
|
|
208
|
+
return "evalFile";
|
|
209
|
+
return "note";
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Does [[ ]] in this file get expanded into a path, or stay literal?
|
|
213
|
+
*
|
|
214
|
+
* The rule is not "which directory" but "does this text become a prompt".
|
|
215
|
+
* An agent's body, a flow step's instruction and an eval's task all end up
|
|
216
|
+
* in front of a model, which cannot open `[[house-style]]` but can open
|
|
217
|
+
* `../../knowledge/house-style.md`. A knowledge or tool document is read as
|
|
218
|
+
* a file, and its links are for the person reading it.
|
|
219
|
+
*
|
|
220
|
+
* Exported so the editor can say which case a file is in, rather than
|
|
221
|
+
* offering the same word for two different behaviours.
|
|
222
|
+
*/
|
|
223
|
+
export function resolvesLinks(path) {
|
|
224
|
+
const kind = fileKind(path);
|
|
225
|
+
return kind === "agent" || kind === "flow" || kind === "evalFile";
|
|
226
|
+
}
|
|
227
|
+
/** Assertion keywords for eval case bodies. */
|
|
228
|
+
const ASSERTIONS = [
|
|
229
|
+
{ label: "contains", hint: "text must appear" },
|
|
230
|
+
{ label: "not-contains", hint: "text must not appear" },
|
|
231
|
+
{ label: "matches", hint: "regular expression" },
|
|
232
|
+
{ label: "file", hint: "path exists and is non-empty" },
|
|
233
|
+
{ label: "run", hint: "shell command exits 0" },
|
|
234
|
+
{ label: "judge", hint: "a model grades against this sentence" },
|
|
235
|
+
];
|
|
236
|
+
const STEP_OPTIONS = [
|
|
237
|
+
{ label: "when", insert: "when: ", hint: "skip unless prior results mention it" },
|
|
238
|
+
{ label: "retry", insert: "retry: 1" },
|
|
239
|
+
{ label: "timeout", insert: "timeout: 300", hint: "seconds" },
|
|
240
|
+
{ label: "verify", insert: "verify: ", hint: "shell command, or contains:/matches:/file:/judge:" },
|
|
241
|
+
{ label: "output", insert: "output: json", hint: "the step returns a JSON value" },
|
|
242
|
+
{ label: "each", insert: "each: items", hint: "fan out: lines | items | rows of <csv>" },
|
|
243
|
+
{ label: "model", insert: "model: fast" },
|
|
244
|
+
{ label: "effort", insert: "effort: high", hint: "this step only" },
|
|
245
|
+
{ label: "approve", insert: "approve: true", hint: "pause for a human" },
|
|
246
|
+
];
|
|
247
|
+
// Header and query-parameter names, for the indented lines under `headers:`
|
|
248
|
+
// and `query:`. Each inserts a working pair, because a bare key teaches
|
|
249
|
+
// nothing about where the credential goes.
|
|
250
|
+
const HEADER_NAMES = [
|
|
251
|
+
{ label: "Authorization", insert: "Authorization: Bearer ${TOKEN_NAME}", hint: "bearer token" },
|
|
252
|
+
{ label: "X-API-Key", insert: "X-API-Key: ${API_KEY}", hint: "api key header" },
|
|
253
|
+
{ label: "X-Auth-Token", insert: "X-Auth-Token: ${TOKEN_NAME}" },
|
|
254
|
+
{ label: "api-key", insert: "api-key: ${API_KEY}", hint: "Azure, Anthropic" },
|
|
255
|
+
{ label: "x-api-key", insert: "x-api-key: ${API_KEY}", hint: "Anthropic, AWS API Gateway" },
|
|
256
|
+
{ label: "Content-Type", insert: "Content-Type: application/json" },
|
|
257
|
+
{ label: "Accept", insert: "Accept: application/json" },
|
|
258
|
+
{ label: "Accept-Language", insert: "Accept-Language: en-AU" },
|
|
259
|
+
{ label: "User-Agent", insert: "User-Agent: foldrun" },
|
|
260
|
+
{ label: "X-Api-Version", insert: "X-Api-Version: " },
|
|
261
|
+
{ label: "anthropic-version", insert: "anthropic-version: 2023-06-01" },
|
|
262
|
+
{ label: "developer-token", insert: "developer-token: ${DEVELOPER_TOKEN}", hint: "Google Ads" },
|
|
263
|
+
{ label: "login-customer-id", insert: "login-customer-id: ${ADS_CUSTOMER_ID}", hint: "Google Ads" },
|
|
264
|
+
{ label: "X-Request-Id", insert: "X-Request-Id: " },
|
|
265
|
+
{ label: "X-Correlation-Id", insert: "X-Correlation-Id: " },
|
|
266
|
+
{ label: "Idempotency-Key", insert: "Idempotency-Key: ", hint: "Stripe and similar" },
|
|
267
|
+
{ label: "Stripe-Version", insert: "Stripe-Version: " },
|
|
268
|
+
{ label: "OpenAI-Organization", insert: "OpenAI-Organization: ${OPENAI_ORG}" },
|
|
269
|
+
{ label: "X-GitHub-Api-Version", insert: "X-GitHub-Api-Version: 2022-11-28" },
|
|
270
|
+
{ label: "Cookie", insert: "Cookie: ${SESSION_COOKIE}" },
|
|
271
|
+
];
|
|
272
|
+
const QUERY_NAMES = [
|
|
273
|
+
{ label: "api_key", insert: "api_key: ${API_KEY}" },
|
|
274
|
+
{ label: "key", insert: "key: ${API_KEY}" },
|
|
275
|
+
{ label: "token", insert: "token: ${TOKEN_NAME}" },
|
|
276
|
+
];
|
|
277
|
+
// Prefix matches first, then anything containing the query — so "key" finds
|
|
278
|
+
// `X-API-Key` and "auth" finds `Authorization`, without burying exact starts.
|
|
279
|
+
const filter = (items, query) => {
|
|
280
|
+
if (!query)
|
|
281
|
+
return items.slice(0, 16);
|
|
282
|
+
const q = query.toLowerCase();
|
|
283
|
+
const starts = items.filter((i) => i.label.toLowerCase().startsWith(q));
|
|
284
|
+
const contains = items.filter((i) => !i.label.toLowerCase().startsWith(q) && i.label.toLowerCase().includes(q));
|
|
285
|
+
return [...starts, ...contains].slice(0, 16);
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* What can go at the cursor?
|
|
289
|
+
* @returns null when nothing sensible can be suggested.
|
|
290
|
+
*/
|
|
291
|
+
export function completionsAt(path, text, cursor, vocab) {
|
|
292
|
+
const before = text.slice(0, cursor);
|
|
293
|
+
const lineStart = before.lastIndexOf("\n") + 1;
|
|
294
|
+
const line = before.slice(lineStart);
|
|
295
|
+
const kind = fileKind(path);
|
|
296
|
+
// 1. [[wikilink]] — agents and flows, anywhere.
|
|
297
|
+
const open = before.lastIndexOf("[[");
|
|
298
|
+
if (open !== -1 && !before.slice(open).includes("]]") && !before.slice(open).includes("\n")) {
|
|
299
|
+
const query = before.slice(open + 2);
|
|
300
|
+
// Inside a frontmatter list that names files — `tools: [read, [[` or
|
|
301
|
+
// ` - [[` under `skills:` — the bracket means "one of mine", so offer
|
|
302
|
+
// that field's files and nothing else. `[[search]]` in tools: is how a
|
|
303
|
+
// tool named after a runtime group gets granted; offering the group
|
|
304
|
+
// here would defeat the point of the brackets.
|
|
305
|
+
const inlineOwner = line.match(/^\s*([a-zA-Z_]+):\s*(?:\[.*)?\[\[[^\]]*$/)?.[1];
|
|
306
|
+
const blockOwner = /^\s+-\s*\[\[/.test(line)
|
|
307
|
+
? before.slice(0, lineStart).split("\n").reverse().find((l) => /^\s*[a-zA-Z_]+:\s*$/.test(l))?.trim().replace(":", "")
|
|
308
|
+
: undefined;
|
|
309
|
+
const owner = inlineOwner ?? blockOwner;
|
|
310
|
+
const byField = {
|
|
311
|
+
tools: vocab.tools.map((t) => ({ label: t, hint: "your tool" })),
|
|
312
|
+
skills: vocab.skills.map((sk) => ({ label: sk, hint: "skill" })),
|
|
313
|
+
agents: vocab.agents.map((a) => ({ label: a, hint: "agent" })),
|
|
314
|
+
delegate: vocab.agents.map((a) => ({ label: a, hint: "agent" })),
|
|
315
|
+
after: vocab.flows.map((f) => ({ label: `flow:${f}`, hint: "flow" })),
|
|
316
|
+
};
|
|
317
|
+
if (owner && byField[owner]) {
|
|
318
|
+
return {
|
|
319
|
+
from: open + 2,
|
|
320
|
+
query,
|
|
321
|
+
items: filter(byField[owner], query).map((i) => ({ ...i, insert: `${i.label}]]` })),
|
|
322
|
+
title: `${owner} — a link names one of your files; a bare name may be a built-in`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const items = [
|
|
326
|
+
...vocab.agents.map((a) => ({ label: a, hint: "agent" })),
|
|
327
|
+
...vocab.flows.map((f) => ({ label: `flow:${f}`, hint: "flow" })),
|
|
328
|
+
// Knowledge, memory and files join the same bracket: in a flow the
|
|
329
|
+
// first link on a step line stays structural (the parser only reads
|
|
330
|
+
// that one), and everywhere else the runner resolves these to the
|
|
331
|
+
// real relative path before the model sees the prompt.
|
|
332
|
+
...(vocab.docs ?? []).map((d) => ({ label: d.name, hint: d.hint })),
|
|
333
|
+
];
|
|
334
|
+
// The bracket is offered everywhere, because a link between documents is
|
|
335
|
+
// worth writing whether or not a model ever expands it. But only text
|
|
336
|
+
// that BECOMES A PROMPT is expanded — an agent's body, a step's
|
|
337
|
+
// instruction, an eval's task, and what a person types at a gate. In a
|
|
338
|
+
// knowledge or tool document the link stays literal, which is the right
|
|
339
|
+
// behaviour and the wrong thing to leave unsaid: the list used to say
|
|
340
|
+
// "Link" in both cases and let people infer the difference.
|
|
341
|
+
return {
|
|
342
|
+
from: open + 2,
|
|
343
|
+
query,
|
|
344
|
+
items: filter(items, query),
|
|
345
|
+
title: resolvesLinks(path)
|
|
346
|
+
? "Link — expanded to a real path before the model sees it"
|
|
347
|
+
: "Link — literal here; expanded only where text becomes a prompt",
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
// 2. ${SECRET} — inside a header, env or query value.
|
|
351
|
+
const dollar = before.lastIndexOf("${");
|
|
352
|
+
if (dollar !== -1 && !before.slice(dollar).includes("}") && !before.slice(dollar).includes("\n")) {
|
|
353
|
+
const query = before.slice(dollar + 2);
|
|
354
|
+
return {
|
|
355
|
+
from: dollar + 2,
|
|
356
|
+
query,
|
|
357
|
+
items: filter(vocab.secrets.map((s) => ({ label: s, insert: `${s}}`, hint: "secret" })), query),
|
|
358
|
+
title: "Secret — resolved server-side, never shown to the model",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// 3. Inside an unclosed inline list — `methods: [GET, ` — offer what hasn't
|
|
362
|
+
// been chosen yet, one at a time. Fixed combinations were the wrong
|
|
363
|
+
// model: nobody wants a menu of every permutation, they want to add the
|
|
364
|
+
// next verb.
|
|
365
|
+
const inlineList = line.match(/^(\s*)([a-zA-Z_]+):\s*\[([^\]]*)$/);
|
|
366
|
+
if (inlineList) {
|
|
367
|
+
const [, , key, inside] = inlineList;
|
|
368
|
+
// The inline and block forms of a list are the same field — `tools: [a, b]`
|
|
369
|
+
// and `tools:\n - a` must complete identically, or the shorthand people
|
|
370
|
+
// actually write is the one the editor abandons.
|
|
371
|
+
const options = {
|
|
372
|
+
methods: METHODS,
|
|
373
|
+
tags: [],
|
|
374
|
+
tools: [...TOOL_GROUPS, ...vocab.tools.map((t) => ({ label: t, hint: "your tool" })), ...SDK_TOOL_NAMES],
|
|
375
|
+
secrets: vocab.secrets.map((se) => ({ label: se, hint: "secret" })),
|
|
376
|
+
skills: vocab.skills.map((sk) => ({ label: sk, hint: "skill" })),
|
|
377
|
+
agents: vocab.agents.map((a) => ({ label: a, hint: "consult mid-run" })),
|
|
378
|
+
delegate: vocab.agents.map((a) => ({ label: a, hint: "agent" })),
|
|
379
|
+
};
|
|
380
|
+
const all = options[key];
|
|
381
|
+
if (all) {
|
|
382
|
+
const lastComma = inside.lastIndexOf(",");
|
|
383
|
+
const partial = inside.slice(lastComma + 1).trim();
|
|
384
|
+
const chosen = inside
|
|
385
|
+
.slice(0, lastComma + 1)
|
|
386
|
+
.split(",")
|
|
387
|
+
.map((s) => s.trim().toUpperCase())
|
|
388
|
+
.filter(Boolean);
|
|
389
|
+
const remaining = all.filter((m) => !chosen.includes(m.label.toUpperCase()));
|
|
390
|
+
const items = filter(remaining, partial).map((m) => ({
|
|
391
|
+
...m,
|
|
392
|
+
// Close the list when it's the last one left, so the common case is
|
|
393
|
+
// one keystroke rather than two.
|
|
394
|
+
insert: remaining.length === 1 ? `${m.label}]` : m.label,
|
|
395
|
+
}));
|
|
396
|
+
if (items.length) {
|
|
397
|
+
return {
|
|
398
|
+
from: cursor - partial.length,
|
|
399
|
+
query: partial,
|
|
400
|
+
items,
|
|
401
|
+
title: chosen.length
|
|
402
|
+
? `${key} — ${chosen.join(", ")} chosen; add another or type ]`
|
|
403
|
+
: `${key} — pick one, then comma for the next`,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// 4. `key: value` — the value side of a known field.
|
|
409
|
+
const kv = line.match(/^(\s*)([a-zA-Z_]+):\s*(\S*)$/);
|
|
410
|
+
if (kv) {
|
|
411
|
+
const [, , key, partial] = kv;
|
|
412
|
+
const from = cursor - partial.length;
|
|
413
|
+
const byKey = {
|
|
414
|
+
model: [...MODELS, ...(vocab.models ?? []).map((m) => ({ label: m.id, hint: m.hint }))],
|
|
415
|
+
effort: EFFORTS,
|
|
416
|
+
// `type:` is OKF's field and only OKF's — an open vocabulary describing
|
|
417
|
+
// what a piece of knowledge is about, on knowledge and memory files.
|
|
418
|
+
// Nothing else declares what it is: an agent or a flow is identified by
|
|
419
|
+
// the directory it sits in. Tools say how they connect in `transport:`,
|
|
420
|
+
// which is a different question again.
|
|
421
|
+
//
|
|
422
|
+
// Types already in this workspace first — they're the ones that keep the
|
|
423
|
+
// index from splitting into near-duplicate headings — then our defaults.
|
|
424
|
+
type: [
|
|
425
|
+
...vocab.types.map((x) => ({ label: x, hint: "used here" })),
|
|
426
|
+
...OKF_TYPES.filter((d) => !vocab.types.includes(d.label)),
|
|
427
|
+
],
|
|
428
|
+
transport: TRANSPORTS,
|
|
429
|
+
trigger: TRIGGERS,
|
|
430
|
+
schedule: CRON,
|
|
431
|
+
timezone: TIMEZONES,
|
|
432
|
+
agent: vocab.agents.map((a) => ({ label: a })),
|
|
433
|
+
flow: vocab.flows.map((f) => ({ label: f })),
|
|
434
|
+
run: vocab.scripts.map((s) => ({ label: s, hint: "script" })),
|
|
435
|
+
// Before the bracket is open: a starting point. Once it's open, rule 3
|
|
436
|
+
// takes over and adds verbs one at a time.
|
|
437
|
+
methods: [
|
|
438
|
+
{ label: "[GET]", hint: "read-only — the default" },
|
|
439
|
+
{ label: "[", hint: "build the list yourself" },
|
|
440
|
+
{ label: "[GET, POST]" },
|
|
441
|
+
{ label: "[POST]", hint: "GraphQL and RPC-style APIs" },
|
|
442
|
+
{ label: "[GET, POST, PUT, PATCH, DELETE]", hint: "full CRUD" },
|
|
443
|
+
],
|
|
444
|
+
status: [
|
|
445
|
+
{ label: "stable", hint: "the default" },
|
|
446
|
+
{ label: "draft", hint: "not ready to rely on" },
|
|
447
|
+
{ label: "deprecated", hint: "kept for history" },
|
|
448
|
+
],
|
|
449
|
+
verify: [
|
|
450
|
+
{ label: "test -s outputs/report.md", hint: "a file was produced" },
|
|
451
|
+
{ label: "npm run build" },
|
|
452
|
+
{ label: "contains: ", hint: "the reply must mention this" },
|
|
453
|
+
{ label: "not-contains: ", hint: "the reply must not mention this" },
|
|
454
|
+
{ label: "matches: ", hint: "regular expression over the reply" },
|
|
455
|
+
{ label: "file: outputs/report.md", hint: "exists and is non-empty" },
|
|
456
|
+
{ label: "judge: ", hint: "a fast model grades the reply against this sentence" },
|
|
457
|
+
],
|
|
458
|
+
output: [{ label: "json", hint: "the reply ends with one ```json block" }],
|
|
459
|
+
name: PROVIDERS.map((p) => ({ label: p.name, hint: `${p.title} — ${p.format}${p.baseUrl ? "" : ", needs base_url"}` })),
|
|
460
|
+
format: [
|
|
461
|
+
{ label: "anthropic", hint: "spoken to directly (the default)" },
|
|
462
|
+
{ label: "openai", hint: "Chat Completions — through the runtime's translator" },
|
|
463
|
+
{ label: "responses", hint: "OpenAI Responses — the translator's second shape; PDFs cross" },
|
|
464
|
+
],
|
|
465
|
+
auth: [
|
|
466
|
+
{ label: "bearer", hint: "authorization: Bearer (the default)" },
|
|
467
|
+
{ label: "x-api-key", hint: "Anthropic, DeepSeek, Ollama, Cloudflare" },
|
|
468
|
+
],
|
|
469
|
+
params: [
|
|
470
|
+
{ label: "temperature", insert: "temperature: 0.2" },
|
|
471
|
+
{ label: "seed", insert: "seed: 7", hint: "repeatable sampling, where supported" },
|
|
472
|
+
{ label: "top_p", insert: "top_p: null", hint: "null removes it from the request" },
|
|
473
|
+
{ label: "response_format", insert: "response_format:\n type: json_object" },
|
|
474
|
+
],
|
|
475
|
+
each: [
|
|
476
|
+
{ label: "lines", hint: "one instance per line of the previous result" },
|
|
477
|
+
{ label: "items", hint: "one per element of the previous output: json array" },
|
|
478
|
+
{ label: "rows of ../../storage/x.csv", hint: "one per CSV row" },
|
|
479
|
+
],
|
|
480
|
+
};
|
|
481
|
+
const items = byKey[key];
|
|
482
|
+
if (items)
|
|
483
|
+
return { from, query: partial, items: filter(items, partial), title: key };
|
|
484
|
+
}
|
|
485
|
+
// 4. `- item` under a list field — look upward for the field name.
|
|
486
|
+
const listItem = line.match(/^(\s+)-\s*(\S*)$/);
|
|
487
|
+
if (listItem) {
|
|
488
|
+
const partial = listItem[2];
|
|
489
|
+
const from = cursor - partial.length;
|
|
490
|
+
const priorLines = before.slice(0, lineStart).split("\n").reverse();
|
|
491
|
+
const owner = priorLines.find((l) => /^\s*[a-zA-Z_]+:\s*$/.test(l))?.trim().replace(":", "");
|
|
492
|
+
const byOwner = {
|
|
493
|
+
// One list now: built-ins and your own tools are granted the same way.
|
|
494
|
+
// Groups first (they cover most needs), then your own tools — the ones
|
|
495
|
+
// you can't guess — then the exact SDK names.
|
|
496
|
+
tools: [
|
|
497
|
+
...TOOL_GROUPS,
|
|
498
|
+
...vocab.tools.map((t) => ({ label: t, hint: "your tool" })),
|
|
499
|
+
...SDK_TOOL_NAMES,
|
|
500
|
+
],
|
|
501
|
+
secrets: vocab.secrets.map((s) => ({ label: s, hint: "secret" })),
|
|
502
|
+
skills: vocab.skills.map((s) => ({ label: s, hint: "skill" })),
|
|
503
|
+
disallowedTools: [...TOOL_GROUPS, { label: "Bash" }, { label: "Write" }],
|
|
504
|
+
when: [{ label: "schema" }, { label: "urgent" }],
|
|
505
|
+
expect: ASSERTIONS.map((a) => ({ ...a, insert: `${a.label}: ` })),
|
|
506
|
+
};
|
|
507
|
+
const items = owner ? byOwner[owner] : undefined;
|
|
508
|
+
if (items) {
|
|
509
|
+
const titles = {
|
|
510
|
+
tools: "tools — built-ins and your own; anything here is granted",
|
|
511
|
+
secrets: "secrets — resolved server-side, never shown to the model",
|
|
512
|
+
skills: "skills — omit the list entirely to inherit all of them",
|
|
513
|
+
};
|
|
514
|
+
return {
|
|
515
|
+
from,
|
|
516
|
+
query: partial,
|
|
517
|
+
items: filter(items, partial),
|
|
518
|
+
title: titles[owner] ?? owner,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// 5. An indented mapping line under headers:, query: or env: — the credential
|
|
523
|
+
// goes in the value, so suggest the whole pair rather than the key alone.
|
|
524
|
+
const mapping = line.match(/^(\s+)([A-Za-z][A-Za-z0-9-]*)?$/);
|
|
525
|
+
if (mapping && kind !== "flow") {
|
|
526
|
+
const partial = mapping[2] ?? "";
|
|
527
|
+
const priorLines = before.slice(0, lineStart).split("\n").reverse();
|
|
528
|
+
const owner = priorLines
|
|
529
|
+
.find((l) => /^\s*[a-zA-Z_]+:\s*$/.test(l))
|
|
530
|
+
?.trim()
|
|
531
|
+
.replace(":", "");
|
|
532
|
+
const byOwner = {
|
|
533
|
+
headers: HEADER_NAMES,
|
|
534
|
+
query: QUERY_NAMES,
|
|
535
|
+
env: QUERY_NAMES,
|
|
536
|
+
};
|
|
537
|
+
const items = owner ? byOwner[owner] : undefined;
|
|
538
|
+
if (items) {
|
|
539
|
+
const matched = filter(items, partial);
|
|
540
|
+
// A header we don't know is still a header. Offer the pair shape for
|
|
541
|
+
// whatever was typed, so an unrecognised name never means no help.
|
|
542
|
+
const custom = partial.length >= 2 && !matched.some((m) => m.label.toLowerCase() === partial.toLowerCase())
|
|
543
|
+
? [
|
|
544
|
+
{
|
|
545
|
+
label: partial,
|
|
546
|
+
insert: `${partial}: \${SECRET_NAME}`,
|
|
547
|
+
hint: "custom — value takes a ${SECRET}",
|
|
548
|
+
},
|
|
549
|
+
]
|
|
550
|
+
: [];
|
|
551
|
+
return {
|
|
552
|
+
from: cursor - partial.length,
|
|
553
|
+
query: partial,
|
|
554
|
+
items: [...matched, ...custom],
|
|
555
|
+
title: `${owner} — the value is where the credential goes`,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// 6. An indented option under a flow step.
|
|
560
|
+
if (kind === "flow" && /^\s+[a-z]*$/.test(line) && line.trim().length >= 0) {
|
|
561
|
+
const partial = line.trim();
|
|
562
|
+
return {
|
|
563
|
+
from: cursor - partial.length,
|
|
564
|
+
query: partial,
|
|
565
|
+
items: filter(STEP_OPTIONS, partial),
|
|
566
|
+
title: "Step option",
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
// 7. A bare word at the start of a frontmatter line — offer the fields for
|
|
570
|
+
// this kind of file. This is where you learn the format by writing it.
|
|
571
|
+
const fmEnd = text.indexOf("\n---", text.indexOf("---") + 3);
|
|
572
|
+
const inFrontmatter = text.startsWith("---") && (fmEnd === -1 || cursor <= fmEnd);
|
|
573
|
+
if (inFrontmatter && /^[a-zA-Z_]*$/.test(line)) {
|
|
574
|
+
const from = cursor - line.length;
|
|
575
|
+
return { from, query: line, items: filter(FIELDS[kind], line), title: `${kind} fields` };
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ConfineVerdict {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
reason?: string;
|
|
4
|
+
/** Rewritten tool input, when a virtual prefix was expanded. */
|
|
5
|
+
updatedInput?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface Roots {
|
|
8
|
+
/** The agent's own directory — relative paths resolve from here. */
|
|
9
|
+
agentDir: string;
|
|
10
|
+
/** The workspace: the read/write boundary. */
|
|
11
|
+
workspaceRoot: string;
|
|
12
|
+
/** The account library: readable, never writable. */
|
|
13
|
+
libraryRoot: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const isFilesystemTool: (toolName: string) => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The path the agent probably meant. Most escapes are one of three guesses —
|
|
18
|
+
* an invented `/tmp/outputs/…`, an absolute spelling of a workspace
|
|
19
|
+
* directory, or a directory a tool made outside the workspace — and a
|
|
20
|
+
* refusal that names the correction is taken on the next turn, where a
|
|
21
|
+
* generic one is followed by two more wrong guesses. Nothing here is
|
|
22
|
+
* enforced; it is advice in the error text.
|
|
23
|
+
*/
|
|
24
|
+
export declare function suggestPath(raw: string): string;
|
|
25
|
+
/** Expand a virtual prefix to a real absolute path, or null if it has none. */
|
|
26
|
+
export declare function expandVirtual(raw: string, roots: Roots): {
|
|
27
|
+
abs: string;
|
|
28
|
+
readOnly: boolean;
|
|
29
|
+
} | null;
|
|
30
|
+
export declare function checkPaths(toolName: string, input: Record<string, unknown>, roots: Roots): ConfineVerdict;
|
|
31
|
+
export declare function checkBash(command: string): ConfineVerdict;
|