@relayflows/sdk 2.0.18 → 2.0.20
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 +6 -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.map +1 -1
- package/dist/authored-flow-executor.js +8 -0
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/cli/answer.d.ts.map +1 -1
- package/dist/cli/answer.js +2 -1
- package/dist/cli/answer.js.map +1 -1
- package/dist/cli/build.d.ts +9 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +25 -6
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/cloud-run.d.ts +7 -1
- package/dist/cli/cloud-run.d.ts.map +1 -1
- package/dist/cli/cloud-run.js +12 -14
- package/dist/cli/cloud-run.js.map +1 -1
- package/dist/cli/cloud-sync.d.ts +8 -1
- package/dist/cli/cloud-sync.d.ts.map +1 -1
- package/dist/cli/cloud-sync.js +79 -8
- package/dist/cli/cloud-sync.js.map +1 -1
- package/dist/cli/deploy.d.ts +6 -0
- package/dist/cli/deploy.d.ts.map +1 -1
- package/dist/cli/deploy.js +29 -4
- package/dist/cli/deploy.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +2 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/serve-webhook.d.ts +7 -1
- package/dist/cli/serve-webhook.d.ts.map +1 -1
- package/dist/cli/serve-webhook.js +19 -9
- package/dist/cli/serve-webhook.js.map +1 -1
- package/dist/cli-commands.d.ts +398 -0
- package/dist/cli-commands.d.ts.map +1 -0
- package/dist/cli-commands.js +254 -0
- package/dist/cli-commands.js.map +1 -0
- package/dist/cli-watch.d.ts +3 -1
- package/dist/cli-watch.d.ts.map +1 -1
- package/dist/cli-watch.js +4 -10
- package/dist/cli-watch.js.map +1 -1
- package/dist/cli.d.ts +127 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +81 -48
- package/dist/cli.js.map +1 -1
- package/dist/cloud-sync.d.ts +85 -2
- package/dist/cloud-sync.d.ts.map +1 -1
- package/dist/cloud-sync.js +123 -10
- package/dist/cloud-sync.js.map +1 -1
- package/dist/flow-requirements.d.ts +2 -2
- package/dist/flow-requirements.d.ts.map +1 -1
- package/dist/flow-requirements.js +182 -0
- package/dist/flow-requirements.js.map +1 -1
- package/dist/human-to.d.ts +45 -0
- package/dist/human-to.d.ts.map +1 -0
- package/dist/human-to.js +49 -0
- package/dist/human-to.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/relay-cli.d.ts +50 -0
- package/dist/relay-cli.d.ts.map +1 -0
- package/dist/relay-cli.js +64 -0
- package/dist/relay-cli.js.map +1 -0
- package/package.json +7 -2
- package/src/authored-flow-error.ts +5 -0
- package/src/authored-flow-executor.ts +8 -0
- package/src/cli/answer.ts +2 -1
- package/src/cli/build.ts +20 -6
- package/src/cli/cloud-run.ts +11 -12
- package/src/cli/cloud-sync.ts +85 -8
- package/src/cli/deploy.ts +27 -5
- package/src/cli/run.ts +2 -1
- package/src/cli/serve-webhook.ts +15 -8
- package/src/cli-commands.ts +339 -0
- package/src/cli-watch.ts +8 -9
- package/src/cli.ts +103 -44
- package/src/cloud-sync.ts +164 -11
- package/src/flow-requirements.ts +143 -2
- package/src/human-to.ts +71 -0
- package/src/index.ts +5 -2
- package/src/relay-cli.ts +117 -0
package/src/human-to.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who an `f.human(question, { to })` question is for, parsed from the
|
|
3
|
+
* author's `to` string. This is the delivery contract Cloud reads (the local
|
|
4
|
+
* kit records it, Cloud delivers it), so the forms are fixed here and
|
|
5
|
+
* documented in docs/SURFACE.md §5 Human gates:
|
|
6
|
+
*
|
|
7
|
+
* "slack:#eng" → post in the Slack channel; anyone there may answer
|
|
8
|
+
* "slack:@khaliq" → DM the Slack user; only they may answer
|
|
9
|
+
* "github:@khaliq" → comment on the triggering issue / pull request,
|
|
10
|
+
* mentioning the handle; only they may answer
|
|
11
|
+
* "khaliq" → the deploy's approver, on whichever channel the run
|
|
12
|
+
* was triggered from (Cloud resolves; the local kit
|
|
13
|
+
* only records it)
|
|
14
|
+
*
|
|
15
|
+
* Anything else — an unknown provider (`email:x`), a provider with no target
|
|
16
|
+
* (`slack:`), a GitHub channel (`github:#eng`), a target with whitespace or
|
|
17
|
+
* other characters no handle carries — is refused as `human_to_invalid`
|
|
18
|
+
* rather than turned into a delivery target that would silently reach no one.
|
|
19
|
+
*/
|
|
20
|
+
export type HumanRecipient =
|
|
21
|
+
| { readonly provider: 'slack'; readonly kind: 'channel' | 'user'; readonly target: string }
|
|
22
|
+
| { readonly provider: 'github'; readonly kind: 'user'; readonly target: string }
|
|
23
|
+
| { readonly provider: 'approver'; readonly kind: 'user'; readonly target: string };
|
|
24
|
+
|
|
25
|
+
export type HumanRecipientParse =
|
|
26
|
+
| { readonly ok: true; readonly recipient: HumanRecipient }
|
|
27
|
+
| { readonly ok: false; readonly reason: string };
|
|
28
|
+
|
|
29
|
+
/** A Slack channel name, or a Slack / GitHub / approver handle: one word, no spaces or quotes. */
|
|
30
|
+
const TARGET = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/u;
|
|
31
|
+
const PROVIDER_FORM = /^([A-Za-z][A-Za-z0-9_-]*)\s*:(.*)$/su;
|
|
32
|
+
|
|
33
|
+
export function parseHumanTo(to: string): HumanRecipientParse {
|
|
34
|
+
const trimmed = to.trim();
|
|
35
|
+
if (trimmed === '') return { ok: false, reason: 'to is empty' };
|
|
36
|
+
const match = PROVIDER_FORM.exec(trimmed);
|
|
37
|
+
if (match === null) {
|
|
38
|
+
const target = trimmed.replace(/^@/u, '');
|
|
39
|
+
if (!TARGET.test(target)) return { ok: false, reason: `"${trimmed}" is not a handle (one word: letters, digits, . _ -)` };
|
|
40
|
+
return { ok: true, recipient: { provider: 'approver', kind: 'user', target } };
|
|
41
|
+
}
|
|
42
|
+
const provider = match[1]!.toLowerCase();
|
|
43
|
+
const rest = match[2]!.trim();
|
|
44
|
+
if (provider !== 'slack' && provider !== 'github') {
|
|
45
|
+
return { ok: false, reason: `"${provider}:" is not a delivery provider; use "slack:#channel", "slack:@user", "github:@user" or a bare approver handle` };
|
|
46
|
+
}
|
|
47
|
+
if (rest === '') return { ok: false, reason: `"${provider}:" names no ${provider === 'slack' ? 'channel or user' : 'user'}` };
|
|
48
|
+
if (rest.startsWith('#')) {
|
|
49
|
+
if (provider === 'github') return { ok: false, reason: `"${trimmed}": GitHub has no channels; use "github:@user"` };
|
|
50
|
+
const target = rest.slice(1);
|
|
51
|
+
if (!TARGET.test(target)) return { ok: false, reason: `"${trimmed}" is not a Slack channel name` };
|
|
52
|
+
return { ok: true, recipient: { provider: 'slack', kind: 'channel', target } };
|
|
53
|
+
}
|
|
54
|
+
const target = rest.replace(/^@/u, '');
|
|
55
|
+
if (!TARGET.test(target)) return { ok: false, reason: `"${trimmed}" is not a ${provider === 'slack' ? 'Slack' : 'GitHub'} handle` };
|
|
56
|
+
return { ok: true, recipient: { provider, kind: 'user', target } };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The parsed recipient of a `to` already accepted by `f.human` (`parseHumanTo` ok). */
|
|
60
|
+
export function parseHumanRecipient(to: string): HumanRecipient {
|
|
61
|
+
const parsed = parseHumanTo(to);
|
|
62
|
+
if (!parsed.ok) throw new Error(`f.human to ${parsed.reason}`);
|
|
63
|
+
return parsed.recipient;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The Cloud integration a recipient needs connected: none for the approver, none for a malformed `to`. */
|
|
67
|
+
export function humanRecipientProvider(to: string): 'slack' | 'github' | undefined {
|
|
68
|
+
const parsed = parseHumanTo(to);
|
|
69
|
+
if (!parsed.ok || parsed.recipient.provider === 'approver') return undefined;
|
|
70
|
+
return parsed.recipient.provider;
|
|
71
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -64,8 +64,10 @@ export {
|
|
|
64
64
|
type CloudFlowSource, type RunInCloudOptions, type CloudRunReceipt, type CloudRunState,
|
|
65
65
|
} from './cloud-run.js';
|
|
66
66
|
export {
|
|
67
|
-
downloadCloudPatch, applyCloudPatch, packWorkingTree, patchedPaths,
|
|
68
|
-
|
|
67
|
+
downloadCloudPatch, downloadCloudPatchSet, applyCloudPatch, packWorkingTree, patchedPaths,
|
|
68
|
+
excludedPatchPaths, CLOUD_SYNC_PATCH_EXCLUDES, MAX_SYNC_BYTES,
|
|
69
|
+
type CloudPatch, type CloudPathPatch, type CloudPatchSet, type PackedTree,
|
|
70
|
+
type ApplyCloudPatchOptions, type AppliedCloudPatch,
|
|
69
71
|
} from './cloud-sync.js';
|
|
70
72
|
export {
|
|
71
73
|
scheduleInCloud, listCloudSchedules, unscheduleInCloud, everyToCron, declaredScheduleCron,
|
|
@@ -84,6 +86,7 @@ export {
|
|
|
84
86
|
flowRequirements, describeFlowRequirements, harnessFromCli, FLOW_HARNESSES,
|
|
85
87
|
type FlowRequirements, type FlowRequirementsContext, type FlowIntegrationRequirement, type FlowHarnessRequirement, type FlowHarness,
|
|
86
88
|
} from './flow-requirements.js';
|
|
89
|
+
export { parseHumanTo, parseHumanRecipient, humanRecipientProvider, type HumanRecipient, type HumanRecipientParse } from './human-to.js';
|
|
87
90
|
|
|
88
91
|
export { canonicalize, specHash } from './canonical.js';
|
|
89
92
|
export {
|
package/src/relay-cli.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { runCli } from './cli.js';
|
|
4
|
+
import { CLI_VERBS, CLI_VERB_NAMES, type CliCommandSpec } from './cli-commands.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `@relayflows/sdk/relay-cli` entrypoint: a mountable CLI surface.
|
|
8
|
+
*
|
|
9
|
+
* `agent-relay` mounts this as `agent-relay flows`. The surface is a thin
|
|
10
|
+
* projection over the CLI this package already ships -- `commands` comes from
|
|
11
|
+
* the same `CLI_VERBS` table `parseArgs` dispatches on, and `run` delegates
|
|
12
|
+
* straight to `runCli`. No command is reimplemented here, and
|
|
13
|
+
* `packages/relayflows/bin/flows.js` keeps calling `runCli` on the same path.
|
|
14
|
+
*
|
|
15
|
+
* Structurally typed against `@agent-relay/cli-surface` without importing it,
|
|
16
|
+
* so `dist/relay-cli.d.ts` has no dependency on the contract package and this
|
|
17
|
+
* package gains no runtime dependency on relay. `tests/relay-cli-surface.test.ts`
|
|
18
|
+
* asserts the assignability and runs the contract's own conformance checks.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Host-supplied output sink. Mirrors `RelayCliIo`. */
|
|
22
|
+
export interface RelayCliIo {
|
|
23
|
+
stdout(chunk: string): void;
|
|
24
|
+
stderr(chunk: string): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A mountable product CLI. Mirrors `RelayCliSurface`. */
|
|
28
|
+
export interface RelayCliSurface {
|
|
29
|
+
id: string;
|
|
30
|
+
version: string;
|
|
31
|
+
contract: 1;
|
|
32
|
+
commands: readonly CliCommandSpec[];
|
|
33
|
+
run(argv: readonly string[], io: RelayCliIo): Promise<number>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Options for {@link createRelayCliSurface}. */
|
|
37
|
+
export interface CreateRelayCliSurfaceOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Cancellation for the long-running verbs, owned by the host.
|
|
40
|
+
*
|
|
41
|
+
* A surface must install no global signal handlers, so one is always passed
|
|
42
|
+
* to `runCli` -- a never-aborting signal when the host supplies none. Pass a
|
|
43
|
+
* real one to get graceful cancellation (for example, the
|
|
44
|
+
* "Stopped observing; the hosted run has not been cancelled" path on
|
|
45
|
+
* `run --cloud --wait`) instead of the host's SIGINT killing the process.
|
|
46
|
+
*/
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Exit code for an argv the surface cannot route, per the contract. */
|
|
51
|
+
const EXIT_UNKNOWN_COMMAND = 2;
|
|
52
|
+
|
|
53
|
+
/** Drop `variants` -- the routing detail the host has no use for. */
|
|
54
|
+
function toCommandSpec(verb: CliCommandSpec & { variants?: unknown }): CliCommandSpec {
|
|
55
|
+
const { variants: _variants, ...spec } = verb;
|
|
56
|
+
return spec;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read this package's version from its own manifest.
|
|
61
|
+
*
|
|
62
|
+
* Resolved from `import.meta.url` rather than imported, because `package.json`
|
|
63
|
+
* sits outside `rootDir` and a hardcoded literal would silently go stale at the
|
|
64
|
+
* next release. `src/` and `dist/` are both one level below the manifest, so
|
|
65
|
+
* the same relative path is correct before and after a build.
|
|
66
|
+
*/
|
|
67
|
+
function packageVersion(): string {
|
|
68
|
+
try {
|
|
69
|
+
const manifest: unknown = JSON.parse(
|
|
70
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
71
|
+
);
|
|
72
|
+
const version = (manifest as { version?: unknown }).version;
|
|
73
|
+
return typeof version === 'string' && version.length > 0 ? version : '0.0.0';
|
|
74
|
+
} catch {
|
|
75
|
+
// A surface that cannot read its own manifest is still perfectly runnable;
|
|
76
|
+
// refusing to mount over a cosmetic field would be the worse failure.
|
|
77
|
+
return '0.0.0';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build the relayflows CLI surface.
|
|
83
|
+
*
|
|
84
|
+
* @param options - Host-supplied cancellation.
|
|
85
|
+
* @returns A surface whose `run` resolves to an exit code, writes only through
|
|
86
|
+
* the supplied io, and installs no process signal handlers.
|
|
87
|
+
*/
|
|
88
|
+
export function createRelayCliSurface(
|
|
89
|
+
options: CreateRelayCliSurfaceOptions = {},
|
|
90
|
+
): RelayCliSurface {
|
|
91
|
+
return {
|
|
92
|
+
id: 'relayflows',
|
|
93
|
+
version: packageVersion(),
|
|
94
|
+
contract: 1,
|
|
95
|
+
commands: CLI_VERBS.map(toCommandSpec),
|
|
96
|
+
async run(argv: readonly string[], io: RelayCliIo): Promise<number> {
|
|
97
|
+
const verb = argv[0];
|
|
98
|
+
// `runCli` would refuse this too, but with the full usage block. Naming
|
|
99
|
+
// the offending token is the more useful answer when the host has just
|
|
100
|
+
// routed `agent-relay flows <typo>` here.
|
|
101
|
+
if (verb !== undefined && !verb.startsWith('-') && !CLI_VERB_NAMES.has(verb)) {
|
|
102
|
+
io.stderr(`error: '${verb}' is not a command of relayflows\n`);
|
|
103
|
+
io.stderr("Run 'agent-relay flows --help' for the available commands.\n");
|
|
104
|
+
return EXIT_UNKNOWN_COMMAND;
|
|
105
|
+
}
|
|
106
|
+
return runCli(
|
|
107
|
+
argv,
|
|
108
|
+
// `CliIo` is line-oriented and the contract's io is chunk-oriented, so
|
|
109
|
+
// the terminator is added here rather than by every call site.
|
|
110
|
+
{ stdout: (line) => io.stdout(`${line}\n`), stderr: (line) => io.stderr(`${line}\n`) },
|
|
111
|
+
// Always pass a signal: that is what keeps `runCli` from installing the
|
|
112
|
+
// SIGINT/SIGTERM handlers the standalone binary still relies on.
|
|
113
|
+
{ signal: options.signal ?? new AbortController().signal },
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|