@foldrun/core 1.4.2 → 2.1.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/dist/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/src/catalog.d.ts +7 -0
- package/dist/src/catalog.js +16 -1
- package/dist/src/deploy.d.ts +6 -27
- package/dist/src/deploy.js +14 -56
- package/dist/src/history.d.ts +30 -10
- package/dist/src/history.js +232 -99
- package/dist/src/library.d.ts +0 -13
- package/dist/src/library.js +0 -50
- package/dist/src/model-credential.js +1 -1
- package/dist/src/model-env.js +1 -1
- package/dist/src/platform.d.ts +1 -3
- package/dist/src/platform.js +0 -1
- package/dist/src/run-container.d.ts +31 -5
- package/dist/src/run-container.js +71 -3
- package/dist/src/runner.d.ts +2 -0
- package/dist/src/runner.js +20 -12
- package/dist/src/secrets.js +1 -12
- package/dist/src/starter.js +2 -2
- package/dist/src/step-exec.d.ts +23 -5
- package/dist/src/step-exec.js +30 -23
- package/dist/src/store.d.ts +61 -22
- package/dist/src/store.js +105 -25
- package/dist/src/tool-loop.d.ts +4 -1
- package/dist/src/tool-loop.js +2 -0
- package/dist/src/translator.d.ts +12 -0
- package/dist/src/translator.js +36 -13
- package/index.ts +0 -3
- package/package.json +1 -3
- package/src/catalog.ts +18 -2
- package/src/deploy.ts +13 -74
- package/src/history.ts +230 -108
- package/src/library.ts +0 -44
- package/src/model-credential.ts +1 -1
- package/src/model-env.ts +1 -1
- package/src/platform.ts +1 -4
- package/src/run-container.ts +78 -5
- package/src/runner.ts +22 -12
- package/src/secrets.ts +1 -11
- package/src/starter.ts +2 -2
- package/src/step-exec.ts +44 -27
- package/src/store.ts +145 -37
- package/src/tool-loop.ts +6 -1
- package/src/translator.ts +39 -15
- package/dist/src/git-hook.d.ts +0 -1
- package/dist/src/git-hook.js +0 -70
- package/dist/src/git.d.ts +0 -37
- package/dist/src/git.js +0 -101
- package/dist/src/gitrepo.d.ts +0 -113
- package/dist/src/gitrepo.js +0 -404
- package/dist/src/tar.d.ts +0 -30
- package/dist/src/tar.js +0 -112
- package/src/git-hook.ts +0 -73
- package/src/git.ts +0 -117
- package/src/gitrepo.ts +0 -439
- package/src/tar.ts +0 -125
package/dist/index.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ export * from "./src/tool-names.ts";
|
|
|
24
24
|
export * from "./src/history.ts";
|
|
25
25
|
export * from "./src/zip.ts";
|
|
26
26
|
export * from "./src/package.ts";
|
|
27
|
-
export * from "./src/gitrepo.ts";
|
|
28
27
|
export * from "./src/storage.ts";
|
|
29
28
|
export * from "./src/secrets.ts";
|
|
30
29
|
export * from "./src/oauth-presets.ts";
|
|
@@ -35,5 +34,3 @@ export * from "./src/approvals.ts";
|
|
|
35
34
|
export * from "./src/deploy.ts";
|
|
36
35
|
export * from "./src/agent-import.ts";
|
|
37
36
|
export * from "./src/account.ts";
|
|
38
|
-
export * from "./src/tar.ts";
|
|
39
|
-
export * from "./src/git.ts";
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,6 @@ export * from "./src/tool-names.js";
|
|
|
35
35
|
export * from "./src/history.js";
|
|
36
36
|
export * from "./src/zip.js";
|
|
37
37
|
export * from "./src/package.js";
|
|
38
|
-
export * from "./src/gitrepo.js";
|
|
39
38
|
export * from "./src/storage.js";
|
|
40
39
|
export * from "./src/secrets.js";
|
|
41
40
|
export * from "./src/oauth-presets.js";
|
|
@@ -46,5 +45,3 @@ export * from "./src/approvals.js";
|
|
|
46
45
|
export * from "./src/deploy.js";
|
|
47
46
|
export * from "./src/agent-import.js";
|
|
48
47
|
export * from "./src/account.js";
|
|
49
|
-
export * from "./src/tar.js";
|
|
50
|
-
export * from "./src/git.js";
|
package/dist/src/catalog.d.ts
CHANGED
|
@@ -83,7 +83,14 @@ export declare function clampEffort(catalog: Catalog | null, modelId: string, ef
|
|
|
83
83
|
export declare function catalogCost(catalog: Catalog | null, modelId: string, usage: {
|
|
84
84
|
inputTokens: number;
|
|
85
85
|
outputTokens: number;
|
|
86
|
+
cacheReadTokens?: number;
|
|
87
|
+
cacheWriteTokens?: number;
|
|
86
88
|
}): number | null;
|
|
89
|
+
/** Anthropic's cache multipliers over the input price: a write costs a
|
|
90
|
+
* quarter more than fresh input, a read a tenth of it. One source for the
|
|
91
|
+
* mid-step ceiling (priceTurn) and the record's repricing (catalogCost). */
|
|
92
|
+
export declare const CACHE_WRITE_MULTIPLIER = 1.25;
|
|
93
|
+
export declare const CACHE_READ_MULTIPLIER = 0.1;
|
|
87
94
|
/** Catalogue entries as completion items: price per MTok, context, and the
|
|
88
95
|
* one capability that decides agent-worthiness. Tool-capable models first —
|
|
89
96
|
* on an agent platform they are almost always what the author wants. */
|
package/dist/src/catalog.js
CHANGED
|
@@ -200,8 +200,23 @@ export function catalogCost(catalog, modelId, usage) {
|
|
|
200
200
|
const m = findModel(catalog, modelId);
|
|
201
201
|
if (!m || m.promptPrice == null || m.completionPrice == null)
|
|
202
202
|
return null;
|
|
203
|
-
|
|
203
|
+
// A catalogue quotes one input price; cache traffic is priced off it with
|
|
204
|
+
// the multipliers every gateway that bills for caching has copied from
|
|
205
|
+
// Anthropic. Before the counts were split (18 Oct 2026) every cached
|
|
206
|
+
// token was billed here at the full rate — a long cached step cost ten
|
|
207
|
+
// times its price on the ledger.
|
|
208
|
+
// inputTokens is all input; the cache fields are parts of it.
|
|
209
|
+
const read = usage.cacheReadTokens ?? 0;
|
|
210
|
+
const written = usage.cacheWriteTokens ?? 0;
|
|
211
|
+
const fresh = Math.max(0, usage.inputTokens - read - written);
|
|
212
|
+
const input = fresh + written * CACHE_WRITE_MULTIPLIER + read * CACHE_READ_MULTIPLIER;
|
|
213
|
+
return input * m.promptPrice + usage.outputTokens * m.completionPrice;
|
|
204
214
|
}
|
|
215
|
+
/** Anthropic's cache multipliers over the input price: a write costs a
|
|
216
|
+
* quarter more than fresh input, a read a tenth of it. One source for the
|
|
217
|
+
* mid-step ceiling (priceTurn) and the record's repricing (catalogCost). */
|
|
218
|
+
export const CACHE_WRITE_MULTIPLIER = 1.25;
|
|
219
|
+
export const CACHE_READ_MULTIPLIER = 0.1;
|
|
205
220
|
/** Catalogue entries as completion items: price per MTok, context, and the
|
|
206
221
|
* one capability that decides agent-worthiness. Tool-capable models first —
|
|
207
222
|
* on an agent platform they are almost always what the author wants. */
|
package/dist/src/deploy.d.ts
CHANGED
|
@@ -97,7 +97,9 @@ export declare function deployIssues(files: DeployFile[]): DeployIssue[];
|
|
|
97
97
|
*/
|
|
98
98
|
export declare function runsInFlight(tenant: string, workspace: string): string[];
|
|
99
99
|
/** What a deploy would do, without doing it. */
|
|
100
|
-
export declare function planDeploy(tenant: string, workspace: string, files: DeployFile[]
|
|
100
|
+
export declare function planDeploy(tenant: string, workspace: string, files: DeployFile[], opts?: {
|
|
101
|
+
keepUntracked?: boolean;
|
|
102
|
+
}): DeployPlan;
|
|
101
103
|
export interface DeployOptions {
|
|
102
104
|
/** The commit these files came from, recorded so the dashboard can show it. */
|
|
103
105
|
commit?: string | null;
|
|
@@ -115,6 +117,9 @@ export interface DeployOptions {
|
|
|
115
117
|
* no window between the check and the swap. Absent: no check, as before.
|
|
116
118
|
*/
|
|
117
119
|
expectRemoved?: string[];
|
|
120
|
+
/** Keep what is not editable source (planDeploy, saveWorkspace): a
|
|
121
|
+
* restore takes history's tree, and history tracks source only. */
|
|
122
|
+
keepUntracked?: boolean;
|
|
118
123
|
}
|
|
119
124
|
/**
|
|
120
125
|
* Deploy a file list to a workspace, if it checks out.
|
|
@@ -124,32 +129,6 @@ export interface DeployOptions {
|
|
|
124
129
|
* webhook or a CLI that needs to report *why*, and every reason is already in
|
|
125
130
|
* the plan.
|
|
126
131
|
*/
|
|
127
|
-
/** What a push recorded about itself, where the Repository page reads it. */
|
|
128
|
-
export interface PushNote {
|
|
129
|
-
commit: string;
|
|
130
|
-
at: string;
|
|
131
|
-
applied: boolean;
|
|
132
|
-
issues?: unknown[];
|
|
133
|
-
blockedBy?: string[];
|
|
134
|
-
/** Set when a blocked push was applied later, by the retry. */
|
|
135
|
-
appliedAt?: string;
|
|
136
|
-
}
|
|
137
|
-
export declare const pushNotePath: (tenant: string, workspace: string) => string;
|
|
138
|
-
export declare function readPushNote(tenant: string, workspace: string): PushNote | null;
|
|
139
|
-
export declare function writePushNote(tenant: string, workspace: string, note: PushNote): void;
|
|
140
|
-
/**
|
|
141
|
-
* A push whose deploy was refused only because a run was in flight is not a
|
|
142
|
-
* failure — it is a deploy that has not happened yet. Git already said the
|
|
143
|
-
* push succeeded (the deploy happens after the client is gone), so nothing
|
|
144
|
-
* would ever tell anyone the workspace on disk is behind `main`. This applies
|
|
145
|
-
* it once the runs are done.
|
|
146
|
-
*
|
|
147
|
-
* Only `blockedBy`: a push refused for `issues` is a broken workspace and
|
|
148
|
-
* wants a person, not a retry.
|
|
149
|
-
*/
|
|
150
|
-
export declare function applyPendingPush(tenant: string, workspace: string, filesAt: (tenant: string, scope: string, ref: string) => DeployFile[]): {
|
|
151
|
-
commit: string;
|
|
152
|
-
} | null;
|
|
153
132
|
export declare function deployWorkspace(tenant: string, workspace: string, files: DeployFile[], opts?: DeployOptions): DeployResult;
|
|
154
133
|
export declare function writeDeployedCommit(tenant: string, workspace: string, commit: string): void;
|
|
155
134
|
export declare function deployedCommit(tenant: string, workspace: string): {
|
package/dist/src/deploy.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import fs from "node:fs";
|
|
21
21
|
import os from "node:os";
|
|
22
22
|
import path from "node:path";
|
|
23
|
-
import { saveWorkspace, keptOnDeploy, workspaceDir,
|
|
23
|
+
import { saveWorkspace, keptOnDeploy, workspaceDir, listRunSummaries, listWorkspaceFiles, parseFlow, parseToolDef, WORKSPACE_DIRS, assertSafeName, } from "./store.js";
|
|
24
24
|
import { lintFlow } from "./flow-lint.js";
|
|
25
25
|
import { refNames } from "./refs.js";
|
|
26
26
|
import matter from "gray-matter";
|
|
@@ -32,7 +32,6 @@ import { networkProblems } from "./network.js";
|
|
|
32
32
|
import { actionProblems } from "./web-actions.js";
|
|
33
33
|
import { languageProblem } from "./language.js";
|
|
34
34
|
import { localeProblems } from "./locale.js";
|
|
35
|
-
import { repoDir } from "./gitrepo.js";
|
|
36
35
|
import { entriesNoFollow } from "./paths.js";
|
|
37
36
|
import { filterIgnored, findingFingerprint, ruleForWhere, scanFiles } from "./scan.js";
|
|
38
37
|
/** Directories a deploy never reads out of a source tree. */
|
|
@@ -434,12 +433,12 @@ export function deployIssues(files) {
|
|
|
434
433
|
export function runsInFlight(tenant, workspace) {
|
|
435
434
|
if (!fs.existsSync(workspaceDir(tenant, workspace)))
|
|
436
435
|
return [];
|
|
437
|
-
return
|
|
436
|
+
return listRunSummaries(tenant, workspace)
|
|
438
437
|
.filter((r) => r.status === "queued" || r.status === "running" || r.status === "awaiting-approval")
|
|
439
438
|
.map((r) => r.id);
|
|
440
439
|
}
|
|
441
440
|
/** What a deploy would do, without doing it. */
|
|
442
|
-
export function planDeploy(tenant, workspace, files) {
|
|
441
|
+
export function planDeploy(tenant, workspace, files, opts = {}) {
|
|
443
442
|
const dir = workspaceDir(tenant, workspace);
|
|
444
443
|
const shipped = new Map(files.map((f) => [path.normalize(f.path), f.content]));
|
|
445
444
|
const present = new Set();
|
|
@@ -481,7 +480,10 @@ export function planDeploy(tenant, workspace, files) {
|
|
|
481
480
|
// memory, the platform's own files. Reporting these as removals would be
|
|
482
481
|
// a lie, and the alarming kind; one predicate serves both.
|
|
483
482
|
const shippedPaths = new Set(shipped.keys());
|
|
484
|
-
|
|
483
|
+
// keepUntracked (a restore): what is not editable source stays, as
|
|
484
|
+
// saveWorkspace keeps it — see its meta.keepUntracked.
|
|
485
|
+
const tracked = opts.keepUntracked ? new Set(listWorkspaceFiles(tenant, workspace)) : null;
|
|
486
|
+
const removed = [...present].filter((rel) => !shipped.has(rel) && !keptOnDeploy(rel, shippedPaths) && (!tracked || tracked.has(rel)));
|
|
485
487
|
return {
|
|
486
488
|
files,
|
|
487
489
|
added: added.sort(),
|
|
@@ -492,62 +494,18 @@ export function planDeploy(tenant, workspace, files) {
|
|
|
492
494
|
blockedBy: runsInFlight(tenant, workspace),
|
|
493
495
|
};
|
|
494
496
|
}
|
|
495
|
-
export const pushNotePath = (tenant, workspace) => path.join(repoDir(tenant, workspace), "foldrun-last-push.json");
|
|
496
|
-
export function readPushNote(tenant, workspace) {
|
|
497
|
-
try {
|
|
498
|
-
return JSON.parse(fs.readFileSync(pushNotePath(tenant, workspace), "utf8"));
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
return null;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
export function writePushNote(tenant, workspace, note) {
|
|
505
|
-
try {
|
|
506
|
-
fs.writeFileSync(pushNotePath(tenant, workspace), JSON.stringify(note));
|
|
507
|
-
}
|
|
508
|
-
catch {
|
|
509
|
-
// The note is a record, not the deploy. Losing it must not fail a push.
|
|
510
|
-
}
|
|
511
|
-
// A push waiting on a run is the scheduler's to apply; tell it there is one.
|
|
512
|
-
notifyWorkspaceChanged(tenant, workspace, "push");
|
|
513
|
-
}
|
|
514
497
|
/**
|
|
515
|
-
*
|
|
516
|
-
* failure — it is a deploy that has not happened yet. Git already said the
|
|
517
|
-
* push succeeded (the deploy happens after the client is gone), so nothing
|
|
518
|
-
* would ever tell anyone the workspace on disk is behind `main`. This applies
|
|
519
|
-
* it once the runs are done.
|
|
498
|
+
* Deploy a file list to a workspace, if it checks out.
|
|
520
499
|
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
500
|
+
* Returns the plan either way, with `applied` saying whether anything changed.
|
|
501
|
+
* A refused deploy is a normal outcome, not an exception: the caller is a
|
|
502
|
+
* webhook or a CLI that needs to report *why*, and every reason is already in
|
|
503
|
+
* the plan.
|
|
523
504
|
*/
|
|
524
|
-
export function applyPendingPush(tenant, workspace, filesAt) {
|
|
525
|
-
const note = readPushNote(tenant, workspace);
|
|
526
|
-
if (!note || note.applied)
|
|
527
|
-
return null;
|
|
528
|
-
if (!note.blockedBy?.length || (note.issues?.length ?? 0) > 0)
|
|
529
|
-
return null;
|
|
530
|
-
if (runsInFlight(tenant, workspace).length > 0)
|
|
531
|
-
return null;
|
|
532
|
-
// Only the commit the note names, and only while it is still what main
|
|
533
|
-
// points at — a newer push supersedes it and wrote its own note.
|
|
534
|
-
const result = deployWorkspace(tenant, workspace, filesAt(tenant, workspace, note.commit), {
|
|
535
|
-
commit: note.commit,
|
|
536
|
-
force: false,
|
|
537
|
-
});
|
|
538
|
-
writePushNote(tenant, workspace, {
|
|
539
|
-
...note,
|
|
540
|
-
applied: result.applied,
|
|
541
|
-
issues: result.issues ?? [],
|
|
542
|
-
blockedBy: result.blockedBy ?? [],
|
|
543
|
-
...(result.applied ? { appliedAt: new Date().toISOString() } : {}),
|
|
544
|
-
});
|
|
545
|
-
return result.applied ? { commit: note.commit } : null;
|
|
546
|
-
}
|
|
547
505
|
export function deployWorkspace(tenant, workspace, files, opts = {}) {
|
|
548
506
|
assertSafeName(tenant, "tenant");
|
|
549
507
|
assertSafeName(workspace, "workspace");
|
|
550
|
-
const plan = planDeploy(tenant, workspace, files);
|
|
508
|
+
const plan = planDeploy(tenant, workspace, files, { keepUntracked: opts.keepUntracked });
|
|
551
509
|
const blocked = plan.blockedBy.length > 0 && !opts.force;
|
|
552
510
|
if (plan.issues.length > 0 || blocked) {
|
|
553
511
|
return { ...plan, applied: false, commit: null, preserved: 0 };
|
|
@@ -559,7 +517,7 @@ export function deployWorkspace(tenant, workspace, files, opts = {}) {
|
|
|
559
517
|
return { ...plan, applied: false, commit: null, preserved: 0, unexpectedRemovals };
|
|
560
518
|
}
|
|
561
519
|
}
|
|
562
|
-
const { preserved } = saveWorkspace(tenant, workspace, files, { commit: opts.commit ?? null, by: opts.by ?? "deploy", message: opts.message });
|
|
520
|
+
const { preserved } = saveWorkspace(tenant, workspace, files, { commit: opts.commit ?? null, by: opts.by ?? "deploy", message: opts.message, keepUntracked: opts.keepUntracked });
|
|
563
521
|
const commit = opts.commit ?? null;
|
|
564
522
|
if (commit)
|
|
565
523
|
writeDeployedCommit(tenant, workspace, commit);
|
package/dist/src/history.d.ts
CHANGED
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
type TreeReader = (tenant: string, scope: string) => RevisionFile[] | null;
|
|
11
11
|
export declare function registerTreeReader(fn: TreeReader): void;
|
|
12
|
-
/** Something to do after a commit lands on main — the mirror push. Registered
|
|
13
|
-
* by mirror.ts; this module cannot import it (secrets, settings). */
|
|
14
|
-
type AfterCommit = (tenant: string, scope: string, sha: string) => void;
|
|
15
|
-
export declare function registerAfterCommit(fn: AfterCommit): void;
|
|
16
|
-
export declare function notifyCommit(tenant: string, scope: string, sha: string): void;
|
|
17
12
|
export interface RevisionFile {
|
|
18
13
|
path: string;
|
|
19
14
|
/** Null when the file did not exist before. */
|
|
@@ -55,17 +50,42 @@ export declare function recordRevision(tenant: string, scope: string, files: Rev
|
|
|
55
50
|
id?: string;
|
|
56
51
|
commit?: string | null;
|
|
57
52
|
}): Revision | null;
|
|
53
|
+
/** Whether a revision can be restored to: it has a snapshot. */
|
|
54
|
+
export declare function hasSnapshot(tenant: string, scope: string, id: string): boolean;
|
|
55
|
+
/** The whole tree at revision `id`, or null when it has no snapshot. */
|
|
56
|
+
export declare function snapshotAt(tenant: string, scope: string, id: string): {
|
|
57
|
+
path: string;
|
|
58
|
+
content: string;
|
|
59
|
+
}[] | null;
|
|
60
|
+
/** The newest revision that can be restored to — what "now" is in history. */
|
|
61
|
+
export declare function headRevision(tenant: string, scope: string): RevisionSummary | null;
|
|
62
|
+
/**
|
|
63
|
+
* A scope with no snapshot yet gets one: its tree as it stands, recorded as
|
|
64
|
+
* a revision of its own — where its history starts. Every workspace that
|
|
65
|
+
* existed when history moved off git starts here, and so does one created
|
|
66
|
+
* by a path that never recorded a change. Returns the head's id, or null
|
|
67
|
+
* when there is nothing to snapshot.
|
|
68
|
+
*/
|
|
69
|
+
export declare function ensureBaseline(tenant: string, scope: string, by?: string): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* The revision a restore means: an id (or the commit a deploy recorded), a
|
|
72
|
+
* date (`2026-10-17`, `2026-10-17T09:00Z`) or a span back from now (`90m`,
|
|
73
|
+
* `12h`, `3d`, `2w`) — the newest revision with a snapshot at or before that
|
|
74
|
+
* moment. Null when history holds nothing that old. Throws (status 400) on
|
|
75
|
+
* something that is none of those.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveRevision(tenant: string, scope: string, to: string, now?: number): {
|
|
78
|
+
id: string;
|
|
79
|
+
at: string;
|
|
80
|
+
by: string;
|
|
81
|
+
message: string;
|
|
82
|
+
} | null;
|
|
58
83
|
/** Newest first. `forPath` narrows to revisions that touched one file. */
|
|
59
84
|
export declare function listRevisions(tenant: string, scope: string, opts?: {
|
|
60
85
|
forPath?: string;
|
|
61
86
|
limit?: number;
|
|
62
87
|
}): RevisionSummary[];
|
|
63
88
|
export declare function readRevision(tenant: string, scope: string, id: string, forPath?: string): Revision | null;
|
|
64
|
-
/**
|
|
65
|
-
* Make sure a scope's repository has its initial import, so a clone made
|
|
66
|
-
* right now is complete. A no-op once any commit exists.
|
|
67
|
-
*/
|
|
68
|
-
export declare function ensureImported(tenant: string, scope: string): string | null;
|
|
69
89
|
/** The most recent revision id for a scope — what a change is attributed
|
|
70
90
|
* to when nothing more specific (a deployed commit) is on record. */
|
|
71
91
|
export declare function latestRevisionId(tenant: string, scope: string): string | null;
|