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
16 changes: 11 additions & 5 deletions docs/CLOUD.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,17 @@ listener's rules match them there. The digest form,
decides which form is meant.

`--on <provider>[:key=value,…]` takes `github` (`repository`, `labels`,
`contains`), `slack` (`channel`, `contains`), `linear` (`team`, `contains`),
`jira` (`project`, `contains`) or `shortcut` (`workspace`, `contains`), each
at most once. A GitHub source without `repository` is scoped to `--repo`.
Today a GitHub listener wakes on `issues.opened` and `issues.labeled` only;
pull-request and comment events are filtered out before launch.
`contains`, `events`), `slack` (`channel`, `contains`), `linear` (`team`,
`contains`), `jira` (`project`, `contains`) or `shortcut` (`workspace`,
`contains`), each at most once. A GitHub source without `repository` is
scoped to `--repo`. `events` is `issues` (the default: `issues.opened` and
`issues.labeled`) or `pull_request`, which wakes on a pull request being
opened, receiving commits, being reopened, or being reviewed; a
pull-request run checks out the pull request's own head and receives
`input.pullRequest` (`number`, `title`, `body`, `headRef`, `headSha`,
`baseRef`, `author`, `draft`, `labels`, `url`, and `review` for a submitted
review) beside `input.issue` and `input.event`. Comment and check-run
events are not wake sources yet.

Each matching ticket launches one run of the stored source. Cloud clones
`--repo` at its default branch onto a fresh `relayflow/<name>-<id>` branch,
Expand Down
8 changes: 4 additions & 4 deletions packages/sdk/package-lock.json

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

2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.30.0",
"@relayfile/adapter-core": "0.5.24",
"@relayfile/adapter-core": "0.5.25",
"@relayfile/relay-helpers": "0.4.11",
"@relayflows/surface": "2.0.16",
"@types/js-yaml": "^4.0.9",
Expand Down
13 changes: 12 additions & 1 deletion packages/sdk/src/cloud-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export type FlowTriggerProvider = (typeof FLOW_TRIGGER_PROVIDERS)[number];

/** Settings Cloud's launcher prefilter reads per provider (`flow-trigger-sources.ts`). */
const PROVIDER_SETTINGS: Record<FlowTriggerProvider, readonly string[]> = {
github: ['repository', 'labels', 'contains'],
// `events`: `issues` (default) or `pull_request` — which GitHub records
// wake the listener (AgentWorkforce/cloud#3772).
github: ['repository', 'labels', 'contains', 'events'],
slack: ['channel', 'contains'],
linear: ['team', 'contains'],
jira: ['project', 'contains'],
Expand Down Expand Up @@ -102,6 +104,15 @@ export function parseTriggerSource(value: string): FlowTriggerSource {
if (!setting || setting.length > MAX_SETTING_LENGTH || key in settings) {
throw new CloudFlowError('invalid_input', `Trigger setting "${key}" must be given once with a non-empty value.`);
}
if (key === 'events') {
// Cloud's enum is lowercase; send it that way whatever the shell typed.
const events = setting.toLowerCase();
if (!['issues', 'pull_request'].includes(events)) {
throw new CloudFlowError('invalid_input', `github events must be "issues" or "pull_request", got "${setting}".`);
}
settings[key] = events;
continue;
}
settings[key] = setting;
}
}
Expand Down
6 changes: 5 additions & 1 deletion packages/sdk/tests/cloud-deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,13 @@ describe('trigger source and repository parsing', () => {
['github:labels=agent,contains=urgent', { provider: 'github', settings: { labels: 'agent', contains: 'urgent' } }],
['slack:channel=#eng', { provider: 'slack', settings: { channel: '#eng' } }],
['linear:team=ENG', { provider: 'linear', settings: { team: 'ENG' } }],
['github:events=pull_request,labels=agent', { provider: 'github', settings: { events: 'pull_request', labels: 'agent' } }],
['github:events=PULL_REQUEST', { provider: 'github', settings: { events: 'pull_request' } }],
])('parses %s', (value, expected) => {
expect(parseTriggerSource(value)).toEqual(expected);
});

