Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 52 additions & 13 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
description: Package to publish (single-package selections are dry-run only)
required: true
type: choice
options: [all, surface, sdk, runtime-linux-x64]
options: [all, surface, sdk, runtime-linux-x64, relayflows]
default: all
version:
description: Version bump type
Expand Down Expand Up @@ -108,7 +108,26 @@ jobs:
./node_modules/.bin/tsc
node scripts/make-cli-executable.mjs
- name: Pack and assert SDK
id: sdk
run: node scripts/pack-release.mjs sdk
# relayflows only re-exposes the SDK's CLI under the unscoped name, so it
# builds against the packed SDK the same way the SDK builds against the
# packed surface — and it's cheap enough to smoke-test for real here
# rather than only asserting the tarball's shape in pack-release.mjs.
- name: Build relayflows CLI wrapper against packed SDK
env:
SDK_TARBALL: ${{ steps.sdk.outputs.tarball }}
working-directory: packages/relayflows
run: |
npm install --no-save --package-lock=false --ignore-scripts "$SDK_TARBALL"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: On every release that advances the version, this fresh CI install tries to fetch @relayflows/surface@NEW_VERSION from npm even though that version has not been published yet, so the relayflows build fails before its smoke test. Install the packed surface tarball alongside SDK_TARBALL (for example, pass ${{ steps.surface.outputs.tarball }} to this npm install).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/publish.yml, line 122:

<comment>On every release that advances the version, this fresh CI install tries to fetch `@relayflows/surface@NEW_VERSION` from npm even though that version has not been published yet, so the relayflows build fails before its smoke test. Install the packed surface tarball alongside `SDK_TARBALL` (for example, pass `${{ steps.surface.outputs.tarball }}` to this `npm install`).</comment>

<file context>
@@ -108,7 +108,26 @@ jobs:
+          SDK_TARBALL: ${{ steps.sdk.outputs.tarball }}
+        working-directory: packages/relayflows
+        run: |
+          npm install --no-save --package-lock=false --ignore-scripts "$SDK_TARBALL"
+          test ! -L node_modules/@relayflows/sdk
+          report=$(node bin/flows.js check --json ../../testdata/hello-deterministic.flow.yaml)
</file context>
Suggested change
npm install --no-save --package-lock=false --ignore-scripts "$SDK_TARBALL"
npm install --no-save --package-lock=false --ignore-scripts \
"${{ steps.surface.outputs.tarball }}" "$SDK_TARBALL"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Install the packed surface before testing the SDK alias

On every real version bump where the new surface version is not already published, the “Build relayflows CLI wrapper against packed SDK” step fails here: version-packages.mjs pins the packed SDK to the new @relayflows/surface version, and installing that SDK tarball also installs its dependencies, so npm queries the registry for the still-unpublished surface package rather than using the tarball installed earlier in a different package directory. The npm install documentation confirms that installing a package installs its dependencies. Install the packed surface tarball alongside the SDK tarball so the build can reach the later dependency-ordered publish step.

Useful? React with 👍 / 👎.

