@relayflows/sdk 2.0.31 → 2.0.33
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/authored-preflight.d.ts +4 -0
- package/dist/authored-preflight.d.ts.map +1 -0
- package/dist/authored-preflight.js +50 -0
- package/dist/authored-preflight.js.map +1 -0
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +3 -2
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check.d.ts +10 -2
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +16 -137
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cli-probe.d.ts +20 -0
- package/dist/cli/cli-probe.d.ts.map +1 -0
- package/dist/cli/cli-probe.js +166 -0
- package/dist/cli/cli-probe.js.map +1 -0
- package/dist/cli/cloud-mirror-session.d.ts +97 -0
- package/dist/cli/cloud-mirror-session.d.ts.map +1 -0
- package/dist/cli/cloud-mirror-session.js +264 -0
- package/dist/cli/cloud-mirror-session.js.map +1 -0
- package/dist/cli-commands.d.ts +6 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +1 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +84 -18
- package/dist/cli.js.map +1 -1
- package/dist/cloud-deploy.d.ts +1 -1
- package/dist/cloud-deploy.d.ts.map +1 -1
- package/dist/cloud-deploy.js +24 -8
- package/dist/cloud-deploy.js.map +1 -1
- package/dist/cloud-mirror-ledger.d.ts +40 -0
- package/dist/cloud-mirror-ledger.d.ts.map +1 -0
- package/dist/cloud-mirror-ledger.js +142 -0
- package/dist/cloud-mirror-ledger.js.map +1 -0
- package/dist/cloud-mirror-step.d.ts +180 -0
- package/dist/cloud-mirror-step.d.ts.map +1 -0
- package/dist/cloud-mirror-step.js +523 -0
- package/dist/cloud-mirror-step.js.map +1 -0
- package/dist/cloud-mirror-transport.d.ts +94 -0
- package/dist/cloud-mirror-transport.d.ts.map +1 -0
- package/dist/cloud-mirror-transport.js +163 -0
- package/dist/cloud-mirror-transport.js.map +1 -0
- package/dist/cloud-mirror.d.ts +132 -0
- package/dist/cloud-mirror.d.ts.map +1 -0
- package/dist/cloud-mirror.js +556 -0
- package/dist/cloud-mirror.js.map +1 -0
- package/dist/cloud-transcript-codex.d.ts.map +1 -1
- package/dist/cloud-transcript-codex.js +90 -4
- package/dist/cloud-transcript-codex.js.map +1 -1
- package/dist/cloud-transcript-types.d.ts +19 -1
- package/dist/cloud-transcript-types.d.ts.map +1 -1
- package/dist/cloud-transcript-types.js.map +1 -1
- package/dist/cloud-transcript.d.ts +1 -1
- package/dist/cloud-transcript.d.ts.map +1 -1
- package/dist/cloud-transcript.js +10 -0
- package/dist/cloud-transcript.js.map +1 -1
- package/dist/helper-reference.d.ts.map +1 -1
- package/dist/helper-reference.js +134 -6
- package/dist/helper-reference.js.map +1 -1
- package/dist/hosted-extension-runtime.js +1 -1
- package/dist/hosted-extension-sandbox.js +1 -1
- package/dist/package-version.d.ts +10 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +22 -0
- package/dist/package-version.js.map +1 -0
- package/dist/preflight.d.ts +14 -0
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +18 -3
- package/dist/preflight.js.map +1 -1
- package/dist/relay-cli.d.ts.map +1 -1
- package/dist/relay-cli.js +1 -21
- package/dist/relay-cli.js.map +1 -1
- package/package.json +2 -2
- package/src/authored-preflight.ts +46 -0
- package/src/authored-worker-step.ts +4 -3
- package/src/cli/check.ts +25 -163
- package/src/cli/cli-probe.ts +194 -0
- package/src/cli/cloud-mirror-session.ts +329 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +94 -19
- package/src/cloud-deploy.ts +26 -8
- package/src/cloud-mirror-ledger.ts +161 -0
- package/src/cloud-mirror-step.ts +650 -0
- package/src/cloud-mirror-transport.ts +261 -0
- package/src/cloud-mirror.ts +633 -0
- package/src/cloud-transcript-codex.ts +96 -4
- package/src/cloud-transcript-types.ts +18 -1
- package/src/cloud-transcript.ts +9 -1
- package/src/helper-reference.ts +131 -6
- package/src/hosted-extension-runtime.ts +1 -1
- package/src/hosted-extension-sandbox.ts +1 -1
- package/src/package-version.ts +23 -0
- package/src/preflight.ts +31 -7
- package/src/relay-cli.ts +1 -24
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Which Cloud run mirrored which journal, remembered between invocations.
|
|
2
|
+
//
|
|
3
|
+
// A journal outlives the process that started it: `flows run` parks on an
|
|
4
|
+
// `f.human`, exits, and some time later a different `flows resume` picks the
|
|
5
|
+
// same journal up. Cloud models that second attempt as its own run row — its
|
|
6
|
+
// own hosted resume mints a fresh run id too — so a parked-then-resumed flow
|
|
7
|
+
// is two rows however it is driven. What made that read as two *unrelated*
|
|
8
|
+
// pieces of work was that the resuming process had no idea the first attempt
|
|
9
|
+
// existed: nothing on disk connected the journal to the Cloud run mirroring
|
|
10
|
+
// it, so the second registration could not name its predecessor.
|
|
11
|
+
//
|
|
12
|
+
// This is that connection, and deliberately nothing more.
|
|
13
|
+
//
|
|
14
|
+
// ## What is stored, and what is not
|
|
15
|
+
//
|
|
16
|
+
// The run id and the deployment that issued it. **Never the credential.** The
|
|
17
|
+
// run token can write this run's steps and read the workspace's runs; leaving
|
|
18
|
+
// one in a dotfile under a developer's home directory, for every run they
|
|
19
|
+
// have ever started, would be a worse trade than the feature is worth. A
|
|
20
|
+
// resume mints its own credential the same way the first attempt did — the
|
|
21
|
+
// registration request it already makes.
|
|
22
|
+
//
|
|
23
|
+
// So an entry is not a capability. Everything in it is already in the URL the
|
|
24
|
+
// first attempt printed to the terminal.
|
|
25
|
+
//
|
|
26
|
+
// ## Failure is not an error
|
|
27
|
+
//
|
|
28
|
+
// Every operation here is best-effort and silent. A missing entry means the
|
|
29
|
+
// resumed run registers without naming a predecessor, which is exactly what
|
|
30
|
+
// happened before this file existed; an unreadable or corrupt one means the
|
|
31
|
+
// same. A mirror is an observer, and bookkeeping for an observer must never
|
|
32
|
+
// be able to fail a run.
|
|
33
|
+
|
|
34
|
+
import { mkdir, readFile, readdir, stat, unlink, writeFile } from 'node:fs/promises';
|
|
35
|
+
import { join, resolve } from 'node:path';
|
|
36
|
+
|
|
37
|
+
/** Where the ledger lives, beside the journals it describes. */
|
|
38
|
+
export const LEDGER_DIRECTORY = 'cloud-runs';
|
|
39
|
+
/**
|
|
40
|
+
* How long an entry is worth keeping.
|
|
41
|
+
*
|
|
42
|
+
* A resume of a two-week-old parked run is a real thing; a resume of a run
|
|
43
|
+
* from last quarter is not the case this serves, and a ledger that only grows
|
|
44
|
+
* is a directory nobody ever looks at filling up forever.
|
|
45
|
+
*/
|
|
46
|
+
export const LEDGER_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1_000;
|
|
47
|
+
/** Entries kept. Past it, the oldest are dropped on the next write. */
|
|
48
|
+
export const LEDGER_MAX_ENTRIES = 500;
|
|
49
|
+
|
|
50
|
+
/** One journal's mirror, as the ledger records it. */
|
|
51
|
+
export interface LedgerEntry {
|
|
52
|
+
/** Cloud's run id for the attempt that mirrored this journal. */
|
|
53
|
+
cloudRunId: string;
|
|
54
|
+
/** The deployment that issued it; an entry from another one is not this one's. */
|
|
55
|
+
apiUrl: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const JOURNAL_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
|
|
59
|
+
const CLOUD_RUN_ID = /^[A-Za-z0-9_-]{1,128}$/u;
|
|
60
|
+
|
|
61
|
+
function entryPath(dataDir: string, journalRunId: string): string {
|
|
62
|
+
return join(resolve(dataDir), LEDGER_DIRECTORY, `${journalRunId}.json`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Record that `journalRunId` is mirrored by `entry.cloudRunId`.
|
|
67
|
+
*
|
|
68
|
+
* Written with mode 0600 for the same reason the journals are private: it
|
|
69
|
+
* names runs in someone's workspace, and a shared machine should not publish
|
|
70
|
+
* that. It is not a secret — there is nothing here to steal — but it is
|
|
71
|
+
* nobody else's business either.
|
|
72
|
+
*/
|
|
73
|
+
export async function recordMirroredRun(
|
|
74
|
+
dataDir: string,
|
|
75
|
+
journalRunId: string,
|
|
76
|
+
entry: LedgerEntry,
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
if (!JOURNAL_RUN_ID.test(journalRunId) || !CLOUD_RUN_ID.test(entry.cloudRunId)) return;
|
|
79
|
+
const directory = join(resolve(dataDir), LEDGER_DIRECTORY);
|
|
80
|
+
try {
|
|
81
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
82
|
+
await writeFile(
|
|
83
|
+
entryPath(dataDir, journalRunId),
|
|
84
|
+
JSON.stringify({ cloudRunId: entry.cloudRunId, apiUrl: entry.apiUrl }),
|
|
85
|
+
{ mode: 0o600 },
|
|
86
|
+
);
|
|
87
|
+
} catch {
|
|
88
|
+
// A resume that cannot name its predecessor is the behaviour this file
|
|
89
|
+
// improves on, not a failure of the run.
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await prune(directory);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The Cloud run mirroring this journal, if one is recorded *for this
|
|
97
|
+
* deployment*.
|
|
98
|
+
*
|
|
99
|
+
* The deployment check is not bookkeeping. A developer who ran a flow against
|
|
100
|
+
* staging and resumes it against production must not have the resumed run
|
|
101
|
+
* claim to continue a run id that means something else there — or, worse,
|
|
102
|
+
* nothing at all, which Cloud would refuse and which would read as a bug in
|
|
103
|
+
* the resume rather than in the pairing.
|
|
104
|
+
*/
|
|
105
|
+
export async function readMirroredRun(
|
|
106
|
+
dataDir: string,
|
|
107
|
+
journalRunId: string,
|
|
108
|
+
apiUrl: string,
|
|
109
|
+
): Promise<string | undefined> {
|
|
110
|
+
if (!JOURNAL_RUN_ID.test(journalRunId)) return undefined;
|
|
111
|
+
let parsed: unknown;
|
|
112
|
+
try {
|
|
113
|
+
parsed = JSON.parse(await readFile(entryPath(dataDir, journalRunId), 'utf8'));
|
|
114
|
+
} catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined;
|
|
118
|
+
const entry = parsed as Record<string, unknown>;
|
|
119
|
+
if (typeof entry['cloudRunId'] !== 'string' || !CLOUD_RUN_ID.test(entry['cloudRunId'])) return undefined;
|
|
120
|
+
if (entry['apiUrl'] !== apiUrl) return undefined;
|
|
121
|
+
return entry['cloudRunId'];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Drop entries past their age, then past the count. Never throws. */
|
|
125
|
+
async function prune(directory: string): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
const names = (await readdir(directory)).filter(name => name.endsWith('.json'));
|
|
128
|
+
if (names.length <= LEDGER_MAX_ENTRIES) {
|
|
129
|
+
// Still age out: a data dir that never reaches the count cap would
|
|
130
|
+
// otherwise keep its first entry forever.
|
|
131
|
+
await dropOlderThan(directory, names, Date.now() - LEDGER_MAX_AGE_MS);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const ages = await Promise.all(names.map(async name => {
|
|
135
|
+
try {
|
|
136
|
+
return { name, at: (await stat(join(directory, name))).mtimeMs };
|
|
137
|
+
} catch {
|
|
138
|
+
return { name, at: 0 };
|
|
139
|
+
}
|
|
140
|
+
}));
|
|
141
|
+
ages.sort((left, right) => right.at - left.at);
|
|
142
|
+
await Promise.all(ages.slice(LEDGER_MAX_ENTRIES).map(entry =>
|
|
143
|
+
unlink(join(directory, entry.name)).catch(() => undefined)));
|
|
144
|
+
await dropOlderThan(directory, ages.slice(0, LEDGER_MAX_ENTRIES).map(entry => entry.name),
|
|
145
|
+
Date.now() - LEDGER_MAX_AGE_MS);
|
|
146
|
+
} catch {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function dropOlderThan(directory: string, names: string[], floor: number): Promise<void> {
|
|
152
|
+
await Promise.all(names.map(async name => {
|
|
153
|
+
try {
|
|
154
|
+
if ((await stat(join(directory, name))).mtimeMs < floor) {
|
|
155
|
+
await unlink(join(directory, name));
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
}
|