it.each(['gitlab', 'github:channel=x', 'github:labels=', 'github:labels=a,labels=b', 'slack:labels=x'])
it.each(['gitlab', 'github:channel=x', 'github:labels=', 'github:labels=a,labels=b', 'slack:labels=x', 'github:events=releases'])
('refuses %s', (value) => {
expect(() => parseTriggerSource(value)).toThrow(expect.objectContaining({ code: 'invalid_input' }));
});
Expand Down Expand Up @@ -91,6 +93,8 @@ describe('deployToCloud', () => {
});
expect(calls.map(c => [c.method, c.path])).toEqual([['GET', '/api/v1/auth/whoami'], ['POST', '/api/v1/flows/deploy']]);
const body = calls[1]!.body as Record<string, unknown>;
// The serialized body carries Cloud's lowercase enum whatever the shell typed.
expect(parseTriggerSource('github:events=Pull_Request').settings.events).toBe('pull_request');
expect(body).toMatchObject({
workspaceId: 'ws-1', mode: 'activate', name: 'issue-triage', workflow: 'flows-cli',
inputs: { approver: 'khaliqgant', agents: ['claude'] }, repository: { owner: 'AgentWorkforce', name: 'flows' },
Expand Down
9 changes: 7 additions & 2 deletions packages/surface/src/triggers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ filter names that same provider, and the filter pins an event type.
values exactly: use the channel ID and reaction name from the incoming event.
`github.pull_request(action)` filters `payload.action`; omitting the action
accepts every pull request event. Upstream mappings do not declare action enums,
so the action parameter is a string. Other generated methods accept an optional
recursive payload filter, for example `github.push({ ref: 'refs/heads/main' })`.
so the action parameter is a string. `github.check_run(action)` (a CI check
finished: `completed`, with `check_run.conclusion` in the payload) and
`github.issue_comment(action)` (a comment on an issue or pull-request
conversation: `created`, `edited`, `deleted`) take an action the same way — the
two events a PR reviewer needs for merge-on-green and comment-driven directives.
Other generated methods accept an optional recursive payload filter, for
example `github.push({ ref: 'refs/heads/main' })`.

The receiver accepts `POST /providers/slack` with this JSON:

Expand Down
6 changes: 6 additions & 0 deletions packages/surface/src/triggers/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { providerTrigger, triggerArgument } from "../provider-trigger.js";
import type { WebhookFilter } from "../triggers.js";

export const github = Object.freeze({
check_run(action?: string) {
return providerTrigger("github", "check_run", action === undefined ? undefined : { action: triggerArgument(action, "action") });
},
issue_comment(action?: string) {
return providerTrigger("github", "issue_comment", action === undefined ? undefined : { action: triggerArgument(action, "action") });
},
issues(filter?: WebhookFilter) {
return providerTrigger("github", "issues", filter);
},
Expand Down
2 changes: 1 addition & 1 deletion packages/surface/src/triggers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export { slack } from "./slack.js";

/** Exact upstream event names, plus the generated Slack mention shorthand. */
export const providerEventTypes = Object.freeze({
"github": Object.freeze(["issues","pull_request","pull_request_review","push"] as const),
"github": Object.freeze(["check_run","issue_comment","issues","pull_request","pull_request_review","push"] as const),
"slack": Object.freeze(["app_mention","message","reaction_added"] as const),
});
14 changes: 14 additions & 0 deletions packages/surface/tests/triggers-github-events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { github, providerEventTypes } from "../src/triggers/index.js";

describe("generated GitHub trigger vocabulary", () => {
it("offers check_run and issue_comment with an action filter, like pull_request", () => {
expect([...providerEventTypes.github]).toEqual([
"check_run", "issue_comment", "issues", "pull_request", "pull_request_review", "push",
]);
expect(github.check_run("completed").filter).toEqual({ provider: "github", type: "check_run", payload: { action: "completed" } });
expect(github.issue_comment("created").filter).toEqual({ provider: "github", type: "issue_comment", payload: { action: "created" } });
expect(github.issue_comment().filter).toEqual({ provider: "github", type: "issue_comment" });
expect(() => github.check_run("")).toThrow(TypeError);
});
});
Loading