@relayflows/sdk 2.0.15 → 2.0.17
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/agent-artifacts.d.ts +20 -0
- package/dist/agent-artifacts.d.ts.map +1 -0
- package/dist/agent-artifacts.js +81 -0
- package/dist/agent-artifacts.js.map +1 -0
- package/dist/authored-flow-error.d.ts +1 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts +3 -3
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +93 -7
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-flow-lifecycle.d.ts +10 -0
- package/dist/authored-flow-lifecycle.d.ts.map +1 -1
- package/dist/authored-flow-lifecycle.js +7 -0
- package/dist/authored-flow-lifecycle.js.map +1 -1
- package/dist/authored-flow-operation.d.ts +11 -0
- package/dist/authored-flow-operation.d.ts.map +1 -1
- package/dist/authored-flow-operation.js +32 -7
- package/dist/authored-flow-operation.js.map +1 -1
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +37 -14
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-step-output.d.ts +2 -0
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +5 -1
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +15 -8
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check-triggers.d.ts.map +1 -1
- package/dist/cli/check-triggers.js +10 -0
- package/dist/cli/check-triggers.js.map +1 -1
- package/dist/cli/check.d.ts +14 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +6 -1
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-deploy.d.ts +30 -0
- package/dist/cli/cloud-deploy.d.ts.map +1 -0
- package/dist/cli/cloud-deploy.js +162 -0
- package/dist/cli/cloud-deploy.js.map +1 -0
- package/dist/cli/cloud-run.d.ts +3 -1
- package/dist/cli/cloud-run.d.ts.map +1 -1
- package/dist/cli/cloud-run.js +37 -5
- package/dist/cli/cloud-run.js.map +1 -1
- package/dist/cli/cloud-schedule.d.ts +29 -0
- package/dist/cli/cloud-schedule.d.ts.map +1 -0
- package/dist/cli/cloud-schedule.js +131 -0
- package/dist/cli/cloud-schedule.js.map +1 -0
- package/dist/cli/cloud-sync.d.ts +13 -0
- package/dist/cli/cloud-sync.d.ts.map +1 -0
- package/dist/cli/cloud-sync.js +39 -0
- package/dist/cli/cloud-sync.js.map +1 -0
- package/dist/cli/direct-run.js +1 -1
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/run.d.ts +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +17 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +117 -12
- package/dist/cli.js.map +1 -1
- package/dist/cloud-deploy.d.ts +70 -0
- package/dist/cloud-deploy.d.ts.map +1 -0
- package/dist/cloud-deploy.js +192 -0
- package/dist/cloud-deploy.js.map +1 -0
- package/dist/cloud-http.d.ts +52 -2
- package/dist/cloud-http.d.ts.map +1 -1
- package/dist/cloud-http.js +135 -9
- package/dist/cloud-http.js.map +1 -1
- package/dist/cloud-run.d.ts +53 -0
- package/dist/cloud-run.d.ts.map +1 -1
- package/dist/cloud-run.js +97 -25
- package/dist/cloud-run.js.map +1 -1
- package/dist/cloud-schedule.d.ts +48 -0
- package/dist/cloud-schedule.d.ts.map +1 -0
- package/dist/cloud-schedule.js +125 -0
- package/dist/cloud-schedule.js.map +1 -0
- package/dist/cloud-sync.d.ts +66 -0
- package/dist/cloud-sync.d.ts.map +1 -0
- package/dist/cloud-sync.js +287 -0
- package/dist/cloud-sync.js.map +1 -0
- package/dist/failure-kinds.d.ts +10 -3
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +10 -0
- package/dist/failure-kinds.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/named-gate-lowering.js +9 -0
- package/dist/named-gate-lowering.js.map +1 -1
- package/dist/named-gates.d.ts +1 -1
- package/dist/named-gates.d.ts.map +1 -1
- package/dist/named-gates.js +11 -1
- package/dist/named-gates.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +155 -9
- package/dist/preflight.js.map +1 -1
- package/dist/schedule-trigger.d.ts +35 -0
- package/dist/schedule-trigger.d.ts.map +1 -0
- package/dist/schedule-trigger.js +53 -0
- package/dist/schedule-trigger.js.map +1 -0
- package/dist/spec.d.ts +11 -1
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/trigger-executor.js +1 -1
- package/dist/trigger-executor.js.map +1 -1
- package/dist/validate.js +1 -1
- package/dist/validate.js.map +1 -1
- package/dist/worker-cli.d.ts +10 -0
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +51 -22
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +9 -0
- package/dist/worker.js.map +1 -1
- package/dist/wrapper-session.d.ts +3 -1
- package/dist/wrapper-session.d.ts.map +1 -1
- package/dist/wrapper-session.js +6 -3
- package/dist/wrapper-session.js.map +1 -1
- package/package.json +3 -3
- package/src/agent-artifacts.ts +76 -0
- package/src/authored-flow-error.ts +1 -0
- package/src/authored-flow-executor.ts +95 -8
- package/src/authored-flow-lifecycle.ts +7 -0
- package/src/authored-flow-operation.ts +35 -10
- package/src/authored-node-runner.ts +35 -13
- package/src/authored-step-output.ts +6 -1
- package/src/authored-worker-step.ts +16 -9
- package/src/cli/check-triggers.ts +9 -0
- package/src/cli/check.ts +21 -1
- package/src/cli/cloud-deploy.ts +157 -0
- package/src/cli/cloud-run.ts +37 -6
- package/src/cli/cloud-schedule.ts +135 -0
- package/src/cli/cloud-sync.ts +37 -0
- package/src/cli/direct-run.ts +1 -1
- package/src/cli/run.ts +18 -5
- package/src/cli.ts +107 -12
- package/src/cloud-deploy.ts +247 -0
- package/src/cloud-http.ts +173 -9
- package/src/cloud-run.ts +143 -24
- package/src/cloud-schedule.ts +158 -0
- package/src/cloud-sync.ts +315 -0
- package/src/failure-kinds.ts +10 -0
- package/src/index.ts +14 -0
- package/src/named-gate-lowering.ts +9 -0
- package/src/named-gates.ts +11 -1
- package/src/preflight.ts +132 -9
- package/src/schedule-trigger.ts +73 -0
- package/src/spec.ts +12 -1
- package/src/trigger-executor.ts +1 -1
- package/src/validate.ts +1 -1
- package/src/worker-cli.ts +41 -0
- package/src/worker.ts +9 -0
- package/src/wrapper-session.ts +5 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { CloudFlowError } from '../cloud-http.js';
|
|
2
|
+
import { listCloudSchedules, scheduleInCloud, unscheduleInCloud, type CloudSchedule } from '../cloud-schedule.js';
|
|
3
|
+
import { DirectInputError, isAuthoredFlowPath, parseDirectInput } from '../direct-input.js';
|
|
4
|
+
import { snapshotJsonValue, type JsonValue } from '../json-value.js';
|
|
5
|
+
import type { CliIo } from '../cli.js';
|
|
6
|
+
|
|
7
|
+
export interface CloudScheduleArgs {
|
|
8
|
+
command: 'schedule';
|
|
9
|
+
value: string;
|
|
10
|
+
cron: string | undefined;
|
|
11
|
+
every: string | undefined;
|
|
12
|
+
tz: string | undefined;
|
|
13
|
+
input: string | undefined;
|
|
14
|
+
name: string | undefined;
|
|
15
|
+
json: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `flows schedule <flow.yaml|flow.ts> [--cron "<expr>" | --every <n><s|m|h|d>]
|
|
20
|
+
* [--tz <IANA>] [--input <json|file>] [--name <n>] [--json]`
|
|
21
|
+
*
|
|
22
|
+
* With neither `--cron` nor `--every`, the flow's own `schedule.*` handler
|
|
23
|
+
* supplies the cron, so a declared schedule and its hosted registration
|
|
24
|
+
* cannot drift.
|
|
25
|
+
*/
|
|
26
|
+
export function parseCloudScheduleArgs(args: readonly string[]): CloudScheduleArgs | undefined {
|
|
27
|
+
let value: string | undefined;
|
|
28
|
+
const flags: Record<'cron' | 'every' | 'tz' | 'input' | 'name', string | undefined> = {
|
|
29
|
+
cron: undefined, every: undefined, tz: undefined, input: undefined, name: undefined,
|
|
30
|
+
};
|
|
31
|
+
let json = false;
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
const arg = args[i]!;
|
|
34
|
+
if (arg === '--json') {
|
|
35
|
+
if (json) return undefined;
|
|
36
|
+
json = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === '--cron' || arg === '--every' || arg === '--tz' || arg === '--input' || arg === '--name') {
|
|
40
|
+
const key = arg.slice(2) as keyof typeof flags;
|
|
41
|
+
const next = args[i + 1];
|
|
42
|
+
// A cron expression legitimately starts with `*`; only a flag-shaped value is refused.
|
|
43
|
+
if (flags[key] !== undefined || next === undefined || next.startsWith('--')) return undefined;
|
|
44
|
+
flags[key] = next;
|
|
45
|
+
i += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (arg.startsWith('-') || value !== undefined) return undefined;
|
|
49
|
+
value = arg;
|
|
50
|
+
}
|
|
51
|
+
if (value === undefined) return undefined;
|
|
52
|
+
if (flags.cron !== undefined && flags.every !== undefined) return undefined;
|
|
53
|
+
if (flags.input !== undefined && !isAuthoredFlowPath(value)) return undefined;
|
|
54
|
+
return { command: 'schedule', value, ...flags, json };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function describe(schedule: CloudSchedule): string {
|
|
58
|
+
const last = schedule.lastTriggeredAt === undefined ? ''
|
|
59
|
+
: ` last ${schedule.lastTriggeredAt}${schedule.lastTriggerStatus === undefined ? '' : ` (${schedule.lastTriggerStatus})`}`;
|
|
60
|
+
return `${schedule.id} ${schedule.status} ${JSON.stringify(schedule.name)} cron "${schedule.cronExpression}" tz ${schedule.timezone}${last}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function runCloudScheduleCli(args: CloudScheduleArgs, io: CliIo): Promise<0 | 1 | 2> {
|
|
64
|
+
try {
|
|
65
|
+
let input: JsonValue | undefined;
|
|
66
|
+
let inputPresent = false;
|
|
67
|
+
if (isAuthoredFlowPath(args.value)) {
|
|
68
|
+
try {
|
|
69
|
+
input = snapshotJsonValue(parseDirectInput(args.input), 'Cloud authored input');
|
|
70
|
+
inputPresent = true;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof DirectInputError) throw new CloudFlowError('invalid_input', error.message);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const schedule = await scheduleInCloud({
|
|
77
|
+
flow: { path: args.value },
|
|
78
|
+
...(args.cron === undefined ? {} : { cron: args.cron }),
|
|
79
|
+
...(args.every === undefined ? {} : { every: args.every }),
|
|
80
|
+
...(args.tz === undefined ? {} : { tz: args.tz }),
|
|
81
|
+
...(args.name === undefined ? {} : { name: args.name }),
|
|
82
|
+
...(inputPresent ? { input } : {}),
|
|
83
|
+
});
|
|
84
|
+
if (args.json) {
|
|
85
|
+
io.stdout(JSON.stringify({ ok: true, ...schedule }));
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
io.stdout(`SCHEDULED ${describe(schedule)}`);
|
|
89
|
+
io.stdout('Each fire submits this exact source through the same admission as flows run --cloud; list with: flows schedules');
|
|
90
|
+
return 0;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return reportFailure(error, args.json, io);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function runCloudSchedulesCli({ json }: { json: boolean }, io: CliIo): Promise<0 | 1 | 2> {
|
|
97
|
+
try {
|
|
98
|
+
const schedules = await listCloudSchedules();
|
|
99
|
+
if (json) {
|
|
100
|
+
io.stdout(JSON.stringify({ ok: true, schedules }));
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
if (schedules.length === 0) io.stdout('No workflow schedules in this workspace.');
|
|
104
|
+
for (const schedule of schedules) io.stdout(describe(schedule));
|
|
105
|
+
return 0;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return reportFailure(error, json, io);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function runCloudUnscheduleCli(
|
|
112
|
+
{ scheduleId, json }: { scheduleId: string; json: boolean }, io: CliIo,
|
|
113
|
+
): Promise<0 | 1 | 2> {
|
|
114
|
+
try {
|
|
115
|
+
await unscheduleInCloud(scheduleId);
|
|
116
|
+
io.stdout(json ? JSON.stringify({ ok: true, scheduleId, deleted: true }) : `UNSCHEDULED ${scheduleId}`);
|
|
117
|
+
return 0;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return reportFailure(error, json, io);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function reportFailure(error: unknown, json: boolean, io: CliIo): 1 | 2 {
|
|
124
|
+
const code = error instanceof CloudFlowError ? error.code : 'cloud_schedule_failed';
|
|
125
|
+
let message = error instanceof Error ? error.message : 'Cloud schedule failed.';
|
|
126
|
+
// The schedule routes take a browser session or a `cli:auth` token.
|
|
127
|
+
if (error instanceof CloudFlowError && error.status === 403) {
|
|
128
|
+
message += ' Scheduling needs an interactive `cli:auth` credential: run `agent-relay cloud login`.';
|
|
129
|
+
}
|
|
130
|
+
if (json) io.stdout(JSON.stringify({ ok: false, code, message }));
|
|
131
|
+
else io.stderr(`${code}: ${message}`);
|
|
132
|
+
return error instanceof CloudFlowError
|
|
133
|
+
&& (['configuration', 'unsupported_source', 'invalid_input'].includes(error.code) || error.status === 403 || error.status === 401)
|
|
134
|
+
? 2 : 1;
|
|
135
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CloudFlowError } from '../cloud-http.js';
|
|
2
|
+
import { applyCloudPatch, downloadCloudPatch, patchedPaths } from '../cloud-sync.js';
|
|
3
|
+
import type { CliIo } from '../cli.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `flows sync <run-id>`: fetch the diff a hosted run left in its synced tree
|
|
7
|
+
* and apply it here. The patch is the sandbox's own `git diff` against the
|
|
8
|
+
* uploaded baseline, so applying it reproduces exactly what the run's steps
|
|
9
|
+
* wrote — no re-execution, no re-upload.
|
|
10
|
+
*/
|
|
11
|
+
export async function runCloudSyncCli(
|
|
12
|
+
{ runId, json, root }: { runId: string; json: boolean; root: string },
|
|
13
|
+
io: CliIo,
|
|
14
|
+
): Promise<0 | 1 | 2> {
|
|
15
|
+
try {
|
|
16
|
+
const { patch, hasChanges } = await downloadCloudPatch(runId, {});
|
|
17
|
+
if (!hasChanges || !patch.trim()) {
|
|
18
|
+
if (json) io.stdout(JSON.stringify({ ok: true, runId, hasChanges: false, applied: false }));
|
|
19
|
+
else io.stdout(`NO CHANGES ${runId}`);
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
applyCloudPatch(root, patch);
|
|
23
|
+
const files = patchedPaths(patch);
|
|
24
|
+
if (json) io.stdout(JSON.stringify({ ok: true, runId, hasChanges: true, applied: true, files }));
|
|
25
|
+
else {
|
|
26
|
+
io.stdout(`APPLIED ${runId}: ${files.length} file${files.length === 1 ? '' : 's'}${files.length ? `\n ${files.join('\n ')}` : ''}`);
|
|
27
|
+
io.stdout('Applied to the working tree, uncommitted: review with git diff before keeping it.');
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const code = error instanceof CloudFlowError ? error.code : 'cloud_sync_failed';
|
|
32
|
+
const message = error instanceof Error ? error.message : 'Cloud sync failed.';
|
|
33
|
+
if (json) io.stdout(JSON.stringify({ ok: false, code, message, runId }));
|
|
34
|
+
else io.stderr(`${code}: ${message} (run ${runId})`);
|
|
35
|
+
return error instanceof CloudFlowError && ['configuration', 'sync_unsupported', 'patch_conflict'].includes(error.code) ? 2 : 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/cli/direct-run.ts
CHANGED
|
@@ -172,7 +172,7 @@ export async function runDirectFlow(
|
|
|
172
172
|
// diagnostic carried up from `classifyOutcome` already names the step, its
|
|
173
173
|
// exit code and its output tail; this branch is what lets it reach the
|
|
174
174
|
// terminal. `resumeFlow` takes the same branch, through the same helper.
|
|
175
|
-
if (error instanceof AuthoredFlowExecutionError && error.code === 'step_failed') {
|
|
175
|
+
if (error instanceof AuthoredFlowExecutionError && (error.code === 'step_failed' || error.code === 'gate_failed')) {
|
|
176
176
|
return authoredStepFailure('run', base, socketPath, error);
|
|
177
177
|
}
|
|
178
178
|
const runId = error instanceof AuthoredFlowExecutionError ? error.runId : undefined;
|
package/src/cli/run.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface ParkedStep {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface RunDiagnostic extends StepFailedDetails {
|
|
40
|
-
severity: 'refusal' | 'failure' | 'parked' | 'warning';
|
|
40
|
+
severity: 'refusal' | 'failure' | 'parked' | 'warning' | 'declined';
|
|
41
41
|
kind: RunFailureKind | RunWarningKind | RunCompletionReason;
|
|
42
42
|
message: string;
|
|
43
43
|
}
|
|
@@ -223,7 +223,7 @@ export async function resumeFlow(
|
|
|
223
223
|
// leaving it on `protocolFailure` meant `flows run` printed the evidence
|
|
224
224
|
// while `flows resume` still printed `protocol_error` and
|
|
225
225
|
// `RUN <id> unknown` for the identical failure.
|
|
226
|
-
if (error instanceof AuthoredFlowExecutionError && error.code === 'step_failed') {
|
|
226
|
+
if (error instanceof AuthoredFlowExecutionError && (error.code === 'step_failed' || error.code === 'gate_failed')) {
|
|
227
227
|
return authoredStepFailure('resume', base, socketPath, error, runId);
|
|
228
228
|
}
|
|
229
229
|
if (!(error instanceof JournalProtocolError) || error.code !== 'run_not_found') {
|
|
@@ -287,10 +287,12 @@ export function authoredStepFailure(
|
|
|
287
287
|
completionReason: 'step_failed',
|
|
288
288
|
diagnostics: [...base.diagnostics, {
|
|
289
289
|
severity: 'failure',
|
|
290
|
-
|
|
290
|
+
// A predicate gate that judged false is a run failure with its own
|
|
291
|
+
// name, so the report says which kind of check the body did not pass.
|
|
292
|
+
kind: error.code === 'gate_failed' ? 'gate_failed' : 'step_failed',
|
|
291
293
|
// The `step_failed: ` prefix `AuthoredFlowExecutionError` adds is
|
|
292
294
|
// redundant once the diagnostic is labelled `[step_failed]`.
|
|
293
|
-
message: error.message.replace(/^step_failed: /, ''),
|
|
295
|
+
message: error.message.replace(/^(?:step_failed|gate_failed): /, ''),
|
|
294
296
|
}],
|
|
295
297
|
},
|
|
296
298
|
};
|
|
@@ -328,6 +330,17 @@ export function authoredCompletion(
|
|
|
328
330
|
exitCode: 0,
|
|
329
331
|
report: { ...common, ok: true, status: 'completed', completionReason: 'success' },
|
|
330
332
|
};
|
|
333
|
+
case 'declined':
|
|
334
|
+
return {
|
|
335
|
+
exitCode: 0,
|
|
336
|
+
report: {
|
|
337
|
+
...common, ok: true, status: 'completed', completionReason: 'success',
|
|
338
|
+
diagnostics: [...base.diagnostics, {
|
|
339
|
+
severity: 'declined', kind: 'run_declined',
|
|
340
|
+
message: 'Flow deliberately chose not to act on this input.',
|
|
341
|
+
}],
|
|
342
|
+
},
|
|
343
|
+
};
|
|
331
344
|
case 'needs_human':
|
|
332
345
|
return {
|
|
333
346
|
exitCode: 3,
|
|
@@ -358,7 +371,7 @@ export function authoredCompletion(
|
|
|
358
371
|
},
|
|
359
372
|
};
|
|
360
373
|
}
|
|
361
|
-
// Exhaustive by construction. A
|
|
374
|
+
// Exhaustive by construction. A new lowered completion has to choose its
|
|
362
375
|
// own exit code and wording here; it must not inherit "its own checks did not
|
|
363
376
|
// pass", which would state something the body never declared. Letting an
|
|
364
377
|
// unlisted reason fall through to the failure branch is how a reporting-side
|
package/src/cli.ts
CHANGED
|
@@ -24,6 +24,9 @@ import { runDirectFlow } from './cli/direct-run.js';
|
|
|
24
24
|
import { parseReplayArgs, replayJournal, type ReplayArgs } from './cli/replay.js';
|
|
25
25
|
import { checkTypeScriptFlow } from './cli/check-typescript.js';
|
|
26
26
|
import { runCloudCli } from './cli/cloud-run.js';
|
|
27
|
+
import { runCloudSyncCli } from './cli/cloud-sync.js';
|
|
28
|
+
import { parseCloudDeployArgs, runCloudDeployCli, runCloudDeploymentsCli, runCloudUndeployCli, type CloudDeployArgs } from './cli/cloud-deploy.js';
|
|
29
|
+
import { parseCloudScheduleArgs, runCloudScheduleCli, runCloudSchedulesCli, runCloudUnscheduleCli, type CloudScheduleArgs } from './cli/cloud-schedule.js';
|
|
27
30
|
import { isAuthoredFlowPath } from './direct-input.js';
|
|
28
31
|
import { parseDeployArgs, runDeploy, type DeployArgs } from './cli/deploy.js';
|
|
29
32
|
import { parseDigestReference } from './bundle-transport.js';
|
|
@@ -51,7 +54,14 @@ type ParsedArgs =
|
|
|
51
54
|
| BuildArgs
|
|
52
55
|
| DeployArgs
|
|
53
56
|
| { command: 'serve-webhook'; dataDir: string; port: number; admitted?: readonly string[] }
|
|
54
|
-
| { command: 'cloud-run'; value: string; json: boolean; wait: boolean }
|
|
57
|
+
| { command: 'cloud-run'; value: string; json: boolean; wait: boolean; input: string | undefined; syncCode: boolean }
|
|
58
|
+
| { command: 'sync'; runId: string; json: boolean; root: string }
|
|
59
|
+
| CloudDeployArgs
|
|
60
|
+
| { command: 'deployments'; json: boolean }
|
|
61
|
+
| { command: 'undeploy'; agentId: string; json: boolean }
|
|
62
|
+
| CloudScheduleArgs
|
|
63
|
+
| { command: 'schedules'; json: boolean }
|
|
64
|
+
| { command: 'unschedule'; scheduleId: string; json: boolean }
|
|
55
65
|
| { command: 'check'; json: boolean; watch: boolean; value: string }
|
|
56
66
|
| { command: 'run'; bucket: string | undefined; reuseFromRunId: string | undefined; localAgent: boolean; dataDir: string; input: string | undefined; json: boolean; spawn: boolean; noObserverLink: boolean; allowHumanInfluenced: boolean; value: string }
|
|
57
67
|
| { command: 'resume'; localAgent: boolean; dataDir: string; json: boolean; spawn: boolean; noObserverLink: boolean; allowHumanInfluenced: boolean; value: string }
|
|
@@ -66,12 +76,20 @@ const USAGE = [
|
|
|
66
76
|
'flows add <helper-name|@flows/helper-name>',
|
|
67
77
|
'flows build [--out <dir>] <flow.yaml|flow.ts>',
|
|
68
78
|
'flows build --verify <bundle-dir>',
|
|
79
|
+
'flows deploy <flow.ts> --repo <owner/name> --on <provider>[:key=value,...] [--on ...] --approver <handle> [--agents claude[,codex]] [--name <name>] [--draft] [--json]',
|
|
80
|
+
'flows deployments [--json]',
|
|
81
|
+
'flows undeploy [--json] <deployment-id>',
|
|
82
|
+
'flows schedule <flow.yaml|flow.ts> [--cron "<expr>" | --every <n><s|m|h|d>] [--tz <IANA>] [--input <inline-json-or-file>] [--name <name>] [--json]',
|
|
83
|
+
'flows schedules [--json]',
|
|
84
|
+
'flows unschedule [--json] <schedule-id>',
|
|
69
85
|
'flows deploy <flow>@sha256:<digest> --to <file-bucket-uri>',
|
|
70
86
|
'flows run <flow>@sha256:<digest> [--bucket <file-bucket-uri>] [--data-dir <dir>] [--json]',
|
|
71
87
|
'flows check [--watch] [--json] <flow.ts|flow.yaml|spec.json>',
|
|
72
88
|
'flows serve-webhook --data-dir <dir> --port <p> [--allow <name>[,<name>]]',
|
|
73
89
|
'flows run [--json] [--no-spawn] [--no-observer-link] [--data-dir <dir>] [--local-agent] [--reuse-from <run-id>] <flow.yaml|spec.json>',
|
|
74
|
-
'flows run --cloud [--json] [--wait] <flow.yaml|spec.json>',
|
|
90
|
+
'flows run --cloud [--json] [--wait] [--sync-code] <flow.yaml|spec.json>',
|
|
91
|
+
'flows run --cloud [--json] [--wait] [--sync-code] <flow.ts> --input <inline-json-or-file>',
|
|
92
|
+
'flows sync [--json] [--dir <path>] <run-id>',
|
|
75
93
|
'flows run [--json] [--no-spawn] [--no-observer-link] [--data-dir <dir>] [--local-agent] <flow.ts> --input <inline-json-or-file>',
|
|
76
94
|
'flows tick start --schedule-id <id> --interval-ms <ms> [--epoch-ms <ms>] [--max-catch-up <n>] [--poll-interval-ms <ms>] [--data-dir <dir>] <spec.json>',
|
|
77
95
|
'flows resume [--allow-human-influenced] [--json] [--no-spawn] [--no-observer-link] [--data-dir <dir>] [--local-agent] <run-id>',
|
|
@@ -116,6 +134,13 @@ export async function runCli(
|
|
|
116
134
|
if (parsed.command === 'serve-webhook') return runServeWebhook(parsed, io);
|
|
117
135
|
|
|
118
136
|
if (parsed.command === 'cloud-run') return runCloudCli(parsed, io);
|
|
137
|
+
if (parsed.command === 'sync') return runCloudSyncCli(parsed, io);
|
|
138
|
+
if (parsed.command === 'cloud-deploy') return runCloudDeployCli(parsed, io);
|
|
139
|
+
if (parsed.command === 'deployments') return runCloudDeploymentsCli(parsed, io);
|
|
140
|
+
if (parsed.command === 'undeploy') return runCloudUndeployCli(parsed, io);
|
|
141
|
+
if (parsed.command === 'schedule') return runCloudScheduleCli(parsed, io);
|
|
142
|
+
if (parsed.command === 'schedules') return runCloudSchedulesCli(parsed, io);
|
|
143
|
+
if (parsed.command === 'unschedule') return runCloudUnscheduleCli(parsed, io);
|
|
119
144
|
if (parsed.command === 'replay') return replayJournal(parsed, io);
|
|
120
145
|
if (parsed.command === 'build') return runBuild(parsed, io);
|
|
121
146
|
if (parsed.command === 'deploy') return runDeploy(parsed, io);
|
|
@@ -251,6 +276,7 @@ async function checkAuthoredFlowComposed(path: string): Promise<{ report: CheckR
|
|
|
251
276
|
return {
|
|
252
277
|
report: {
|
|
253
278
|
...mcp.report,
|
|
279
|
+
...(triggers?.report.schedules === undefined ? {} : { schedules: triggers.report.schedules }),
|
|
254
280
|
diagnostics: [...helper.report.diagnostics, ...mcp.report.diagnostics, ...triggerDiagnostics],
|
|
255
281
|
ok: helper.report.ok && mcp.report.ok && triggerOk,
|
|
256
282
|
},
|
|
@@ -419,17 +445,47 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
419
445
|
if (command === 'add') return args.length === 2 ? { command: 'add', value: args[1]! } : undefined;
|
|
420
446
|
if (command === 'replay') return parseReplayArgs(args.slice(1));
|
|
421
447
|
if (command === 'build') return parseBuildArgs(args.slice(1));
|
|
422
|
-
if (command === 'deploy')
|
|
448
|
+
if (command === 'deploy') {
|
|
449
|
+
// The positional decides the form: an authored source deploys a hosted
|
|
450
|
+
// listener; a digest reference copies a sealed bundle into a file bucket.
|
|
451
|
+
const source = args.slice(1).find(a => !a.startsWith('-') && isAuthoredFlowPath(a));
|
|
452
|
+
return source !== undefined ? parseCloudDeployArgs(args.slice(1)) : parseDeployArgs(args.slice(1));
|
|
453
|
+
}
|
|
454
|
+
if (command === 'schedule') return parseCloudScheduleArgs(args.slice(1));
|
|
455
|
+
if (command === 'schedules') {
|
|
456
|
+
const rest = args.slice(1);
|
|
457
|
+
if (rest.length > 1 || (rest.length === 1 && rest[0] !== '--json')) return undefined;
|
|
458
|
+
return { command: 'schedules', json: rest.length === 1 };
|
|
459
|
+
}
|
|
460
|
+
if (command === 'unschedule') {
|
|
461
|
+
const rest = args.slice(1).filter(a => a !== '--json');
|
|
462
|
+
const json = args.length - 1 - rest.length;
|
|
463
|
+
if (json > 1 || rest.length !== 1 || rest[0]!.startsWith('-')) return undefined;
|
|
464
|
+
return { command: 'unschedule', scheduleId: rest[0]!, json: json === 1 };
|
|
465
|
+
}
|
|
466
|
+
if (command === 'undeploy') {
|
|
467
|
+
const rest = args.slice(1).filter(a => a !== '--json');
|
|
468
|
+
const json = args.length - 1 - rest.length;
|
|
469
|
+
if (json > 1 || rest.length !== 1 || rest[0]!.startsWith('-')) return undefined;
|
|
470
|
+
return { command: 'undeploy', agentId: rest[0]!, json: json === 1 };
|
|
471
|
+
}
|
|
472
|
+
if (command === 'deployments') {
|
|
473
|
+
const rest = args.slice(1);
|
|
474
|
+
if (rest.length > 1 || (rest.length === 1 && rest[0] !== '--json')) return undefined;
|
|
475
|
+
return { command: 'deployments', json: rest.length === 1 };
|
|
476
|
+
}
|
|
423
477
|
if (command === 'serve-webhook') return parseWebhookArgs(args.slice(1));
|
|
424
478
|
if (command === 'hn-monitor') return parseHnMonitorArgs(args.slice(1));
|
|
425
479
|
if (command === 'tick') return parseTickArgs(args.slice(1));
|
|
426
480
|
if (command === 'observer') return parseObserverArgs(args.slice(1));
|
|
481
|
+
if (command === 'sync') return parseSyncArgs(args.slice(1));
|
|
427
482
|
if (command !== 'check' && command !== 'run' && command !== 'resume') return undefined;
|
|
428
483
|
|
|
429
484
|
let json = false;
|
|
430
485
|
let watch = false;
|
|
431
486
|
let cloud = false;
|
|
432
487
|
let wait = false;
|
|
488
|
+
let syncCode = false;
|
|
433
489
|
let localAgent = false;
|
|
434
490
|
let allowHumanInfluenced = false;
|
|
435
491
|
let dataDir = DEFAULT_DATA_DIR;
|
|
@@ -443,10 +499,11 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
443
499
|
const positionals: string[] = [];
|
|
444
500
|
for (let index = 1; index < args.length; index += 1) {
|
|
445
501
|
const argument = args[index]!;
|
|
446
|
-
if (argument === '--cloud' || argument === '--wait') {
|
|
447
|
-
if (command !== 'run' || (argument === '--cloud' ? cloud : wait)) return undefined;
|
|
502
|
+
if (argument === '--cloud' || argument === '--wait' || argument === '--sync-code') {
|
|
503
|
+
if (command !== 'run' || (argument === '--cloud' ? cloud : argument === '--wait' ? wait : syncCode)) return undefined;
|
|
448
504
|
if (argument === '--cloud') cloud = true;
|
|
449
|
-
else wait = true;
|
|
505
|
+
else if (argument === '--wait') wait = true;
|
|
506
|
+
else syncCode = true;
|
|
450
507
|
continue;
|
|
451
508
|
}
|
|
452
509
|
if (argument === '--allow-human-influenced') {
|
|
@@ -520,13 +577,15 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
520
577
|
if (bucket !== undefined && (cloud || !parseDigestReference(positionals[0]!))) return undefined;
|
|
521
578
|
if (cloud) {
|
|
522
579
|
// `--cloud` submits the spec to Cloud, so every flag that only describes a
|
|
523
|
-
// local run --
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
|
|
580
|
+
// local run -- a data dir, a suppressed daemon, a local agent, a local
|
|
581
|
+
// observer-link opt-out -- describes nothing there and is refused rather
|
|
582
|
+
// than ignored. `--input` is the authored body's argument and travels with
|
|
583
|
+
// the source, so it is accepted exactly where a local run accepts it.
|
|
584
|
+
if (allowHumanInfluenced || sawDataDir || !spawn || localAgent || noObserverLink || reuseFromRunId !== undefined) return undefined;
|
|
585
|
+
if (sawInput && !isAuthoredFlowPath(positionals[0]!)) return undefined;
|
|
586
|
+
return { command: 'cloud-run', value: positionals[0]!, json, wait, input, syncCode };
|
|
528
587
|
}
|
|
529
|
-
if (wait) return undefined;
|
|
588
|
+
if (wait || syncCode) return undefined;
|
|
530
589
|
if (reuseFromRunId !== undefined && isAuthoredFlowPath(positionals[0]!)) return undefined;
|
|
531
590
|
|
|
532
591
|
if (command === 'run' && input !== undefined && !isAuthoredFlowPath(positionals[0]!)) return undefined;
|
|
@@ -579,6 +638,32 @@ function parseHnMonitorArgs(rest: readonly string[]): ParsedArgs | undefined {
|
|
|
579
638
|
* directory at all -- the mint is a pure Relaycast API round-trip. No
|
|
580
639
|
* positional argument, no other flags.
|
|
581
640
|
*/
|
|
641
|
+
/** `flows sync [--json] [--dir <path>] <run-id>`: apply a hosted run's patch to a local tree. */
|
|
642
|
+
function parseSyncArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
643
|
+
let json = false;
|
|
644
|
+
let root: string | undefined;
|
|
645
|
+
const positionals: string[] = [];
|
|
646
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
647
|
+
const argument = args[index]!;
|
|
648
|
+
if (argument === '--json') {
|
|
649
|
+
if (json) return undefined;
|
|
650
|
+
json = true;
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (argument === '--dir') {
|
|
654
|
+
const value = args[index + 1];
|
|
655
|
+
if (root !== undefined || value === undefined || value.startsWith('-')) return undefined;
|
|
656
|
+
root = value;
|
|
657
|
+
index += 1;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (argument.startsWith('-')) return undefined;
|
|
661
|
+
positionals.push(argument);
|
|
662
|
+
}
|
|
663
|
+
if (positionals.length !== 1) return undefined;
|
|
664
|
+
return { command: 'sync', runId: positionals[0]!, json, root: root ?? '.' };
|
|
665
|
+
}
|
|
666
|
+
|
|
582
667
|
function parseObserverArgs(rest: readonly string[]): ParsedArgs | undefined {
|
|
583
668
|
let dataDir = DEFAULT_DATA_DIR;
|
|
584
669
|
let sawDataDir = false;
|
|
@@ -689,6 +774,15 @@ function emitCheckReport(report: CheckReport, json: boolean, io: CliIo): void {
|
|
|
689
774
|
const vacuous = gate.acceptsAnyOutput === true ? ' [json_schema accepts any output]' : '';
|
|
690
775
|
io.stdout(`GATE step "${gate.stepId}" ${gate.checks.join('+')} from data (kernel, journal-replayable)${vacuous}`);
|
|
691
776
|
}
|
|
777
|
+
for (const schedule of report.schedules ?? []) {
|
|
778
|
+
const declared = schedule.cron !== undefined
|
|
779
|
+
? `cron "${schedule.cron}"${schedule.tz === undefined ? '' : ` tz ${schedule.tz}`}`
|
|
780
|
+
: `every ${schedule.intervalMs}ms`;
|
|
781
|
+
const local = schedule.localUnsupported !== undefined
|
|
782
|
+
? `Cloud only: ${schedule.localUnsupported}`
|
|
783
|
+
: `local: flows tick start --schedule-id ${schedule.scheduleId} --interval-ms ${schedule.intervalMs} --epoch-ms ${schedule.epochMs}`;
|
|
784
|
+
io.stdout(`SCHEDULE handler ${schedule.handler} ${declared} -> flows.tick schedule_id ${schedule.scheduleId} [${local}]`);
|
|
785
|
+
}
|
|
692
786
|
for (const resolution of report.resolutions) {
|
|
693
787
|
const config = resolution.source === 'project' && report.projectConfigPath !== undefined
|
|
694
788
|
? ` (${report.projectConfigPath})`
|
|
@@ -750,6 +844,7 @@ function diagnosticLabel(severity: string): string {
|
|
|
750
844
|
case 'warning': return 'WARNING';
|
|
751
845
|
case 'failure': return 'FAILED';
|
|
752
846
|
case 'parked': return 'PARKED';
|
|
847
|
+
case 'declined': return 'DECLINED';
|
|
753
848
|
default: return 'REFUSED';
|
|
754
849
|
}
|
|
755
850
|
}
|