@relayflows/sdk 2.0.16 → 2.0.18
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-flow-error.d.ts +21 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +14 -0
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +133 -3
- 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-human.d.ts +49 -0
- package/dist/authored-human.d.ts.map +1 -0
- package/dist/authored-human.js +97 -0
- package/dist/authored-human.js.map +1 -0
- package/dist/authored-node-entry.js +3 -2
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +52 -18
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root.d.ts.map +1 -1
- package/dist/authored-root.js +29 -1
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +14 -25
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/answer.d.ts +17 -0
- package/dist/cli/answer.d.ts.map +1 -0
- package/dist/cli/answer.js +89 -0
- package/dist/cli/answer.js.map +1 -0
- package/dist/cli/build.js +1 -1
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/check-triggers.d.ts.map +1 -1
- package/dist/cli/check-triggers.js +12 -0
- package/dist/cli/check-triggers.js.map +1 -1
- package/dist/cli/check.d.ts +25 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +14 -2
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-connect-cli.d.ts +54 -0
- package/dist/cli/cloud-connect-cli.d.ts.map +1 -0
- package/dist/cli/cloud-connect-cli.js +109 -0
- package/dist/cli/cloud-connect-cli.js.map +1 -0
- package/dist/cli/cloud-deploy.d.ts +3 -1
- package/dist/cli/cloud-deploy.d.ts.map +1 -1
- package/dist/cli/cloud-deploy.js +44 -6
- package/dist/cli/cloud-deploy.js.map +1 -1
- package/dist/cli/cloud-run.d.ts +2 -1
- package/dist/cli/cloud-run.d.ts.map +1 -1
- package/dist/cli/cloud-run.js +17 -3
- package/dist/cli/cloud-run.js.map +1 -1
- package/dist/cli/cloud-schedule.d.ts +31 -0
- package/dist/cli/cloud-schedule.d.ts.map +1 -0
- package/dist/cli/cloud-schedule.js +156 -0
- package/dist/cli/cloud-schedule.js.map +1 -0
- package/dist/cli/direct-run.d.ts.map +1 -1
- package/dist/cli/direct-run.js +6 -2
- package/dist/cli/direct-run.js.map +1 -1
- package/dist/cli/run.d.ts +22 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +39 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +148 -8
- package/dist/cli.js.map +1 -1
- package/dist/cloud-connect.d.ts +52 -0
- package/dist/cloud-connect.d.ts.map +1 -0
- package/dist/cloud-connect.js +138 -0
- package/dist/cloud-connect.js.map +1 -0
- package/dist/cloud-deploy.d.ts +19 -1
- package/dist/cloud-deploy.d.ts.map +1 -1
- package/dist/cloud-deploy.js +52 -2
- package/dist/cloud-deploy.js.map +1 -1
- package/dist/cloud-http.d.ts +17 -2
- package/dist/cloud-http.d.ts.map +1 -1
- package/dist/cloud-http.js +44 -8
- package/dist/cloud-http.js.map +1 -1
- package/dist/cloud-run.d.ts +30 -0
- package/dist/cloud-run.d.ts.map +1 -1
- package/dist/cloud-run.js +70 -27
- package/dist/cloud-run.js.map +1 -1
- package/dist/cloud-schedule.d.ts +55 -0
- package/dist/cloud-schedule.d.ts.map +1 -0
- package/dist/cloud-schedule.js +146 -0
- package/dist/cloud-schedule.js.map +1 -0
- package/dist/failure-kinds.d.ts +3 -3
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +4 -0
- package/dist/failure-kinds.js.map +1 -1
- package/dist/flow-requirements.d.ts +68 -0
- package/dist/flow-requirements.d.ts.map +1 -0
- package/dist/flow-requirements.js +152 -0
- package/dist/flow-requirements.js.map +1 -0
- 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/journal-client.d.ts +11 -0
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +13 -0
- package/dist/journal-client.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/progress.d.ts +2 -1
- package/dist/progress.d.ts.map +1 -1
- package/dist/progress.js +9 -3
- package/dist/progress.js.map +1 -1
- package/dist/protocol.d.ts +25 -1
- package/dist/protocol.d.ts.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/authored-flow-error.ts +30 -0
- package/src/authored-flow-executor.ts +141 -2
- package/src/authored-flow-lifecycle.ts +7 -0
- package/src/authored-flow-operation.ts +35 -10
- package/src/authored-human.ts +150 -0
- package/src/authored-node-entry.ts +3 -2
- package/src/authored-node-runner.ts +55 -18
- package/src/authored-root.ts +28 -1
- package/src/authored-worker-step.ts +15 -26
- package/src/cli/answer.ts +98 -0
- package/src/cli/build.ts +1 -1
- package/src/cli/check-triggers.ts +11 -0
- package/src/cli/check.ts +39 -2
- package/src/cli/cloud-connect-cli.ts +130 -0
- package/src/cli/cloud-deploy.ts +40 -6
- package/src/cli/cloud-run.ts +16 -4
- package/src/cli/cloud-schedule.ts +160 -0
- package/src/cli/direct-run.ts +6 -1
- package/src/cli/run.ts +53 -5
- package/src/cli.ts +134 -9
- package/src/cloud-connect.ts +187 -0
- package/src/cloud-deploy.ts +69 -3
- package/src/cloud-http.ts +52 -8
- package/src/cloud-run.ts +99 -26
- package/src/cloud-schedule.ts +175 -0
- package/src/failure-kinds.ts +4 -0
- package/src/flow-requirements.ts +201 -0
- package/src/index.ts +14 -0
- package/src/journal-client.ts +20 -0
- package/src/named-gate-lowering.ts +9 -0
- package/src/named-gates.ts +11 -1
- package/src/preflight.ts +132 -9
- package/src/progress.ts +11 -4
- package/src/protocol.ts +23 -0
- 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,160 @@
|
|
|
1
|
+
import { CloudFlowError } from '../cloud-http.js';
|
|
2
|
+
import { listCloudSchedules, scheduleInCloud, unscheduleInCloud, validateScheduleArgs, 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 { cliConnectPrompt, ensureFlowConnections, harnessRemedy } from './cloud-connect-cli.js';
|
|
6
|
+
import type { CliIo } from '../cli.js';
|
|
7
|
+
|
|
8
|
+
export interface CloudScheduleArgs {
|
|
9
|
+
command: 'schedule';
|
|
10
|
+
value: string;
|
|
11
|
+
cron: string | undefined;
|
|
12
|
+
every: string | undefined;
|
|
13
|
+
tz: string | undefined;
|
|
14
|
+
input: string | undefined;
|
|
15
|
+
name: string | undefined;
|
|
16
|
+
/** Refuse a missing integration instead of offering to connect it. */
|
|
17
|
+
noConnect: boolean;
|
|
18
|
+
json: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `flows schedule <flow.yaml|flow.ts> [--cron "<expr>" | --every <n><s|m|h|d>]
|
|
23
|
+
* [--tz <IANA>] [--input <json|file>] [--name <n>] [--no-connect] [--json]`
|
|
24
|
+
*
|
|
25
|
+
* With neither `--cron` nor `--every`, the flow's own `schedule.*` handler
|
|
26
|
+
* supplies the cron, so a declared schedule and its hosted registration
|
|
27
|
+
* cannot drift.
|
|
28
|
+
*/
|
|
29
|
+
export function parseCloudScheduleArgs(args: readonly string[]): CloudScheduleArgs | undefined {
|
|
30
|
+
let value: string | undefined;
|
|
31
|
+
const flags: Record<'cron' | 'every' | 'tz' | 'input' | 'name', string | undefined> = {
|
|
32
|
+
cron: undefined, every: undefined, tz: undefined, input: undefined, name: undefined,
|
|
33
|
+
};
|
|
34
|
+
let json = false;
|
|
35
|
+
let noConnect = false;
|
|
36
|
+
for (let i = 0; i < args.length; i++) {
|
|
37
|
+
const arg = args[i]!;
|
|
38
|
+
if (arg === '--json') {
|
|
39
|
+
if (json) return undefined;
|
|
40
|
+
json = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === '--no-connect') {
|
|
44
|
+
if (noConnect) return undefined;
|
|
45
|
+
noConnect = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (arg === '--cron' || arg === '--every' || arg === '--tz' || arg === '--input' || arg === '--name') {
|
|
49
|
+
const key = arg.slice(2) as keyof typeof flags;
|
|
50
|
+
const next = args[i + 1];
|
|
51
|
+
// A cron expression legitimately starts with `*`; only a flag-shaped value is refused.
|
|
52
|
+
if (flags[key] !== undefined || next === undefined || next.startsWith('--')) return undefined;
|
|
53
|
+
flags[key] = next;
|
|
54
|
+
i += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (arg.startsWith('-') || value !== undefined) return undefined;
|
|
58
|
+
value = arg;
|
|
59
|
+
}
|
|
60
|
+
if (value === undefined) return undefined;
|
|
61
|
+
if (flags.cron !== undefined && flags.every !== undefined) return undefined;
|
|
62
|
+
if (flags.input !== undefined && !isAuthoredFlowPath(value)) return undefined;
|
|
63
|
+
return { command: 'schedule', value, ...flags, noConnect, json };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function describe(schedule: CloudSchedule): string {
|
|
67
|
+
const last = schedule.lastTriggeredAt === undefined ? ''
|
|
68
|
+
: ` last ${schedule.lastTriggeredAt}${schedule.lastTriggerStatus === undefined ? '' : ` (${schedule.lastTriggerStatus})`}`;
|
|
69
|
+
return `${schedule.id} ${schedule.status} ${JSON.stringify(schedule.name)} cron "${schedule.cronExpression}" tz ${schedule.timezone}${last}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function runCloudScheduleCli(args: CloudScheduleArgs, io: CliIo): Promise<0 | 1 | 2> {
|
|
73
|
+
let harnesses: readonly string[] = [];
|
|
74
|
+
try {
|
|
75
|
+
let input: JsonValue | undefined;
|
|
76
|
+
let inputPresent = false;
|
|
77
|
+
if (isAuthoredFlowPath(args.value)) {
|
|
78
|
+
try {
|
|
79
|
+
input = snapshotJsonValue(parseDirectInput(args.input), 'Cloud authored input');
|
|
80
|
+
inputPresent = true;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof DirectInputError) throw new CloudFlowError('invalid_input', error.message);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
validateScheduleArgs({
|
|
87
|
+
...(args.cron === undefined ? {} : { cron: args.cron }),
|
|
88
|
+
...(args.every === undefined ? {} : { every: args.every }),
|
|
89
|
+
...(args.tz === undefined ? {} : { tz: args.tz }),
|
|
90
|
+
});
|
|
91
|
+
// Each fire runs the flow as this workspace, so what it needs connected
|
|
92
|
+
// is checked once here — after the arguments are known to be good, so a
|
|
93
|
+
// refused command never opens a browser first — and before the schedule exists.
|
|
94
|
+
const connections = await ensureFlowConnections({
|
|
95
|
+
path: args.value, prompt: cliConnectPrompt(io, { noConnect: args.noConnect, json: args.json }),
|
|
96
|
+
});
|
|
97
|
+
harnesses = connections?.requirements.harnesses ?? [];
|
|
98
|
+
const schedule = await scheduleInCloud({
|
|
99
|
+
flow: { path: args.value },
|
|
100
|
+
...(args.cron === undefined ? {} : { cron: args.cron }),
|
|
101
|
+
...(args.every === undefined ? {} : { every: args.every }),
|
|
102
|
+
...(args.tz === undefined ? {} : { tz: args.tz }),
|
|
103
|
+
...(args.name === undefined ? {} : { name: args.name }),
|
|
104
|
+
...(inputPresent ? { input } : {}),
|
|
105
|
+
});
|
|
106
|
+
if (args.json) {
|
|
107
|
+
io.stdout(JSON.stringify({ ok: true, ...schedule }));
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
io.stdout(`SCHEDULED ${describe(schedule)}`);
|
|
111
|
+
for (const provider of connections?.outcome.connected ?? []) io.stdout(` connected: ${provider}`);
|
|
112
|
+
io.stdout('Each fire submits this exact source through the same admission as flows run --cloud; list with: flows schedules');
|
|
113
|
+
return 0;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return reportFailure(error, args.json, io, harnesses);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function runCloudSchedulesCli({ json }: { json: boolean }, io: CliIo): Promise<0 | 1 | 2> {
|
|
120
|
+
try {
|
|
121
|
+
const schedules = await listCloudSchedules();
|
|
122
|
+
if (json) {
|
|
123
|
+
io.stdout(JSON.stringify({ ok: true, schedules }));
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
if (schedules.length === 0) io.stdout('No workflow schedules in this workspace.');
|
|
127
|
+
for (const schedule of schedules) io.stdout(describe(schedule));
|
|
128
|
+
return 0;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return reportFailure(error, json, io);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function runCloudUnscheduleCli(
|
|
135
|
+
{ scheduleId, json }: { scheduleId: string; json: boolean }, io: CliIo,
|
|
136
|
+
): Promise<0 | 1 | 2> {
|
|
137
|
+
try {
|
|
138
|
+
await unscheduleInCloud(scheduleId);
|
|
139
|
+
io.stdout(json ? JSON.stringify({ ok: true, scheduleId, deleted: true }) : `UNSCHEDULED ${scheduleId}`);
|
|
140
|
+
return 0;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return reportFailure(error, json, io);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function reportFailure(error: unknown, json: boolean, io: CliIo, harnesses: readonly string[] = []): 1 | 2 {
|
|
147
|
+
const code = error instanceof CloudFlowError ? error.code : 'cloud_schedule_failed';
|
|
148
|
+
let message = error instanceof Error ? error.message : 'Cloud schedule failed.';
|
|
149
|
+
message += harnessRemedy(error, harnesses);
|
|
150
|
+
// The schedule routes take a browser session or a `cli:auth` token.
|
|
151
|
+
if (error instanceof CloudFlowError && error.status === 403) {
|
|
152
|
+
message += ' Scheduling needs an interactive `cli:auth` credential: run `agent-relay cloud login`.';
|
|
153
|
+
}
|
|
154
|
+
if (json) io.stdout(JSON.stringify({ ok: false, code, message }));
|
|
155
|
+
else io.stderr(`${code}: ${message}`);
|
|
156
|
+
return error instanceof CloudFlowError
|
|
157
|
+
&& (['configuration', 'unsupported_source', 'invalid_input', 'integration_not_connected'].includes(error.code)
|
|
158
|
+
|| error.status === 403 || error.status === 401)
|
|
159
|
+
? 2 : 1;
|
|
160
|
+
}
|
package/src/cli/direct-run.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
AuthoredFlowExecutionError,
|
|
9
9
|
} from '../authored-flow-executor.js';
|
|
10
10
|
import { executeDurableAuthoredFlow } from '../authored-root.js';
|
|
11
|
+
import { AuthoredHumanParked } from '../authored-flow-error.js';
|
|
11
12
|
import { AuthoredFlowLoadError } from '../authored-flow-loader.js';
|
|
12
13
|
import { DirectInputError, parseDirectInput } from '../direct-input.js';
|
|
13
14
|
import { JournalClient } from '../journal-client.js';
|
|
@@ -15,6 +16,7 @@ import { inputFailureReport } from './check.js';
|
|
|
15
16
|
import { checkAuthoredTriggers } from './check-triggers.js';
|
|
16
17
|
import {
|
|
17
18
|
authoredCompletion,
|
|
19
|
+
authoredHumanParked,
|
|
18
20
|
authoredStepFailure,
|
|
19
21
|
connect,
|
|
20
22
|
emptyReport,
|
|
@@ -172,9 +174,12 @@ export async function runDirectFlow(
|
|
|
172
174
|
// diagnostic carried up from `classifyOutcome` already names the step, its
|
|
173
175
|
// exit code and its output tail; this branch is what lets it reach the
|
|
174
176
|
// terminal. `resumeFlow` takes the same branch, through the same helper.
|
|
175
|
-
if (error instanceof AuthoredFlowExecutionError && error.code === 'step_failed') {
|
|
177
|
+
if (error instanceof AuthoredFlowExecutionError && (error.code === 'step_failed' || error.code === 'gate_failed')) {
|
|
176
178
|
return authoredStepFailure('run', base, socketPath, error);
|
|
177
179
|
}
|
|
180
|
+
if (error instanceof AuthoredHumanParked) {
|
|
181
|
+
return authoredHumanParked('run', base, socketPath, error, { dataDir, localAgent: options.localAgent === true });
|
|
182
|
+
}
|
|
178
183
|
const runId = error instanceof AuthoredFlowExecutionError ? error.runId : undefined;
|
|
179
184
|
return protocolFailure('run', base, socketPath, error, runId);
|
|
180
185
|
} finally {
|
package/src/cli/run.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { parseDigestReference } from '../bundle-transport.js';
|
|
|
2
2
|
import { prepareDigestRun } from './run-digest.js';
|
|
3
3
|
import { reuseSummary } from './reuse.js';
|
|
4
4
|
import { resumeHelperEffect } from '../authored-helper-effect.js';
|
|
5
|
-
import { AuthoredFlowExecutionError } from '../authored-flow-error.js';
|
|
5
|
+
import { AuthoredFlowExecutionError, AuthoredHumanParked, type AuthoredHumanWait } from '../authored-flow-error.js';
|
|
6
|
+
import { answerCommand, resumeCommand } from '../authored-human.js';
|
|
6
7
|
import { join, resolve } from 'node:path';
|
|
7
8
|
import type { ProgressEvent } from '../progress.js';
|
|
8
9
|
import { toKernelSpec } from '../compile.js';
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
} from './check.js';
|
|
30
31
|
|
|
31
32
|
export type RunExitCode = 0 | 1 | 2 | 3;
|
|
32
|
-
export type RunCommand = 'run' | 'resume';
|
|
33
|
+
export type RunCommand = 'run' | 'resume' | 'answer';
|
|
33
34
|
|
|
34
35
|
export interface ParkedStep {
|
|
35
36
|
id: string;
|
|
@@ -53,6 +54,12 @@ export interface RunReport {
|
|
|
53
54
|
completedSteps?: number;
|
|
54
55
|
reuse?: { fromRunId: string; reusedSteps: number; executedSteps: number };
|
|
55
56
|
parkedStep?: ParkedStep;
|
|
57
|
+
/** The open `f.human` question a parked authored run is waiting on. */
|
|
58
|
+
humanWait?: AuthoredHumanWait;
|
|
59
|
+
/** `flows answer` only: the answer it recorded. */
|
|
60
|
+
answer?: { waitId: string; answer: boolean; note?: string };
|
|
61
|
+
/** The invocation that continues this run, when one is known. */
|
|
62
|
+
next?: string;
|
|
56
63
|
projectConfigPath?: string;
|
|
57
64
|
resolutions: CheckReport['resolutions'];
|
|
58
65
|
diagnostics: Array<CheckReport['diagnostics'][number] | RunDiagnostic>;
|
|
@@ -223,9 +230,12 @@ export async function resumeFlow(
|
|
|
223
230
|
// leaving it on `protocolFailure` meant `flows run` printed the evidence
|
|
224
231
|
// while `flows resume` still printed `protocol_error` and
|
|
225
232
|
// `RUN <id> unknown` for the identical failure.
|
|
226
|
-
if (error instanceof AuthoredFlowExecutionError && error.code === 'step_failed') {
|
|
233
|
+
if (error instanceof AuthoredFlowExecutionError && (error.code === 'step_failed' || error.code === 'gate_failed')) {
|
|
227
234
|
return authoredStepFailure('resume', base, socketPath, error, runId);
|
|
228
235
|
}
|
|
236
|
+
if (error instanceof AuthoredHumanParked) {
|
|
237
|
+
return authoredHumanParked('resume', base, socketPath, error, { dataDir, localAgent: options.localAgent === true });
|
|
238
|
+
}
|
|
229
239
|
if (!(error instanceof JournalProtocolError) || error.code !== 'run_not_found') {
|
|
230
240
|
return protocolFailure('resume', base, socketPath, error, runId);
|
|
231
241
|
}
|
|
@@ -287,10 +297,48 @@ export function authoredStepFailure(
|
|
|
287
297
|
completionReason: 'step_failed',
|
|
288
298
|
diagnostics: [...base.diagnostics, {
|
|
289
299
|
severity: 'failure',
|
|
290
|
-
|
|
300
|
+
// A predicate gate that judged false is a run failure with its own
|
|
301
|
+
// name, so the report says which kind of check the body did not pass.
|
|
302
|
+
kind: error.code === 'gate_failed' ? 'gate_failed' : 'step_failed',
|
|
291
303
|
// The `step_failed: ` prefix `AuthoredFlowExecutionError` adds is
|
|
292
304
|
// redundant once the diagnostic is labelled `[step_failed]`.
|
|
293
|
-
message: error.message.replace(/^step_failed: /, ''),
|
|
305
|
+
message: error.message.replace(/^(?:step_failed|gate_failed): /, ''),
|
|
306
|
+
}],
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* An authored body parked on an unanswered `f.human`: exit 3, like every
|
|
313
|
+
* other park, but the diagnostic names the question, who it is for, and the
|
|
314
|
+
* exact `flows answer` invocation — the run is waiting on a person, not on a
|
|
315
|
+
* worker. Shared by `run` and `resume` so the two never drift.
|
|
316
|
+
*/
|
|
317
|
+
export function authoredHumanParked(
|
|
318
|
+
command: RunCommand,
|
|
319
|
+
base: CheckReport | RunReport,
|
|
320
|
+
socketPath: string,
|
|
321
|
+
error: AuthoredHumanParked,
|
|
322
|
+
where: { dataDir?: string; localAgent: boolean },
|
|
323
|
+
): RunExecution {
|
|
324
|
+
const runId = error.runId!;
|
|
325
|
+
return {
|
|
326
|
+
exitCode: 3,
|
|
327
|
+
report: {
|
|
328
|
+
...fromBase(command, base),
|
|
329
|
+
ok: false,
|
|
330
|
+
runId,
|
|
331
|
+
socketPath,
|
|
332
|
+
status: 'parked',
|
|
333
|
+
parkedStep: { id: 'authored-root', type: 'agent' },
|
|
334
|
+
humanWait: error.wait,
|
|
335
|
+
diagnostics: [...base.diagnostics, {
|
|
336
|
+
severity: 'parked',
|
|
337
|
+
kind: 'run_parked',
|
|
338
|
+
message: `Run "${runId}" is waiting for ${error.wait.to} to answer ${error.wait.waitId}: `
|
|
339
|
+
+ `${JSON.stringify(error.wait.question)}\n`
|
|
340
|
+
+ `Answer with: ${answerCommand(runId, error.wait.waitId, where.dataDir)}\n`
|
|
341
|
+
+ `Then continue with: ${resumeCommand(runId, where.dataDir, where.localAgent)}`,
|
|
294
342
|
}],
|
|
295
343
|
},
|
|
296
344
|
};
|
package/src/cli.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { addPlugin } from './cli/add.js';
|
|
3
3
|
import { watchCheck } from './cli-watch.js';
|
|
4
4
|
import { checkHelperBody } from './cli/check-helper-body.js';
|
|
5
|
+
import { describeFlowRequirements } from './flow-requirements.js';
|
|
5
6
|
|
|
6
7
|
import { renderProgress, type ProgressEvent } from './progress.js';
|
|
7
8
|
import { realpathSync } from 'node:fs';
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
type RunProgress,
|
|
19
20
|
type RunReport,
|
|
20
21
|
} from './cli/run.js';
|
|
22
|
+
import { answerFlow } from './cli/answer.js';
|
|
21
23
|
import { checkAuthoredTriggers } from './cli/check-triggers.js';
|
|
22
24
|
import { parseWebhookArgs, runServeWebhook } from './cli/serve-webhook.js';
|
|
23
25
|
import { runDirectFlow } from './cli/direct-run.js';
|
|
@@ -26,6 +28,7 @@ import { checkTypeScriptFlow } from './cli/check-typescript.js';
|
|
|
26
28
|
import { runCloudCli } from './cli/cloud-run.js';
|
|
27
29
|
import { runCloudSyncCli } from './cli/cloud-sync.js';
|
|
28
30
|
import { parseCloudDeployArgs, runCloudDeployCli, runCloudDeploymentsCli, runCloudUndeployCli, type CloudDeployArgs } from './cli/cloud-deploy.js';
|
|
31
|
+
import { parseCloudScheduleArgs, runCloudScheduleCli, runCloudSchedulesCli, runCloudUnscheduleCli, type CloudScheduleArgs } from './cli/cloud-schedule.js';
|
|
29
32
|
import { isAuthoredFlowPath } from './direct-input.js';
|
|
30
33
|
import { parseDeployArgs, runDeploy, type DeployArgs } from './cli/deploy.js';
|
|
31
34
|
import { parseDigestReference } from './bundle-transport.js';
|
|
@@ -53,14 +56,18 @@ type ParsedArgs =
|
|
|
53
56
|
| BuildArgs
|
|
54
57
|
| DeployArgs
|
|
55
58
|
| { command: 'serve-webhook'; dataDir: string; port: number; admitted?: readonly string[] }
|
|
56
|
-
| { command: 'cloud-run'; value: string; json: boolean; wait: boolean; input: string | undefined; syncCode: boolean }
|
|
59
|
+
| { command: 'cloud-run'; value: string; json: boolean; wait: boolean; input: string | undefined; syncCode: boolean; noConnect: boolean }
|
|
57
60
|
| { command: 'sync'; runId: string; json: boolean; root: string }
|
|
58
61
|
| CloudDeployArgs
|
|
59
62
|
| { command: 'deployments'; json: boolean }
|
|
60
63
|
| { command: 'undeploy'; agentId: string; json: boolean }
|
|
64
|
+
| CloudScheduleArgs
|
|
65
|
+
| { command: 'schedules'; json: boolean }
|
|
66
|
+
| { command: 'unschedule'; scheduleId: string; json: boolean }
|
|
61
67
|
| { command: 'check'; json: boolean; watch: boolean; value: string }
|
|
62
68
|
| { 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 }
|
|
63
69
|
| { command: 'resume'; localAgent: boolean; dataDir: string; json: boolean; spawn: boolean; noObserverLink: boolean; allowHumanInfluenced: boolean; value: string }
|
|
70
|
+
| { command: 'answer'; dataDir: string; json: boolean; spawn: boolean; note: string | undefined; by: string | undefined; runId: string; waitId: string; answer: boolean }
|
|
64
71
|
| { command: 'observer'; dataDir: string }
|
|
65
72
|
| { command: 'hn-monitor'; sub: 'start'; dataDir: string; specPath: string; pollIntervalMs: number | undefined }
|
|
66
73
|
| { command: 'tick'; sub: 'start'; dataDir: string; specPath: string; scheduleId: string;
|
|
@@ -72,20 +79,24 @@ const USAGE = [
|
|
|
72
79
|
'flows add <helper-name|@flows/helper-name>',
|
|
73
80
|
'flows build [--out <dir>] <flow.yaml|flow.ts>',
|
|
74
81
|
'flows build --verify <bundle-dir>',
|
|
75
|
-
'flows deploy <flow.ts> --repo <owner/name> --on <provider>[:key=value,...] [--on ...] --approver <handle> [--agents claude[,codex]] [--name <name>] [--draft] [--json]',
|
|
82
|
+
'flows deploy <flow.ts> --repo <owner/name> --on <provider>[:key=value,...] [--on ...] --approver <handle> [--agents claude[,codex]] [--name <name>] [--draft] [--no-connect] [--json]',
|
|
76
83
|
'flows deployments [--json]',
|
|
77
84
|
'flows undeploy [--json] <deployment-id>',
|
|
85
|
+
'flows schedule <flow.yaml|flow.ts> [--cron "<expr>" | --every <n><s|m|h|d>] [--tz <IANA>] [--input <inline-json-or-file>] [--name <name>] [--no-connect] [--json]',
|
|
86
|
+
'flows schedules [--json]',
|
|
87
|
+
'flows unschedule [--json] <schedule-id>',
|
|
78
88
|
'flows deploy <flow>@sha256:<digest> --to <file-bucket-uri>',
|
|
79
89
|
'flows run <flow>@sha256:<digest> [--bucket <file-bucket-uri>] [--data-dir <dir>] [--json]',
|
|
80
90
|
'flows check [--watch] [--json] <flow.ts|flow.yaml|spec.json>',
|
|
81
91
|
'flows serve-webhook --data-dir <dir> --port <p> [--allow <name>[,<name>]]',
|
|
82
92
|
'flows run [--json] [--no-spawn] [--no-observer-link] [--data-dir <dir>] [--local-agent] [--reuse-from <run-id>] <flow.yaml|spec.json>',
|
|
83
|
-
'flows run --cloud [--json] [--wait] [--sync-code] <flow.yaml|spec.json>',
|
|
84
|
-
'flows run --cloud [--json] [--wait] [--sync-code] <flow.ts> --input <inline-json-or-file>',
|
|
93
|
+
'flows run --cloud [--json] [--wait] [--sync-code] [--no-connect] <flow.yaml|spec.json>',
|
|
94
|
+
'flows run --cloud [--json] [--wait] [--sync-code] [--no-connect] <flow.ts> --input <inline-json-or-file>',
|
|
85
95
|
'flows sync [--json] [--dir <path>] <run-id>',
|
|
86
96
|
'flows run [--json] [--no-spawn] [--no-observer-link] [--data-dir <dir>] [--local-agent] <flow.ts> --input <inline-json-or-file>',
|
|
87
97
|
'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>',
|
|
88
98
|
'flows resume [--allow-human-influenced] [--json] [--no-spawn] [--no-observer-link] [--data-dir <dir>] [--local-agent] <run-id>',
|
|
99
|
+
'flows answer [--json] [--no-spawn] [--data-dir <dir>] [--note <text>] [--by <identity>] <run-id> <wait-id> <yes|no>',
|
|
89
100
|
'flows replay [--allow-human-influenced] [--json] [--data-dir <dir>] <run-id> [--at <step-id>]',
|
|
90
101
|
'flows observer [--data-dir <dir>]',
|
|
91
102
|
'flows hn-monitor start [--data-dir <dir>] [--poll-interval-ms <n>] <spec.json>',
|
|
@@ -131,7 +142,19 @@ export async function runCli(
|
|
|
131
142
|
if (parsed.command === 'cloud-deploy') return runCloudDeployCli(parsed, io);
|
|
132
143
|
if (parsed.command === 'deployments') return runCloudDeploymentsCli(parsed, io);
|
|
133
144
|
if (parsed.command === 'undeploy') return runCloudUndeployCli(parsed, io);
|
|
145
|
+
if (parsed.command === 'schedule') return runCloudScheduleCli(parsed, io);
|
|
146
|
+
if (parsed.command === 'schedules') return runCloudSchedulesCli(parsed, io);
|
|
147
|
+
if (parsed.command === 'unschedule') return runCloudUnscheduleCli(parsed, io);
|
|
134
148
|
if (parsed.command === 'replay') return replayJournal(parsed, io);
|
|
149
|
+
if (parsed.command === 'answer') {
|
|
150
|
+
const execution = await answerFlow(parsed.runId, parsed.waitId, parsed.answer, parsed.dataDir, {
|
|
151
|
+
...(parsed.note === undefined ? {} : { note: parsed.note }),
|
|
152
|
+
...(parsed.by === undefined ? {} : { answeredBy: parsed.by }),
|
|
153
|
+
daemon: { spawn: parsed.spawn && spawnAllowedByEnv() },
|
|
154
|
+
});
|
|
155
|
+
emitRunReport(execution, parsed.json, io);
|
|
156
|
+
return execution.exitCode;
|
|
157
|
+
}
|
|
135
158
|
if (parsed.command === 'build') return runBuild(parsed, io);
|
|
136
159
|
if (parsed.command === 'deploy') return runDeploy(parsed, io);
|
|
137
160
|
|
|
@@ -266,6 +289,10 @@ async function checkAuthoredFlowComposed(path: string): Promise<{ report: CheckR
|
|
|
266
289
|
return {
|
|
267
290
|
report: {
|
|
268
291
|
...mcp.report,
|
|
292
|
+
...(triggers?.report.schedules === undefined ? {} : { schedules: triggers.report.schedules }),
|
|
293
|
+
// The authored definition sees helper flags, body use and `cli:`
|
|
294
|
+
// declarations; the compiled view underneath knows only its steps.
|
|
295
|
+
...(triggers?.report.requirements === undefined ? {} : { requirements: triggers.report.requirements }),
|
|
269
296
|
diagnostics: [...helper.report.diagnostics, ...mcp.report.diagnostics, ...triggerDiagnostics],
|
|
270
297
|
ok: helper.report.ok && mcp.report.ok && triggerOk,
|
|
271
298
|
},
|
|
@@ -440,6 +467,18 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
440
467
|
const source = args.slice(1).find(a => !a.startsWith('-') && isAuthoredFlowPath(a));
|
|
441
468
|
return source !== undefined ? parseCloudDeployArgs(args.slice(1)) : parseDeployArgs(args.slice(1));
|
|
442
469
|
}
|
|
470
|
+
if (command === 'schedule') return parseCloudScheduleArgs(args.slice(1));
|
|
471
|
+
if (command === 'schedules') {
|
|
472
|
+
const rest = args.slice(1);
|
|
473
|
+
if (rest.length > 1 || (rest.length === 1 && rest[0] !== '--json')) return undefined;
|
|
474
|
+
return { command: 'schedules', json: rest.length === 1 };
|
|
475
|
+
}
|
|
476
|
+
if (command === 'unschedule') {
|
|
477
|
+
const rest = args.slice(1).filter(a => a !== '--json');
|
|
478
|
+
const json = args.length - 1 - rest.length;
|
|
479
|
+
if (json > 1 || rest.length !== 1 || rest[0]!.startsWith('-')) return undefined;
|
|
480
|
+
return { command: 'unschedule', scheduleId: rest[0]!, json: json === 1 };
|
|
481
|
+
}
|
|
443
482
|
if (command === 'undeploy') {
|
|
444
483
|
const rest = args.slice(1).filter(a => a !== '--json');
|
|
445
484
|
const json = args.length - 1 - rest.length;
|
|
@@ -456,6 +495,7 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
456
495
|
if (command === 'tick') return parseTickArgs(args.slice(1));
|
|
457
496
|
if (command === 'observer') return parseObserverArgs(args.slice(1));
|
|
458
497
|
if (command === 'sync') return parseSyncArgs(args.slice(1));
|
|
498
|
+
if (command === 'answer') return parseAnswerArgs(args.slice(1));
|
|
459
499
|
if (command !== 'check' && command !== 'run' && command !== 'resume') return undefined;
|
|
460
500
|
|
|
461
501
|
let json = false;
|
|
@@ -463,6 +503,7 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
463
503
|
let cloud = false;
|
|
464
504
|
let wait = false;
|
|
465
505
|
let syncCode = false;
|
|
506
|
+
let noConnect = false;
|
|
466
507
|
let localAgent = false;
|
|
467
508
|
let allowHumanInfluenced = false;
|
|
468
509
|
let dataDir = DEFAULT_DATA_DIR;
|
|
@@ -476,11 +517,13 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
476
517
|
const positionals: string[] = [];
|
|
477
518
|
for (let index = 1; index < args.length; index += 1) {
|
|
478
519
|
const argument = args[index]!;
|
|
479
|
-
if (argument === '--cloud' || argument === '--wait' || argument === '--sync-code') {
|
|
480
|
-
if (command !== 'run'
|
|
520
|
+
if (argument === '--cloud' || argument === '--wait' || argument === '--sync-code' || argument === '--no-connect') {
|
|
521
|
+
if (command !== 'run') return undefined;
|
|
522
|
+
if (argument === '--cloud' ? cloud : argument === '--wait' ? wait : argument === '--sync-code' ? syncCode : noConnect) return undefined;
|
|
481
523
|
if (argument === '--cloud') cloud = true;
|
|
482
524
|
else if (argument === '--wait') wait = true;
|
|
483
|
-
else syncCode = true;
|
|
525
|
+
else if (argument === '--sync-code') syncCode = true;
|
|
526
|
+
else noConnect = true;
|
|
484
527
|
continue;
|
|
485
528
|
}
|
|
486
529
|
if (argument === '--allow-human-influenced') {
|
|
@@ -560,9 +603,9 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
560
603
|
// the source, so it is accepted exactly where a local run accepts it.
|
|
561
604
|
if (allowHumanInfluenced || sawDataDir || !spawn || localAgent || noObserverLink || reuseFromRunId !== undefined) return undefined;
|
|
562
605
|
if (sawInput && !isAuthoredFlowPath(positionals[0]!)) return undefined;
|
|
563
|
-
return { command: 'cloud-run', value: positionals[0]!, json, wait, input, syncCode };
|
|
606
|
+
return { command: 'cloud-run', value: positionals[0]!, json, wait, input, syncCode, noConnect };
|
|
564
607
|
}
|
|
565
|
-
if (wait || syncCode) return undefined;
|
|
608
|
+
if (wait || syncCode || noConnect) return undefined;
|
|
566
609
|
if (reuseFromRunId !== undefined && isAuthoredFlowPath(positionals[0]!)) return undefined;
|
|
567
610
|
|
|
568
611
|
if (command === 'run' && input !== undefined && !isAuthoredFlowPath(positionals[0]!)) return undefined;
|
|
@@ -573,6 +616,66 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
573
616
|
: { command, localAgent, dataDir, json, spawn, noObserverLink, allowHumanInfluenced, value: positionals[0]! };
|
|
574
617
|
}
|
|
575
618
|
|
|
619
|
+
/**
|
|
620
|
+
* `flows answer [--json] [--no-spawn] [--data-dir <dir>] [--note <text>] [--by <identity>] <run-id> <wait-id> <yes|no>`.
|
|
621
|
+
* The answer is a literal `yes`/`no` (also `true`/`false`) so a shell cannot
|
|
622
|
+
* hand the kernel an ambiguous word as a decision. `--by` records who answered
|
|
623
|
+
* when the invoker is relaying a person's decision (Cloud's answer route runs
|
|
624
|
+
* this inside the resumed sandbox with the caller's identity); it defaults to
|
|
625
|
+
* the OS user.
|
|
626
|
+
*/
|
|
627
|
+
function parseAnswerArgs(rest: readonly string[]): ParsedArgs | undefined {
|
|
628
|
+
let json = false;
|
|
629
|
+
let spawn = true;
|
|
630
|
+
let dataDir = DEFAULT_DATA_DIR;
|
|
631
|
+
let sawDataDir = false;
|
|
632
|
+
let note: string | undefined;
|
|
633
|
+
let by: string | undefined;
|
|
634
|
+
const positionals: string[] = [];
|
|
635
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
636
|
+
const argument = rest[index]!;
|
|
637
|
+
if (argument === '--json') {
|
|
638
|
+
if (json) return undefined;
|
|
639
|
+
json = true;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (argument === '--no-spawn') {
|
|
643
|
+
if (!spawn) return undefined;
|
|
644
|
+
spawn = false;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (argument === '--by') {
|
|
648
|
+
const value = rest[index + 1];
|
|
649
|
+
if (by !== undefined || value === undefined || value.startsWith('-') || value.trim() === '') return undefined;
|
|
650
|
+
by = value;
|
|
651
|
+
index += 1;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (argument === '--data-dir') {
|
|
655
|
+
const value = rest[index + 1];
|
|
656
|
+
if (sawDataDir || value === undefined || value.startsWith('-')) return undefined;
|
|
657
|
+
dataDir = value;
|
|
658
|
+
sawDataDir = true;
|
|
659
|
+
index += 1;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (argument === '--note') {
|
|
663
|
+
const value = rest[index + 1];
|
|
664
|
+
if (note !== undefined || value === undefined || value.startsWith('--')) return undefined;
|
|
665
|
+
note = value;
|
|
666
|
+
index += 1;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (argument.startsWith('-')) return undefined;
|
|
670
|
+
positionals.push(argument);
|
|
671
|
+
}
|
|
672
|
+
if (positionals.length !== 3) return undefined;
|
|
673
|
+
const [runId, waitId, word] = positionals as [string, string, string];
|
|
674
|
+
const answer = word === 'yes' || word === 'true' ? true : word === 'no' || word === 'false' ? false : undefined;
|
|
675
|
+
if (answer === undefined) return undefined;
|
|
676
|
+
return { command: 'answer', dataDir, json, spawn, note, by, runId, waitId, answer };
|
|
677
|
+
}
|
|
678
|
+
|
|
576
679
|
function parseHnMonitorArgs(rest: readonly string[]): ParsedArgs | undefined {
|
|
577
680
|
const sub = rest[0];
|
|
578
681
|
if (sub !== 'start') return undefined;
|
|
@@ -751,6 +854,15 @@ function emitCheckReport(report: CheckReport, json: boolean, io: CliIo): void {
|
|
|
751
854
|
const vacuous = gate.acceptsAnyOutput === true ? ' [json_schema accepts any output]' : '';
|
|
752
855
|
io.stdout(`GATE step "${gate.stepId}" ${gate.checks.join('+')} from data (kernel, journal-replayable)${vacuous}`);
|
|
753
856
|
}
|
|
857
|
+
for (const schedule of report.schedules ?? []) {
|
|
858
|
+
const declared = schedule.cron !== undefined
|
|
859
|
+
? `cron "${schedule.cron}"${schedule.tz === undefined ? '' : ` tz ${schedule.tz}`}`
|
|
860
|
+
: `every ${schedule.intervalMs}ms`;
|
|
861
|
+
const local = schedule.localUnsupported !== undefined
|
|
862
|
+
? `Cloud only: ${schedule.localUnsupported}`
|
|
863
|
+
: `local: flows tick start --schedule-id ${schedule.scheduleId} --interval-ms ${schedule.intervalMs} --epoch-ms ${schedule.epochMs}`;
|
|
864
|
+
io.stdout(`SCHEDULE handler ${schedule.handler} ${declared} -> flows.tick schedule_id ${schedule.scheduleId} [${local}]`);
|
|
865
|
+
}
|
|
754
866
|
for (const resolution of report.resolutions) {
|
|
755
867
|
const config = resolution.source === 'project' && report.projectConfigPath !== undefined
|
|
756
868
|
? ` (${report.projectConfigPath})`
|
|
@@ -758,6 +870,10 @@ function emitCheckReport(report: CheckReport, json: boolean, io: CliIo): void {
|
|
|
758
870
|
const model = resolution.model === undefined ? '' : ` model "${resolution.model}"`;
|
|
759
871
|
io.stdout(`RESOLVED step "${resolution.stepId}" cli "${resolution.cli}"${model} from ${resolution.source}${config}`);
|
|
760
872
|
}
|
|
873
|
+
// What the workspace must have connected before this flow can run there;
|
|
874
|
+
// the hosted verbs check the same list against Cloud before submitting.
|
|
875
|
+
const requires = report.requirements === undefined ? '' : describeFlowRequirements(report.requirements);
|
|
876
|
+
if (requires) io.stdout(`REQUIRES ${requires}`);
|
|
761
877
|
if (report.ok) io.stdout(`CHECK PASSED ${report.path ?? ''}`.trimEnd());
|
|
762
878
|
}
|
|
763
879
|
|
|
@@ -778,6 +894,15 @@ function emitRunReport(
|
|
|
778
894
|
return;
|
|
779
895
|
}
|
|
780
896
|
if (report.runId === undefined) return;
|
|
897
|
+
if (report.answer !== undefined) {
|
|
898
|
+
io.stdout(`ANSWERED ${report.runId} ${report.answer.waitId} ${report.answer.answer ? 'yes' : 'no'}`
|
|
899
|
+
+ (report.answer.note === undefined ? '' : ` (${report.answer.note})`));
|
|
900
|
+
if (report.next !== undefined) io.stdout(`Continue with: ${report.next}`);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
// A refused answer changed nothing about the run, so there is no run
|
|
904
|
+
// outcome to summarize; the refusal above is the whole report.
|
|
905
|
+
if (report.command === 'answer') return;
|
|
781
906
|
const completed = report.completedSteps === undefined ? '' : ` (${report.completedSteps} ${report.completedSteps === 1 ? 'step' : 'steps'})`;
|
|
782
907
|
const reason = report.completionReason === undefined
|
|
783
908
|
? ''
|