@relayflows/sdk 2.0.38 → 2.0.39
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/cli/cloud-deploy.d.ts +9 -1
- package/dist/cli/cloud-deploy.d.ts.map +1 -1
- package/dist/cli/cloud-deploy.js +24 -5
- package/dist/cli/cloud-deploy.js.map +1 -1
- package/dist/cli/cloud-versions.d.ts +22 -0
- package/dist/cli/cloud-versions.d.ts.map +1 -0
- package/dist/cli/cloud-versions.js +96 -0
- package/dist/cli/cloud-versions.js.map +1 -0
- package/dist/cli-commands.d.ts +27 -0
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +18 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -0
- package/dist/cli.js.map +1 -1
- package/dist/cloud-deploy.d.ts +17 -0
- package/dist/cloud-deploy.d.ts.map +1 -1
- package/dist/cloud-deploy.js +8 -1
- package/dist/cloud-deploy.js.map +1 -1
- package/dist/cloud-versions-wire.d.ts +25 -0
- package/dist/cloud-versions-wire.d.ts.map +1 -0
- package/dist/cloud-versions-wire.js +39 -0
- package/dist/cloud-versions-wire.js.map +1 -0
- package/dist/cloud-versions.d.ts +47 -0
- package/dist/cloud-versions.d.ts.map +1 -0
- package/dist/cloud-versions.js +131 -0
- package/dist/cloud-versions.js.map +1 -0
- package/package.json +2 -2
- package/src/cli/cloud-deploy.ts +21 -7
- package/src/cli/cloud-versions.ts +103 -0
- package/src/cli-commands.ts +18 -0
- package/src/cli.ts +15 -0
- package/src/cloud-deploy.ts +24 -3
- package/src/cloud-versions-wire.ts +58 -0
- package/src/cloud-versions.ts +179 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-versions.js","sourceRoot":"","sources":["../src/cloud-versions.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayflows/sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.39",
|
|
4
4
|
"description": "TypeScript-first authoring SDK for Relayflows. Compiles specs; speaks the journal protocol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
50
50
|
"@relayfile/adapter-core": "0.6.2",
|
|
51
51
|
"@relayfile/relay-helpers": "0.4.12",
|
|
52
|
-
"@relayflows/surface": "2.0.
|
|
52
|
+
"@relayflows/surface": "2.0.39",
|
|
53
53
|
"@types/js-yaml": "^4.0.9",
|
|
54
54
|
"acorn": "^8.18.0",
|
|
55
55
|
"ai-hist": "0.4.1",
|
package/src/cli/cloud-deploy.ts
CHANGED
|
@@ -4,13 +4,17 @@ import {
|
|
|
4
4
|
type FlowTriggerSource,
|
|
5
5
|
} from '../cloud-deploy.js';
|
|
6
6
|
import { describeFlowRequirements } from '../flow-requirements.js';
|
|
7
|
+
import { describeVersionChange } from '../cloud-versions-wire.js';
|
|
8
|
+
import { runCloudDeployVersionCli } from './cloud-versions.js';
|
|
7
9
|
import { cliConnectPrompt, flowRequirementsForPath, harnessRemedy } from './cloud-connect-cli.js';
|
|
8
10
|
import type { CliIo } from '../cli.js';
|
|
9
11
|
|
|
10
12
|
export interface CloudDeployArgs {
|
|
11
13
|
command: 'cloud-deploy';
|
|
12
14
|
value: string;
|
|
13
|
-
|
|
15
|
+
/** Update form: the flow (name or listener id) this source becomes the next version of. */
|
|
16
|
+
flow: string | undefined;
|
|
17
|
+
repo: string | undefined;
|
|
14
18
|
on: string[];
|
|
15
19
|
approver: string | undefined;
|
|
16
20
|
name: string | undefined;
|
|
@@ -25,6 +29,11 @@ export interface CloudDeployArgs {
|
|
|
25
29
|
/**
|
|
26
30
|
* `flows deploy <flow.ts> --repo <owner/name> --on <provider>[:k=v,…] [--on …]
|
|
27
31
|
* --approver <handle> [--name <n>] [--agents <list>] [--draft] [--no-connect] [--json]`
|
|
32
|
+
* `flows deploy <flow.ts> --flow <name-or-listener-id> [--plugin <ref>] [--no-connect] [--json]`
|
|
33
|
+
*
|
|
34
|
+
* The `--flow` form makes the source the next version of an existing flow and
|
|
35
|
+
* changes nothing else; listener settings given beside it are refused when run,
|
|
36
|
+
* by name, rather than silently ignored.
|
|
28
37
|
*
|
|
29
38
|
* Parsed here rather than in `parseDeployArgs` because the two `deploy` forms
|
|
30
39
|
* share nothing but the word: the digest form copies a sealed bundle into a
|
|
@@ -32,6 +41,7 @@ export interface CloudDeployArgs {
|
|
|
32
41
|
*/
|
|
33
42
|
export function parseCloudDeployArgs(args: readonly string[]): CloudDeployArgs | undefined {
|
|
34
43
|
let value: string | undefined;
|
|
44
|
+
let flow: string | undefined;
|
|
35
45
|
let repo: string | undefined;
|
|
36
46
|
let approver: string | undefined;
|
|
37
47
|
let name: string | undefined;
|
|
@@ -72,11 +82,12 @@ export function parseCloudDeployArgs(args: readonly string[]): CloudDeployArgs |
|
|
|
72
82
|
i += 1;
|
|
73
83
|
continue;
|
|
74
84
|
}
|
|
75
|
-
if (arg === '--repo' || arg === '--approver' || arg === '--name' || arg === '--on') {
|
|
85
|
+
if (arg === '--repo' || arg === '--approver' || arg === '--name' || arg === '--on' || arg === '--flow') {
|
|
76
86
|
const next = args[i + 1];
|
|
77
87
|
if (next === undefined || next.startsWith('-')) return undefined;
|
|
78
88
|
i += 1;
|
|
79
89
|
if (arg === '--on') { on.push(next); continue; }
|
|
90
|
+
if (arg === '--flow') { if (flow !== undefined) return undefined; flow = next; continue; }
|
|
80
91
|
if (arg === '--repo') { if (repo !== undefined) return undefined; repo = next; continue; }
|
|
81
92
|
if (arg === '--approver') { if (approver !== undefined) return undefined; approver = next; continue; }
|
|
82
93
|
if (name !== undefined) return undefined;
|
|
@@ -86,8 +97,9 @@ export function parseCloudDeployArgs(args: readonly string[]): CloudDeployArgs |
|
|
|
86
97
|
if (arg.startsWith('-') || value !== undefined) return undefined;
|
|
87
98
|
value = arg;
|
|
88
99
|
}
|
|
89
|
-
if (value === undefined
|
|
90
|
-
|
|
100
|
+
if (value === undefined) return undefined;
|
|
101
|
+
if (flow === undefined && (repo === undefined || on.length === 0)) return undefined;
|
|
102
|
+
return { command: 'cloud-deploy', value, flow, repo, on, approver, name, agents, draft, noConnect, json, plugins };
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
function describeSource(source: FlowTriggerSource): string {
|
|
@@ -96,6 +108,7 @@ function describeSource(source: FlowTriggerSource): string {
|
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
export async function runCloudDeployCli(args: CloudDeployArgs, io: CliIo): Promise<0 | 1 | 2> {
|
|
111
|
+
if (args.flow !== undefined) return runCloudDeployVersionCli({ ...args, flow: args.flow }, io);
|
|
99
112
|
const agents = args.agents === undefined ? undefined : parseAgentHarnessesOr(args.agents);
|
|
100
113
|
// What the source declares, so a harness refusal names the right remedy
|
|
101
114
|
// even when `--agents` was not given; the loader's own failure is reported
|
|
@@ -111,7 +124,7 @@ export async function runCloudDeployCli(args: CloudDeployArgs, io: CliIo): Promi
|
|
|
111
124
|
if (agents === undefined) harnesses = (await flowRequirementsForPath(args.value))?.harnesses ?? [];
|
|
112
125
|
const deployment = await deployToCloud({
|
|
113
126
|
path: args.value,
|
|
114
|
-
repository: parseRepository(args.repo),
|
|
127
|
+
repository: parseRepository(args.repo!),
|
|
115
128
|
sources: args.on.map(parseTriggerSource),
|
|
116
129
|
approver: args.approver,
|
|
117
130
|
draft: args.draft,
|
|
@@ -124,7 +137,8 @@ export async function runCloudDeployCli(args: CloudDeployArgs, io: CliIo): Promi
|
|
|
124
137
|
io.stdout(JSON.stringify({ ok: true, ...deployment }));
|
|
125
138
|
return 0;
|
|
126
139
|
}
|
|
127
|
-
io.stdout(`${deployment.status === 'draft' ? 'SAVED' : 'DEPLOYED'} ${deployment.agentId} ${deployment.status}`
|
|
140
|
+
io.stdout(`${deployment.status === 'draft' ? 'SAVED' : 'DEPLOYED'} ${deployment.agentId} ${deployment.status}`
|
|
141
|
+
+ (deployment.version === undefined ? '' : ` · ${describeVersionChange(deployment.version)}`));
|
|
128
142
|
io.stdout(` flow: ${deployment.name} (${args.value}, sha256 ${deployment.sourceSha256.slice(0, 12)})`);
|
|
129
143
|
io.stdout(` repository: ${deployment.repository.owner}/${deployment.repository.name}`);
|
|
130
144
|
for (const source of deployment.sources) io.stdout(` on: ${describeSource(source)}`);
|
|
@@ -181,7 +195,7 @@ export async function runCloudUndeployCli({ agentId, json }: { agentId: string;
|
|
|
181
195
|
}
|
|
182
196
|
}
|
|
183
197
|
|
|
184
|
-
function reportCloudFailure(error: unknown, json: boolean, io: CliIo, harnesses: readonly string[] = []): 1 | 2 {
|
|
198
|
+
export function reportCloudFailure(error: unknown, json: boolean, io: CliIo, harnesses: readonly string[] = []): 1 | 2 {
|
|
185
199
|
const code = error instanceof CloudFlowError ? error.code : 'cloud_deploy_failed';
|
|
186
200
|
let message = error instanceof Error ? error.message : 'Cloud deploy failed.';
|
|
187
201
|
// Cloud names the missing coding-agent credential on activation; say how it is connected.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { CloudFlowError } from '../cloud-http.js';
|
|
2
|
+
import {
|
|
3
|
+
activateCloudFlowVersion, deployVersionToCloud, getCloudListener, resolveCloudFlow,
|
|
4
|
+
} from '../cloud-versions.js';
|
|
5
|
+
import { describeVersionChange } from '../cloud-versions-wire.js';
|
|
6
|
+
import { describeFlowRequirements } from '../flow-requirements.js';
|
|
7
|
+
import { cliConnectPrompt } from './cloud-connect-cli.js';
|
|
8
|
+
import { reportCloudFailure, type CloudDeployArgs } from './cloud-deploy.js';
|
|
9
|
+
import type { CliIo } from '../cli.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `flows deploy <flow.ts> --flow <name-or-id>`: the source becomes the next
|
|
13
|
+
* version of that flow. Listener settings are not part of a version, so a
|
|
14
|
+
* flag that would change one is refused by name instead of being dropped.
|
|
15
|
+
*/
|
|
16
|
+
export async function runCloudDeployVersionCli(
|
|
17
|
+
args: CloudDeployArgs & { flow: string }, io: CliIo,
|
|
18
|
+
): Promise<0 | 1 | 2> {
|
|
19
|
+
try {
|
|
20
|
+
const settings = [
|
|
21
|
+
args.repo === undefined ? undefined : '--repo',
|
|
22
|
+
args.on.length === 0 ? undefined : '--on',
|
|
23
|
+
args.approver === undefined ? undefined : '--approver',
|
|
24
|
+
args.name === undefined ? undefined : '--name',
|
|
25
|
+
args.agents === undefined ? undefined : '--agents',
|
|
26
|
+
args.draft ? '--draft' : undefined,
|
|
27
|
+
].filter((flag): flag is string => flag !== undefined);
|
|
28
|
+
if (settings.length > 0) {
|
|
29
|
+
throw new CloudFlowError('invalid_input',
|
|
30
|
+
`--flow deploys a new version of the flow's source only; ${settings.join(', ')} stay on the listener. `
|
|
31
|
+
+ 'Drop them, or change them in the Cloud dashboard.');
|
|
32
|
+
}
|
|
33
|
+
const connect = cliConnectPrompt(io, { noConnect: args.noConnect, json: args.json });
|
|
34
|
+
const deployment = await deployVersionToCloud({
|
|
35
|
+
path: args.value,
|
|
36
|
+
flow: args.flow,
|
|
37
|
+
...(connect === undefined ? {} : { connect }),
|
|
38
|
+
...(args.plugins.length === 0 ? {} : { plugins: args.plugins }),
|
|
39
|
+
});
|
|
40
|
+
if (args.json) {
|
|
41
|
+
io.stdout(JSON.stringify({ ok: true, ...deployment }));
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
io.stdout(`${deployment.status === 'draft' ? 'SAVED' : 'DEPLOYED'} ${deployment.agentId} ${deployment.status}`
|
|
45
|
+
+ ` · ${describeVersionChange(deployment.version)}`);
|
|
46
|
+
io.stdout(` flow: ${deployment.name} (${args.value}, sha256 ${deployment.sourceSha256.slice(0, 12)})`);
|
|
47
|
+
const requires = describeFlowRequirements(deployment.requirements);
|
|
48
|
+
if (requires) io.stdout(` requires: ${requires}`);
|
|
49
|
+
for (const provider of deployment.connected) io.stdout(` connected: ${provider}`);
|
|
50
|
+
io.stdout('New runs launch this version; runs already started finish on theirs. History: flows versions '
|
|
51
|
+
+ JSON.stringify(deployment.name));
|
|
52
|
+
return 0;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return reportCloudFailure(error, args.json, io);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `flows versions <name-or-id>`: newest first, the active one marked. */
|
|
59
|
+
export async function runCloudVersionsCli(
|
|
60
|
+
{ flow, json }: { flow: string; json: boolean }, io: CliIo,
|
|
61
|
+
): Promise<0 | 1 | 2> {
|
|
62
|
+
try {
|
|
63
|
+
const listener = await getCloudListener(await resolveCloudFlow(flow));
|
|
64
|
+
if (json) {
|
|
65
|
+
io.stdout(JSON.stringify({
|
|
66
|
+
ok: true, agentId: listener.agentId, name: listener.name,
|
|
67
|
+
activeVersion: listener.activeVersion?.version ?? null, versions: listener.versions,
|
|
68
|
+
}));
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
if (listener.versions.length === 0) {
|
|
72
|
+
io.stdout(`${listener.agentId} ${JSON.stringify(listener.name)} has no recorded versions.`);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
io.stdout(`${listener.agentId} ${JSON.stringify(listener.name)} ${listener.status}`);
|
|
76
|
+
for (const version of listener.versions) {
|
|
77
|
+
const active = version.version === listener.activeVersion?.version ? ' (active)' : '';
|
|
78
|
+
io.stdout(` version ${version.version}${active} ${version.createdAt} ${version.origin} sha256 ${version.sourceSha256.slice(0, 12)}`);
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return reportCloudFailure(error, json, io);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** `flows rollback <name-or-id> <version>`: move the pointer; later versions stay recorded. */
|
|
87
|
+
export async function runCloudRollbackCli(
|
|
88
|
+
{ flow, version, json }: { flow: string; version: string; json: boolean }, io: CliIo,
|
|
89
|
+
): Promise<0 | 1 | 2> {
|
|
90
|
+
try {
|
|
91
|
+
// Cloud numbers versions up to nine digits; anything longer cannot exist.
|
|
92
|
+
if (!/^[1-9][0-9]{0,8}$/u.test(version)) {
|
|
93
|
+
throw new CloudFlowError('invalid_input', `A version is a positive whole number, got "${version}".`);
|
|
94
|
+
}
|
|
95
|
+
const result = await activateCloudFlowVersion(flow, Number(version));
|
|
96
|
+
io.stdout(json
|
|
97
|
+
? JSON.stringify({ ok: true, ...result })
|
|
98
|
+
: `ACTIVATED ${result.agentId} ${result.status} · ${describeVersionChange(result.version)}`);
|
|
99
|
+
return 0;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return reportCloudFailure(error, json, io);
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/cli-commands.ts
CHANGED
|
@@ -163,11 +163,29 @@ export const CLI_VERBS = [
|
|
|
163
163
|
{ flags: '--name <name>', description: 'Name for the hosted listener' },
|
|
164
164
|
{ flags: '--draft', description: 'Create the listener without activating it' },
|
|
165
165
|
{ flags: '--plugin <ref>', description: 'Send-only GitHub flow-extension ref; repeatable. Does not write flows.json' },
|
|
166
|
+
{ flags: '--flow <name|listener-id>', description: 'Deploy the source as the next version of this flow; its settings stay unchanged' },
|
|
166
167
|
NO_CONNECT_OPTION,
|
|
167
168
|
JSON_OPTION,
|
|
168
169
|
],
|
|
169
170
|
variants: ['deploy', 'cloud-deploy'],
|
|
170
171
|
},
|
|
172
|
+
{
|
|
173
|
+
name: 'versions',
|
|
174
|
+
description: 'List a hosted flow’s source versions, the active one marked',
|
|
175
|
+
args: [{ name: 'flow', description: 'Flow name or listener id', required: true }],
|
|
176
|
+
options: [JSON_OPTION],
|
|
177
|
+
variants: ['versions'],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'rollback',
|
|
181
|
+
description: 'Make an earlier (or later) recorded version of a hosted flow the active one',
|
|
182
|
+
args: [
|
|
183
|
+
{ name: 'flow', description: 'Flow name or listener id', required: true },
|
|
184
|
+
{ name: 'version', description: 'Version number to activate', required: true },
|
|
185
|
+
],
|
|
186
|
+
options: [JSON_OPTION],
|
|
187
|
+
variants: ['rollback'],
|
|
188
|
+
},
|
|
171
189
|
{
|
|
172
190
|
name: 'deployments',
|
|
173
191
|
description: 'List this workspace’s hosted trigger listeners',
|
package/src/cli.ts
CHANGED
|
@@ -44,6 +44,7 @@ import { checkTypeScriptFlow } from './cli/check-typescript.js';
|
|
|
44
44
|
import { runCloudCli } from './cli/cloud-run.js';
|
|
45
45
|
import { runCloudSyncCli } from './cli/cloud-sync.js';
|
|
46
46
|
import { parseCloudDeployArgs, runCloudDeployCli, runCloudDeploymentsCli, runCloudUndeployCli, type CloudDeployArgs } from './cli/cloud-deploy.js';
|
|
47
|
+
import { runCloudRollbackCli, runCloudVersionsCli } from './cli/cloud-versions.js';
|
|
47
48
|
import { parseCloudScheduleArgs, runCloudScheduleCli, runCloudSchedulesCli, runCloudUnscheduleCli, type CloudScheduleArgs } from './cli/cloud-schedule.js';
|
|
48
49
|
import { isAuthoredFlowPath } from './direct-input.js';
|
|
49
50
|
import { parseDeployArgs, runDeploy, type DeployArgs } from './cli/deploy.js';
|
|
@@ -97,6 +98,8 @@ export type ParsedArgs =
|
|
|
97
98
|
| CloudDeployArgs
|
|
98
99
|
| { command: 'deployments'; json: boolean }
|
|
99
100
|
| { command: 'undeploy'; agentId: string; json: boolean }
|
|
101
|
+
| { command: 'versions'; flow: string; json: boolean }
|
|
102
|
+
| { command: 'rollback'; flow: string; version: string; json: boolean }
|
|
100
103
|
| CloudScheduleArgs
|
|
101
104
|
| { command: 'schedules'; json: boolean }
|
|
102
105
|
| { command: 'unschedule'; scheduleId: string; json: boolean }
|
|
@@ -123,7 +126,10 @@ const USAGE = [
|
|
|
123
126
|
'flows build [--out <dir>] <flow.yaml|flow.ts>',
|
|
124
127
|
'flows build --verify <bundle-dir>',
|
|
125
128
|
'flows deploy <flow.ts> --repo <owner/name> --on <provider>[:key=value,...] [--on ...] --approver <handle> [--agents claude[,codex]] [--name <name>] [--draft] [--plugin <ref>] [--no-connect] [--json]',
|
|
129
|
+
'flows deploy <flow.ts> --flow <name|listener-id> [--plugin <ref>] [--no-connect] [--json]',
|
|
126
130
|
'flows deployments [--json]',
|
|
131
|
+
'flows versions [--json] <name|listener-id>',
|
|
132
|
+
'flows rollback [--json] <name|listener-id> <version>',
|
|
127
133
|
'flows undeploy [--json] <deployment-id>',
|
|
128
134
|
'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]',
|
|
129
135
|
'flows schedules [--json]',
|
|
@@ -244,6 +250,8 @@ export async function runCli(
|
|
|
244
250
|
if (parsed.command === 'cloud-deploy') return runCloudDeployCli(parsed, io);
|
|
245
251
|
if (parsed.command === 'deployments') return runCloudDeploymentsCli(parsed, io);
|
|
246
252
|
if (parsed.command === 'undeploy') return runCloudUndeployCli(parsed, io);
|
|
253
|
+
if (parsed.command === 'versions') return runCloudVersionsCli(parsed, io);
|
|
254
|
+
if (parsed.command === 'rollback') return runCloudRollbackCli(parsed, io);
|
|
247
255
|
if (parsed.command === 'schedule') return runCloudScheduleCli(parsed, io);
|
|
248
256
|
if (parsed.command === 'schedules') return runCloudSchedulesCli(parsed, io);
|
|
249
257
|
if (parsed.command === 'unschedule') return runCloudUnscheduleCli(parsed, io);
|
|
@@ -631,6 +639,13 @@ function parseArgs(args: readonly string[]): ParsedArgs | undefined {
|
|
|
631
639
|
if (json > 1 || rest.length !== 1 || rest[0]!.startsWith('-')) return undefined;
|
|
632
640
|
return { command: 'undeploy', agentId: rest[0]!, json: json === 1 };
|
|
633
641
|
}
|
|
642
|
+
if (command === 'versions' || command === 'rollback') {
|
|
643
|
+
const rest = args.slice(1).filter(a => a !== '--json');
|
|
644
|
+
const json = args.length - 1 - rest.length;
|
|
645
|
+
if (json > 1 || rest.some(a => a.startsWith('-'))) return undefined;
|
|
646
|
+
if (command === 'versions') return rest.length === 1 ? { command, flow: rest[0]!, json: json === 1 } : undefined;
|
|
647
|
+
return rest.length === 2 ? { command, flow: rest[0]!, version: rest[1]!, json: json === 1 } : undefined;
|
|
648
|
+
}
|
|
634
649
|
if (command === 'deployments') {
|
|
635
650
|
const rest = args.slice(1);
|
|
636
651
|
if (rest.length > 1 || (rest.length === 1 && rest[0] !== '--json')) return undefined;
|
package/src/cloud-deploy.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from './flow-requirements.js';
|
|
12
12
|
import { readProjectConfig } from './cli/check.js';
|
|
13
13
|
import { assertNoUseDependencies, collectExtensionSubmissions } from './flow-extension-submit.js';
|
|
14
|
+
import { parseVersionChange, type CloudFlowVersionChange } from './cloud-versions-wire.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Hosted listener deployment: the CLI form of the agentrelay.com onboarding's
|
|
@@ -104,6 +105,8 @@ export interface CloudDeployment {
|
|
|
104
105
|
requirements: FlowRequirements;
|
|
105
106
|
/** Integrations connected through the prompt during this deploy. */
|
|
106
107
|
connected: string[];
|
|
108
|
+
/** The listener's active version after this deploy; absent from a Cloud without versions. */
|
|
109
|
+
version?: CloudFlowVersionChange;
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
export function parseRepository(value: string): { owner: string; name: string } {
|
|
@@ -165,9 +168,18 @@ export function parseTriggerSource(value: string): FlowTriggerSource {
|
|
|
165
168
|
return { provider: provider as FlowTriggerProvider, settings };
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
/** One authored source, read and loaded the way every hosted deploy sends it. */
|
|
172
|
+
export interface DeploySource {
|
|
173
|
+
bytes: Buffer;
|
|
174
|
+
source: string;
|
|
175
|
+
definition: ReturnType<Awaited<ReturnType<typeof loadAuthoredFlow>>['getDefinition']>;
|
|
176
|
+
extensions: Awaited<ReturnType<typeof collectExtensionSubmissions>>;
|
|
177
|
+
projectCli: string | undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function loadDeploySource(
|
|
181
|
+
input: { path: string; plugins?: readonly string[] },
|
|
182
|
+
): Promise<DeploySource> {
|
|
171
183
|
if (!/\.flow\.ts$/iu.test(input.path)) {
|
|
172
184
|
throw new CloudFlowError('unsupported_source', 'flows deploy takes one authored .flow.ts source.');
|
|
173
185
|
}
|
|
@@ -204,6 +216,13 @@ export async function deployToCloud(
|
|
|
204
216
|
} catch {
|
|
205
217
|
projectCli = undefined;
|
|
206
218
|
}
|
|
219
|
+
return { bytes, source, definition, extensions, projectCli };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function deployToCloud(
|
|
223
|
+
input: DeployToCloudInput, options: CloudConnectionOptions = {},
|
|
224
|
+
): Promise<CloudDeployment> {
|
|
225
|
+
const { bytes, source, definition, extensions, projectCli } = await loadDeploySource(input);
|
|
207
226
|
if (input.sources.length === 0 || input.sources.length > 10) {
|
|
208
227
|
throw new CloudFlowError('invalid_input', 'Give between one and ten --on trigger sources.');
|
|
209
228
|
}
|
|
@@ -274,11 +293,13 @@ export async function deployToCloud(
|
|
|
274
293
|
if (!isCloudRecord(result) || typeof result.agentId !== 'string' || typeof result.status !== 'string') {
|
|
275
294
|
throw new CloudFlowError('invalid_response', 'Cloud did not return a deployment.');
|
|
276
295
|
}
|
|
296
|
+
const version = parseVersionChange(result.version);
|
|
277
297
|
return {
|
|
278
298
|
agentId: result.agentId, name, status: result.status,
|
|
279
299
|
repository: input.repository, sources,
|
|
280
300
|
sourceSha256: createHash('sha256').update(bytes).digest('hex'),
|
|
281
301
|
requirements, connected,
|
|
302
|
+
...(version === undefined ? {} : { version }),
|
|
282
303
|
};
|
|
283
304
|
}
|
|
284
305
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isCloudRecord } from './cloud-http.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a Cloud write did to a listener's active version (cloud#4115).
|
|
5
|
+
* `reactivated`: the bytes match an earlier version, so the pointer moved to
|
|
6
|
+
* it without a new row, and `version` can be LOWER than `previousVersion`.
|
|
7
|
+
*/
|
|
8
|
+
export interface CloudFlowVersionChange {
|
|
9
|
+
version: number;
|
|
10
|
+
previousVersion: number | null;
|
|
11
|
+
change: 'created' | 'reactivated' | 'unchanged';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CloudFlowVersion {
|
|
15
|
+
version: number;
|
|
16
|
+
sourceSha256: string;
|
|
17
|
+
origin: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CHANGES = ['created', 'reactivated', 'unchanged'] as const;
|
|
22
|
+
|
|
23
|
+
function isVersionNumber(value: unknown): value is number {
|
|
24
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Undefined for a Cloud that predates versions or a malformed field. */
|
|
28
|
+
export function parseVersionChange(value: unknown): CloudFlowVersionChange | undefined {
|
|
29
|
+
if (!isCloudRecord(value) || !isVersionNumber(value.version)
|
|
30
|
+
|| !(value.previousVersion === null || isVersionNumber(value.previousVersion))
|
|
31
|
+
|| !(CHANGES as readonly unknown[]).includes(value.change)) return undefined;
|
|
32
|
+
return {
|
|
33
|
+
version: value.version,
|
|
34
|
+
previousVersion: value.previousVersion as number | null,
|
|
35
|
+
change: value.change as CloudFlowVersionChange['change'],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseVersion(value: unknown): CloudFlowVersion | undefined {
|
|
40
|
+
if (!isCloudRecord(value) || !isVersionNumber(value.version) || typeof value.sourceSha256 !== 'string'
|
|
41
|
+
|| typeof value.origin !== 'string' || typeof value.createdAt !== 'string') return undefined;
|
|
42
|
+
return { version: value.version, sourceSha256: value.sourceSha256, origin: value.origin, createdAt: value.createdAt };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* `version 4 (was 3)`; a rollback by content says the number went down, so
|
|
47
|
+
* nobody reads v2-after-v3 as a failed deploy.
|
|
48
|
+
*/
|
|
49
|
+
export function describeVersionChange(change: CloudFlowVersionChange): string {
|
|
50
|
+
if (change.change === 'unchanged') return `version ${change.version} (unchanged)`;
|
|
51
|
+
const was = change.previousVersion === null ? '' : ` (was ${change.previousVersion}`;
|
|
52
|
+
if (change.change === 'reactivated') {
|
|
53
|
+
const down = change.previousVersion !== null && change.version < change.previousVersion
|
|
54
|
+
? '; active version went down' : '';
|
|
55
|
+
return `re-activated version ${change.version}${was}${down}${was ? ')' : ''}`;
|
|
56
|
+
}
|
|
57
|
+
return `version ${change.version}${was}${was ? ')' : ''}`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { ensureIntegrationsConnected, type ConnectPrompt } from './cloud-connect.js';
|
|
3
|
+
import {
|
|
4
|
+
CloudFlowError, cloudFetch, cloudRequest, isCloudRecord, type CloudConnectionOptions,
|
|
5
|
+
} from './cloud-http.js';
|
|
6
|
+
import {
|
|
7
|
+
FLOW_AGENT_HARNESSES, listCloudDeployments, loadDeploySource, type FlowTriggerSource,
|
|
8
|
+
} from './cloud-deploy.js';
|
|
9
|
+
import {
|
|
10
|
+
parseVersion, parseVersionChange, type CloudFlowVersion, type CloudFlowVersionChange,
|
|
11
|
+
} from './cloud-versions-wire.js';
|
|
12
|
+
import { flowRequirements, mergeFlowExtensionRequirements, type FlowRequirements } from './flow-requirements.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Versions of a hosted listener (AgentWorkforce/cloud#4115). A deployed
|
|
16
|
+
* flow's source is immutable: `flows deploy <file> --flow <name-or-id>` makes
|
|
17
|
+
* the next version and moves the listener's pointer to it, leaving the
|
|
18
|
+
* listener's repository, triggers, approver, agents and run budget untouched;
|
|
19
|
+
* `flows rollback` moves the pointer to any recorded version.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const LISTENER_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
23
|
+
|
|
24
|
+
export interface CloudListener {
|
|
25
|
+
agentId: string;
|
|
26
|
+
name: string;
|
|
27
|
+
status: string;
|
|
28
|
+
repository: { owner: string; name: string; host?: 'gitlab' };
|
|
29
|
+
sources: FlowTriggerSource[];
|
|
30
|
+
activeVersion: CloudFlowVersion | null;
|
|
31
|
+
versions: CloudFlowVersion[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A listener id as given, else the one deployment in the workspace with that name. */
|
|
35
|
+
export async function resolveCloudFlow(flow: string, options: CloudConnectionOptions = {}): Promise<string> {
|
|
36
|
+
const wanted = flow.trim();
|
|
37
|
+
if (!wanted) throw new CloudFlowError('invalid_input', '--flow needs a flow name or listener id.');
|
|
38
|
+
if (LISTENER_UUID.test(wanted)) return wanted.toLowerCase();
|
|
39
|
+
const deployments = await listCloudDeployments(options);
|
|
40
|
+
// Any listener id the workspace lists, before names: ids are not all uuids.
|
|
41
|
+
const byId = deployments.find(deployment => deployment.agentId === wanted);
|
|
42
|
+
if (byId) return byId.agentId;
|
|
43
|
+
const matches = deployments.filter(deployment => deployment.name.toLowerCase() === wanted.toLowerCase());
|
|
44
|
+
if (matches.length === 1) return matches[0]!.agentId;
|
|
45
|
+
if (matches.length === 0) {
|
|
46
|
+
throw new CloudFlowError('invalid_input', `No flow named "${wanted}" in this workspace; list them with: flows deployments`);
|
|
47
|
+
}
|
|
48
|
+
throw new CloudFlowError('invalid_input',
|
|
49
|
+
`More than one flow is named "${wanted}" (${matches.map(match => match.agentId).join(', ')}); pass the listener id.`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function getCloudListener(agentId: string, options: CloudConnectionOptions = {}): Promise<CloudListener> {
|
|
53
|
+
const payload = await cloudRequest(`/api/v1/flows/listeners/${encodeURIComponent(agentId)}`, options);
|
|
54
|
+
const listener = isCloudRecord(payload) && isCloudRecord(payload.listener) ? payload.listener : undefined;
|
|
55
|
+
if (!listener || typeof listener.name !== 'string' || typeof listener.status !== 'string'
|
|
56
|
+
|| !isCloudRecord(listener.repository) || typeof listener.repository.owner !== 'string'
|
|
57
|
+
|| typeof listener.repository.name !== 'string' || !Array.isArray(listener.sources)) {
|
|
58
|
+
throw new CloudFlowError('invalid_response', 'Cloud did not return the flow listener.');
|
|
59
|
+
}
|
|
60
|
+
const versions = isCloudRecord(payload) && Array.isArray(payload.versions)
|
|
61
|
+
? payload.versions.flatMap(entry => parseVersion(entry) ?? []) : [];
|
|
62
|
+
return {
|
|
63
|
+
agentId,
|
|
64
|
+
name: listener.name,
|
|
65
|
+
status: listener.status,
|
|
66
|
+
repository: {
|
|
67
|
+
owner: listener.repository.owner, name: listener.repository.name,
|
|
68
|
+
...(listener.repository.host === 'gitlab' ? { host: 'gitlab' as const } : {}),
|
|
69
|
+
},
|
|
70
|
+
sources: listener.sources.flatMap((entry): FlowTriggerSource[] => isCloudRecord(entry) && typeof entry.provider === 'string'
|
|
71
|
+
? [{ provider: entry.provider as FlowTriggerSource['provider'],
|
|
72
|
+
settings: Object.fromEntries(Object.entries(isCloudRecord(entry.settings) ? entry.settings : {})
|
|
73
|
+
.flatMap(([key, value]) => typeof value === 'string' ? [[key, value]] : typeof value === 'boolean' ? [[key, String(value)]] : [])) }]
|
|
74
|
+
: []),
|
|
75
|
+
activeVersion: isCloudRecord(payload) ? parseVersion(payload.activeVersion) ?? null : null,
|
|
76
|
+
versions,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface DeployVersionInput {
|
|
81
|
+
path: string;
|
|
82
|
+
/** The flow's deployed name or its listener id. */
|
|
83
|
+
flow: string;
|
|
84
|
+
plugins?: readonly string[];
|
|
85
|
+
connect?: ConnectPrompt;
|
|
86
|
+
checkConnections?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface CloudVersionDeployment {
|
|
90
|
+
agentId: string;
|
|
91
|
+
name: string;
|
|
92
|
+
status: string;
|
|
93
|
+
sourceSha256: string;
|
|
94
|
+
requirements: FlowRequirements;
|
|
95
|
+
connected: string[];
|
|
96
|
+
version: CloudFlowVersionChange;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** `POST /api/v1/flows/listeners/<id>/versions`: the source alone becomes the next version. */
|
|
100
|
+
export async function deployVersionToCloud(
|
|
101
|
+
input: DeployVersionInput, options: CloudConnectionOptions = {},
|
|
102
|
+
): Promise<CloudVersionDeployment> {
|
|
103
|
+
const { bytes, source, definition, extensions, projectCli } = await loadDeploySource(input);
|
|
104
|
+
const agentId = await resolveCloudFlow(input.flow, options);
|
|
105
|
+
const listener = await getCloudListener(agentId, options);
|
|
106
|
+
options.signal?.throwIfAborted();
|
|
107
|
+
// Requirements derive from the source, read against the listener's own
|
|
108
|
+
// repository and triggers, exactly as the create form reads its flags.
|
|
109
|
+
const requirements = mergeFlowExtensionRequirements(
|
|
110
|
+
flowRequirements(definition, {
|
|
111
|
+
sources: listener.sources,
|
|
112
|
+
// A GitHub repository means every run needs GitHub; a GitLab project does not.
|
|
113
|
+
...(listener.repository.host === 'gitlab' ? {} : { repository: listener.repository }),
|
|
114
|
+
...(projectCli === undefined ? {} : { projectCli }),
|
|
115
|
+
}),
|
|
116
|
+
extensions.map(extension => ({ name: extension.name, permissions: extension.manifest.permissions })),
|
|
117
|
+
);
|
|
118
|
+
// As on create: a harness Cloud cannot run is refused here, not deployed.
|
|
119
|
+
// There is no --agents override beside --flow; the listener's agents stay.
|
|
120
|
+
const unsupported = requirements.harnessUses
|
|
121
|
+
.filter(use => !(FLOW_AGENT_HARNESSES as readonly string[]).includes(use.harness));
|
|
122
|
+
if (unsupported.length > 0) {
|
|
123
|
+
throw new CloudFlowError('unsupported_source',
|
|
124
|
+
`This flow declares ${unsupported.map(use => `${use.harness} (${use.detail})`).join(', ')}, which Cloud deployments cannot run yet; `
|
|
125
|
+
+ `Cloud runs ${FLOW_AGENT_HARNESSES.join(' and ')}. Change the declaration before deploying a new version.`);
|
|
126
|
+
}
|
|
127
|
+
const whoami = await cloudRequest('/api/v1/auth/whoami', options);
|
|
128
|
+
const workspace = isCloudRecord(whoami) && isCloudRecord(whoami.currentWorkspace) ? whoami.currentWorkspace : undefined;
|
|
129
|
+
if (workspace === undefined || typeof workspace.id !== 'string' || !workspace.id) {
|
|
130
|
+
throw new CloudFlowError('invalid_response', 'Cloud did not report a current workspace for this credential.');
|
|
131
|
+
}
|
|
132
|
+
// A draft activates nothing, so Cloud checks nothing; match it here.
|
|
133
|
+
const connected = listener.status !== 'listening' || input.checkConnections === false
|
|
134
|
+
? []
|
|
135
|
+
: (await ensureIntegrationsConnected(requirements, {
|
|
136
|
+
...options, workspaceId: workspace.id, ...(input.connect === undefined ? {} : { prompt: input.connect }),
|
|
137
|
+
})).connected;
|
|
138
|
+
options.signal?.throwIfAborted();
|
|
139
|
+
const result = await cloudFetch(`/api/v1/flows/listeners/${encodeURIComponent(agentId)}/versions`, options, {
|
|
140
|
+
method: 'POST', detail: true, body: JSON.stringify({
|
|
141
|
+
workspaceId: workspace.id,
|
|
142
|
+
source,
|
|
143
|
+
requirements: {
|
|
144
|
+
integrations: requirements.integrations.map(integration => integration.provider),
|
|
145
|
+
harnesses: requirements.harnesses,
|
|
146
|
+
mcp: requirements.mcp,
|
|
147
|
+
},
|
|
148
|
+
...(extensions.length === 0 ? {} : { extensions }),
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
const version = isCloudRecord(result) ? parseVersionChange(result.version) : undefined;
|
|
152
|
+
if (!isCloudRecord(result) || typeof result.status !== 'string' || version === undefined) {
|
|
153
|
+
throw new CloudFlowError('invalid_response', 'Cloud did not return the new flow version.');
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
agentId, name: listener.name, status: result.status,
|
|
157
|
+
sourceSha256: createHash('sha256').update(bytes).digest('hex'),
|
|
158
|
+
requirements, connected, version,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** `POST /api/v1/flows/listeners/<id>/versions/<n>/activate`: history is kept; only the pointer moves. */
|
|
163
|
+
export async function activateCloudFlowVersion(
|
|
164
|
+
flow: string, version: number, options: CloudConnectionOptions = {},
|
|
165
|
+
): Promise<{ agentId: string; status: string; version: CloudFlowVersionChange }> {
|
|
166
|
+
if (!Number.isSafeInteger(version) || version < 1) {
|
|
167
|
+
throw new CloudFlowError('invalid_input', `A version is a positive whole number, got "${version}".`);
|
|
168
|
+
}
|
|
169
|
+
const agentId = await resolveCloudFlow(flow, options);
|
|
170
|
+
const result = await cloudFetch(
|
|
171
|
+
`/api/v1/flows/listeners/${encodeURIComponent(agentId)}/versions/${version}/activate`,
|
|
172
|
+
options, { method: 'POST', detail: true },
|
|
173
|
+
);
|
|
174
|
+
const change = isCloudRecord(result) ? parseVersionChange(result.version) : undefined;
|
|
175
|
+
if (!isCloudRecord(result) || typeof result.status !== 'string' || change === undefined) {
|
|
176
|
+
throw new CloudFlowError('invalid_response', 'Cloud did not confirm the active version.');
|
|
177
|
+
}
|
|
178
|
+
return { agentId, status: result.status, version: change };
|
|
179
|
+
}
|