test ! -L node_modules/@relayflows/sdk
report=$(node bin/flows.js check --json ../../testdata/hello-deterministic.flow.yaml)
echo "$report" | node -e '
const report = JSON.parse(require("fs").readFileSync(0, "utf8"));
if (report.ok !== true) { console.error(report); process.exit(1); }
'
- name: Pack and assert relayflows CLI wrapper
run: node scripts/pack-release.mjs relayflows
- name: Build relayflowd
working-directory: kernel
run: cargo build --locked --release -p relayflowd
Expand Down Expand Up @@ -152,13 +171,19 @@ jobs:
with:
name: build-output
path: dist/build-output
# relayflows is the one unscoped package, so `npm pack` names its tarball
# `relayflows-<version>.tgz`, not `relayflows-relayflows-<version>.tgz`.
- name: Restore built packages
env:
NEW_VERSION: ${{ needs.build.outputs.new_version }}
run: |
for package in surface sdk runtime-linux-x64; do
tar -xzf "dist/build-output/relayflows-${package}-${NEW_VERSION}.tgz" \
--strip-components=1 -C "packages/$package"
for package in surface sdk runtime-linux-x64 relayflows; do
if [[ "$package" == relayflows ]]; then
tarball="relayflows-${NEW_VERSION}.tgz"
else
tarball="relayflows-${package}-${NEW_VERSION}.tgz"
fi
tar -xzf "dist/build-output/$tarball" --strip-components=1 -C "packages/$package"
done
# Repack and check EVERYTHING before the first publish. Tar archives
# preserve executable bits across Actions artifact upload/download.
Expand All @@ -167,18 +192,24 @@ jobs:
node scripts/pack-release.mjs surface
node scripts/pack-release.mjs sdk
node scripts/pack-release.mjs runtime-linux-x64
- name: Publish to NPM (surface before SDK)
node scripts/pack-release.mjs relayflows
- name: Publish to NPM (surface before SDK, relayflows last)
env:
PACKAGE: ${{ inputs.package }}
NEW_VERSION: ${{ needs.build.outputs.new_version }}
DRY_RUN: ${{ inputs.dry_run }}
NPM_TAG: ${{ inputs.tag }}
run: |
for package in surface sdk runtime-linux-x64; do
for package in surface sdk runtime-linux-x64 relayflows; do
if [[ "$PACKAGE" != all && "$PACKAGE" != "$package" ]]; then continue; fi
if [[ "$package" == relayflows ]]; then
tarball="relayflows-${NEW_VERSION}.tgz"
else
tarball="relayflows-${package}-${NEW_VERSION}.tgz"
fi
args=()
if [[ "$DRY_RUN" == true ]]; then args+=(--dry-run); fi
npm publish "dist/publish/relayflows-${package}-${NEW_VERSION}.tgz" \
npm publish "dist/publish/$tarball" \
--access public --provenance --ignore-scripts --tag "$NPM_TAG" "${args[@]}"
done
- name: Regenerate release lockfiles
Expand All @@ -188,17 +219,24 @@ jobs:
run: |
npm install --prefix packages/surface --package-lock-only --ignore-scripts
npm install --prefix packages/sdk --package-lock-only --ignore-scripts --save-exact "@relayflows/surface@$NEW_VERSION"
npm install --prefix packages/relayflows --package-lock-only --ignore-scripts --save-exact "@relayflows/sdk@$NEW_VERSION"
node --input-type=module - <<'NODE'
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
const lock = JSON.parse(readFileSync('packages/sdk/package-lock.json', 'utf8'));
const surface = lock.packages['node_modules/@relayflows/surface'];
assert.equal(surface.version, process.env.NEW_VERSION);
assert.match(surface.resolved, /^https:\/\/registry\.npmjs\.org\//);
assert(!surface.link, 'release lockfile must resolve the published surface');
const version = process.env.NEW_VERSION;
const check = (lockPath, depNodeModulesPath) => {
const lock = JSON.parse(readFileSync(lockPath, 'utf8'));
const dep = lock.packages[depNodeModulesPath];
assert.equal(dep.version, version);
assert.match(dep.resolved, /^https:\/\/registry\.npmjs\.org\//);
assert(!dep.link, `release lockfile must resolve the published dependency (${lockPath})`);
};
check('packages/sdk/package-lock.json', 'node_modules/@relayflows/surface');
check('packages/relayflows/package-lock.json', 'node_modules/@relayflows/sdk');
NODE
npm ci --prefix packages/surface --dry-run --ignore-scripts
npm ci --prefix packages/sdk --dry-run --ignore-scripts
npm ci --prefix packages/relayflows --dry-run --ignore-scripts
- name: Commit version bump and create tag
if: ${{ !inputs.dry_run }}
env:
Expand All @@ -209,7 +247,8 @@ jobs:
git config user.email 'actions@github.com'
git add packages/surface/package.json packages/surface/package-lock.json \
packages/sdk/package.json packages/sdk/package-lock.json \
packages/runtime-linux-x64/package.json
packages/runtime-linux-x64/package.json \
packages/relayflows/package.json packages/relayflows/package-lock.json
if ! git diff --staged --quiet; then
git commit -m "chore(release): v${NEW_VERSION}"
fi
Expand Down
32 changes: 12 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,15 @@ can run autonomously over days and weeks. Every agent session is observable and
- Pull request review pipeline with different agents looking at the pull request from different angles (security, optimization etc) and agents communicate when needed to reach consensus — [`examples/pr-review-pipeline/`](examples/pr-review-pipeline/)
- Dependency upgrade bot: deterministic check flags a dependency out of date which fires an agent who does the upgrade in a sandbox. This upgrade is gated on another agent verifying the entire application with computer use in another sandbox. If completely verified a pull request is opened up — [`examples/dependency-upgrade-bot/`](examples/dependency-upgrade-bot/)

## Cloud review swarm
Comment thread
khaliqgant marked this conversation as resolved.

Every pull request launches the cloud review swarm. Repository administrators
must configure two Actions secrets. The workflow fails during preflight, in
seconds and before submitting a run, when either is absent.

| Secret | What it is | How to obtain it |
|---|---|---|
| `RELAY_WORKSPACE_KEY` | Selects the messaging workspace the swarm runs in. | `agent-relay workspace key --reveal-secrets` |
| `CLOUD_API_KEY` | The Cloud API key for workflow invocation. | Follow `AgentWorkforce/cloud` → `docs/runbooks/relay-ci-workflow-credential.md`, profile `workflow-invoke` |

`CLOUD_API_URL` is not secret; the workflow defaults it and it can be
overridden with a repository variable of the same name.

A workspace key alone cannot run the swarm. `agent-relay cloud run` authenticates
to the Cloud API using `CLOUD_API_KEY`: the workspace key is read only by the
resolver that picks a messaging workspace, while the API key authenticates
`POST /api/v1/workflows/prepare` — which `--sync-code` requires, and `--sync-code`
is how the swarm receives the PR diff. Given no API key, the CLI falls back to an
interactive device login that no runner can approve and exits after the grant expires.

# Get Started

Installation:
```
npm install -g relayflows
```

Give your agent a skill to write a flow:
```
npx skills add https://github.com/agentworkforce/skills --skill writing-relayflows
```
20 changes: 20 additions & 0 deletions packages/relayflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# relayflows

The `flows` CLI, published under the unscoped name so the install is just:

```sh
npm install -g relayflows
```

This package carries no logic of its own. It declares `@relayflows/sdk` as a
normal dependency and `bin/flows.js` forwards argv and stdio straight to that
dependency's `dist/cli.js`, resolved by filesystem path from this package's
own `node_modules` rather than by import specifier — the SDK's package
`exports` doesn't list that subpath, so a bare `import` would be refused.

Versioned and released in lockstep with `@relayflows/sdk`: the two always
carry the same version number, and this package pins its dependency to that
exact version rather than a range, so `npm install -g relayflows` always
resolves the SDK build it shipped with.

See `@relayflows/sdk` and `docs/SURFACE.md` for what the CLI actually does.
23 changes: 23 additions & 0 deletions packages/relayflows/bin/flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

// Not `import '@relayflows/sdk/dist/cli.js'`: that subpath isn't in the
// SDK's package "exports", so specifier resolution would refuse it. Locating
// the installed dependency's real CLI file directly sidesteps that — this
// package's only job is finding it and forwarding argv/stdio.
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
const sdkCli = join(packageRoot, 'node_modules', '@relayflows', 'sdk', 'dist', 'cli.js');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve the SDK through Node's hoisted dependency lookup

When relayflows is installed as a normal project dependency rather than globally, npm's default hoisted strategy places the non-duplicated @relayflows/sdk package in the consumer's top-level node_modules, not under node_modules/relayflows/node_modules; this hard-coded path therefore reports the SDK as missing and exits even though npm installed the declared dependency. The npm install strategy documentation explicitly identifies hoisted as the default. Resolve the installed SDK through Node's package resolution so both global and local/npx layouts work.

Useful? React with 👍 / 👎.


if (!existsSync(sdkCli)) {
process.stderr.write(
`relayflows: could not find @relayflows/sdk at ${sdkCli}\n` +
'Reinstall with `npm install -g relayflows`.\n',
);
process.exit(1);
}

const result = spawnSync(process.execPath, [sdkCli, ...process.argv.slice(2)], { stdio: 'inherit' });
process.exit(result.status ?? 1);
161 changes: 161 additions & 0 deletions packages/relayflows/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions packages/relayflows/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "relayflows",
"version": "2.0.1",
"description": "Relayflow v2 CLI. Installs the `flows` command — a thin wrapper around @relayflows/sdk, published under the unscoped name for `npm install -g relayflows`.",
"type": "module",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/AgentWorkforce/flows.git",
"directory": "packages/relayflows"
},
"files": [
"bin"
],
"bin": {
"flows": "./bin/flows.js"
},
"dependencies": {
"@relayflows/sdk": "2.0.1"
},
"engines": {
"node": ">=20"
}
}
Loading
Loading