From 06f503fe9ecf4cfd2fe35298274f8be242e1c7f0 Mon Sep 17 00:00:00 2001 From: Relayflow Lead Date: Thu, 17 Sep 2026 14:55:17 -0700 Subject: [PATCH 1/4] feat(surface): github.check_run and github.issue_comment triggers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regenerated `packages/surface/src/triggers/github.ts` from the core GitHub mapping in AgentWorkforce/relayfile-adapters#279, which declares `check_run` and `issue_comment` as action-bearing webhook keys. The surface gains `github.check_run(action?)` and `github.issue_comment(action?)` — the two events a PR reviewer needs for merge-on-green and comment-driven directives — and `providerEventTypes.github` lists them, so `flows check` admits a subscription to either instead of refusing it as unpublished. Generated with the adapters checkout's `packages/core/mappings` alone, which is byte-for-byte what the published `@relayfile/adapter-core` tarball will carry, so `generate-triggers.mjs --check` reproduces these files once the SDK's pinned adapter-core is bumped to the release that contains #279. Until that bump the check refuses, by design. Co-Authored-By: Claude Opus 5 (1M context) --- packages/surface/src/triggers/README.md | 9 +++++++-- packages/surface/src/triggers/github.ts | 6 ++++++ packages/surface/src/triggers/index.ts | 2 +- .../surface/tests/triggers-github-events.test.ts | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 packages/surface/tests/triggers-github-events.test.ts diff --git a/packages/surface/src/triggers/README.md b/packages/surface/src/triggers/README.md index 040fb3d3a..245b3827b 100644 --- a/packages/surface/src/triggers/README.md +++ b/packages/surface/src/triggers/README.md @@ -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: diff --git a/packages/surface/src/triggers/github.ts b/packages/surface/src/triggers/github.ts index 43c6959b5..8b6a61f4d 100644 --- a/packages/surface/src/triggers/github.ts +++ b/packages/surface/src/triggers/github.ts @@ -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); }, diff --git a/packages/surface/src/triggers/index.ts b/packages/surface/src/triggers/index.ts index 0ae53b47e..7b6b95169 100644 --- a/packages/surface/src/triggers/index.ts +++ b/packages/surface/src/triggers/index.ts @@ -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), }); diff --git a/packages/surface/tests/triggers-github-events.test.ts b/packages/surface/tests/triggers-github-events.test.ts new file mode 100644 index 000000000..67323b60f --- /dev/null +++ b/packages/surface/tests/triggers-github-events.test.ts @@ -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); + }); +}); From 8a021ade4bf2476bb0315b286e65399733df8282 Mon Sep 17 00:00:00 2001 From: Relayflow Lead Date: Thu, 17 Sep 2026 15:11:14 -0700 Subject: [PATCH 2/4] feat(sdk): flows deploy --on github:events=pull_request The GitHub source setting `events` (AgentWorkforce/cloud#3772) selects which records wake a listener: `issues` (default) or `pull_request`. The CLI validates it client-side like the other settings and the doc describes the pull-request run's input. Co-Authored-By: Claude Opus 5 (1M context) --- docs/CLOUD.md | 16 +++++++++++----- packages/sdk/src/cloud-deploy.ts | 7 ++++++- packages/sdk/tests/cloud-deploy.test.ts | 3 ++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/CLOUD.md b/docs/CLOUD.md index 3f26353bf..6558e48be 100644 --- a/docs/CLOUD.md +++ b/docs/CLOUD.md @@ -135,11 +135,17 @@ listener's rules match them there. The digest form, decides which form is meant. `--on [: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/-` branch, diff --git a/packages/sdk/src/cloud-deploy.ts b/packages/sdk/src/cloud-deploy.ts index 78ee04ca4..e1f161b1b 100644 --- a/packages/sdk/src/cloud-deploy.ts +++ b/packages/sdk/src/cloud-deploy.ts @@ -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 = { - 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'], @@ -102,6 +104,9 @@ 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' && !['issues', 'pull_request'].includes(setting.toLowerCase())) { + throw new CloudFlowError('invalid_input', `github events must be "issues" or "pull_request", got "${setting}".`); + } settings[key] = setting; } } diff --git a/packages/sdk/tests/cloud-deploy.test.ts b/packages/sdk/tests/cloud-deploy.test.ts index ff4b9f0be..5371426f7 100644 --- a/packages/sdk/tests/cloud-deploy.test.ts +++ b/packages/sdk/tests/cloud-deploy.test.ts @@ -59,11 +59,12 @@ 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' } }], ])('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' })); }); From 0884f4b497bc106fb9ca5f84a95d9c7f2676138f Mon Sep 17 00:00:00 2001 From: Relayflow Lead Date: Thu, 17 Sep 2026 16:02:00 -0700 Subject: [PATCH 3/4] chore(sdk): pin @relayfile/adapter-core 0.5.25 The release carrying relayfile-adapters#279 (`check_run` and `issue_comment` webhook keys). `generate-triggers.mjs` against the installed tarball reproduces the committed trigger modules byte for byte, and `--check` passes again. Co-Authored-By: Claude Opus 5 (1M context) --- packages/sdk/package-lock.json | 8 ++++---- packages/sdk/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sdk/package-lock.json b/packages/sdk/package-lock.json index c7a98cb8e..889b28c06 100644 --- a/packages/sdk/package-lock.json +++ b/packages/sdk/package-lock.json @@ -10,7 +10,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", @@ -511,9 +511,9 @@ } }, "node_modules/@relayfile/adapter-core": { - "version": "0.5.24", - "resolved": "https://registry.npmjs.org/@relayfile/adapter-core/-/adapter-core-0.5.24.tgz", - "integrity": "sha512-bOQRuBoAw2RlYs30RtKsOvXlXzcRx4owhHdj384hPrznBIY3U4ZYcb4pVfzreW9TStHEiX7EguUyqhD9g8DJmA==", + "version": "0.5.25", + "resolved": "https://registry.npmjs.org/@relayfile/adapter-core/-/adapter-core-0.5.25.tgz", + "integrity": "sha512-vmXlZAuHujhrNmd9sTeFn4PA1YqqUEjiHd1ZaTLW9JK5MiVov/dHWPunjvKX3Aih3/9MFS61kL6EGVnxd2pNZg==", "license": "Apache-2.0", "dependencies": { "@scalar/postman-to-openapi": "^0.6.0", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 37dbf9b39..3174e2f62 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -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", From baa4f5e737a77b43d8b1e51ca4d5a2b912ec74d8 Mon Sep 17 00:00:00 2001 From: Relayflow Lead Date: Thu, 17 Sep 2026 16:13:22 -0700 Subject: [PATCH 4/4] fix(sdk): send the github events setting as Cloud's lowercase enum Validation was case-insensitive but the caller's spelling was serialized, so `events=PULL_REQUEST` passed the CLI and failed at Cloud (Devin). Co-Authored-By: Claude Opus 5 (1M context) --- packages/sdk/src/cloud-deploy.ts | 10 ++++++++-- packages/sdk/tests/cloud-deploy.test.ts | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/cloud-deploy.ts b/packages/sdk/src/cloud-deploy.ts index e1f161b1b..3f24875b5 100644 --- a/packages/sdk/src/cloud-deploy.ts +++ b/packages/sdk/src/cloud-deploy.ts @@ -104,8 +104,14 @@ 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' && !['issues', 'pull_request'].includes(setting.toLowerCase())) { - throw new CloudFlowError('invalid_input', `github events must be "issues" or "pull_request", got "${setting}".`); + 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; } diff --git a/packages/sdk/tests/cloud-deploy.test.ts b/packages/sdk/tests/cloud-deploy.test.ts index 5371426f7..491d8422f 100644 --- a/packages/sdk/tests/cloud-deploy.test.ts +++ b/packages/sdk/tests/cloud-deploy.test.ts @@ -60,6 +60,7 @@ describe('trigger source and repository parsing', () => { ['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); }); @@ -92,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; + // 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' },