From 6deaf5516f0a7f210578937e80ec6d6dd9d7b622 Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Thu, 24 Sep 2026 15:56:27 -0700 Subject: [PATCH 1/2] feat(sdk): linear:events=assigned|all delegates tickets on assignment Companion to the Cloud watch-rule change: assigning a Linear issue to the connected app user delivers AppUserNotification.issueAssignedToYou, not an issue.create record, so the deploy grammar needs its own event vocabulary. - parseTriggerSource accepts events=issues|assigned|all for linear (case-insensitive, same as github/gitlab) and rejects anything else - CLOUD.md documents the semantics: assigned means assigning a ticket delegates it to the agent - software-factory badge carries on=linear:events=assigned so the one-click deploy wakes on delegation; README CLI example matches Verified: 52 sdk tests pass, tsc --noEmit clean. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- docs/CLOUD.md | 7 +++++-- examples/software-factory/README.md | 19 +++++++++++-------- packages/sdk/src/cloud-deploy.ts | 9 +++++++-- packages/sdk/tests/cloud-deploy.test.ts | 4 +++- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/docs/CLOUD.md b/docs/CLOUD.md index d0418c1d..2ae19c83 100644 --- a/docs/CLOUD.md +++ b/docs/CLOUD.md @@ -455,9 +455,12 @@ decides which form is meant. `contains`, `events`, and the pull-request subscription opt-outs `reviews`, `checks`, `comments`), `gitlab` (`project`, `labels`, `contains`, `events`), `slack` (`channel`, `contains`), `linear` (`team`, `project`, `labels`, -`contains`), `jira` (`project`, `labels`, `contains`) or `shortcut` +`contains`, `events`), `jira` (`project`, `labels`, `contains`) or `shortcut` (`workspace`, `team`, `labels`, `contains`), each at most once. A Linear -`team` matches the team's name or its key. A GitHub source without +`team` matches the team's name or its key. A Linear `events` is `issues` (the +default — `issue.create`), `assigned` — `AppUserNotification.issueAssignedToYou`, +issues assigned to the connected app user, so assigning a ticket delegates it — +or `all` for both. A GitHub source without `repository` is scoped to `--repo`. `events` is `issues` (the default: `issues.opened` and `issues.labeled`) or `pull_request` — `merge_request` for `gitlab` — which wakes on a pull request being diff --git a/examples/software-factory/README.md b/examples/software-factory/README.md index 6873559a..8cb2e961 100644 --- a/examples/software-factory/README.md +++ b/examples/software-factory/README.md @@ -1,8 +1,8 @@ # software-factory -[![Deploy Flow](https://agentrelay.com/launch-agent_small.svg)](https://agentrelay.com/cloud/flows/deploy?flow=https%3A%2F%2Fgithub.com%2FAgentWorkforce%2Fflows%2Fblob%2Fmain%2Fexamples%2Fsoftware-factory%2Fsoftware-factory.flow.ts&on=linear) +[![Deploy Flow](https://agentrelay.com/launch-agent_small.svg)](https://agentrelay.com/cloud/flows/deploy?flow=https%3A%2F%2Fgithub.com%2FAgentWorkforce%2Fflows%2Fblob%2Fmain%2Fexamples%2Fsoftware-factory%2Fsoftware-factory.flow.ts&on=linear%3Aevents%3Dassigned) -One click deploys this flow to [Agent Relay Cloud](https://agentrelay.com/cloud), running on new Linear issues — set your team on the deploy page. +One click deploys this flow to [Agent Relay Cloud](https://agentrelay.com/cloud), running when a Linear issue is assigned to the agent — set your team on the deploy page. A ticket becomes a pull request: implementation agent → deterministic tests → adversarial review agent → PR opened for a human. The review verdict is a file @@ -12,16 +12,19 @@ neither can be talked into a green result. ```sh flows check examples/software-factory/software-factory.flow.ts flows deploy examples/software-factory/software-factory.flow.ts \ - --repo acme/api --on linear:team=TECH --approver you + --repo acme/api --on linear:events=assigned,team=TECH --approver you flows deployments ``` `--on` also takes `github:labels=agent`, `jira:project=OPS`, `shortcut:workspace=…` -or `slack:channel=#eng`, and `linear` accepts `team`, `project`, `labels` and -`contains` (`linear:team=TECH,labels=agent`); `team` matches the Linear team -name or its key, so `team=Engineering` and `team=TECH` are the same filter. The -deploy page shows every filter as an editable field — the badge only fixes the -provider; team, project and labels are filled in there. +or `slack:channel=#eng`, and `linear` accepts `team`, `project`, `labels`, +`contains` and `events` (`linear:team=TECH,labels=agent`); `team` matches the +Linear team name or its key, so `team=Engineering` and `team=TECH` are the same +filter. `events` is `issues` (the default — new issues), `assigned` (issues +assigned to the connected agent — the badge's choice, so assigning a ticket +delegates it), or `all` for both. The deploy page shows every filter as an +editable field — the badge only fixes the provider and `assigned`; team, +project and labels are filled in there. Each matching ticket launches one Cloud run in a fresh `relayflow/software-factory-` branch of `--repo`; a passing review opens a diff --git a/packages/sdk/src/cloud-deploy.ts b/packages/sdk/src/cloud-deploy.ts index 1339e1b4..d132039c 100644 --- a/packages/sdk/src/cloud-deploy.ts +++ b/packages/sdk/src/cloud-deploy.ts @@ -34,7 +34,9 @@ const PROVIDER_SETTINGS: Record = { github: ['repository', 'labels', 'contains', 'events', 'reviews', 'checks', 'comments'], gitlab: ['project', 'labels', 'contains', 'events'], slack: ['channel', 'contains'], - linear: ['team', 'project', 'labels', 'contains'], + // `events`: `issues` (default), `assigned` (issues assigned to the agent's + // connected app user), or `all` — which Linear records wake the listener. + linear: ['team', 'project', 'labels', 'contains', 'events'], jira: ['project', 'labels', 'contains'], shortcut: ['workspace', 'team', 'labels', 'contains'], }; @@ -137,7 +139,10 @@ export function parseTriggerSource(value: string): FlowTriggerSource { if (key === 'events') { // Cloud's enum is lowercase; send it that way whatever the shell typed. const events = setting.toLowerCase(); - const valid = provider === 'gitlab' ? ['issues', 'merge_request'] : ['issues', 'pull_request']; + const valid = + provider === 'gitlab' ? ['issues', 'merge_request'] + : provider === 'linear' ? ['issues', 'assigned', 'all'] + : ['issues', 'pull_request']; if (!valid.includes(events)) { throw new CloudFlowError('invalid_input', `${provider} events must be ${valid.map(v => `"${v}"`).join(' or ')}, got "${setting}".`); diff --git a/packages/sdk/tests/cloud-deploy.test.ts b/packages/sdk/tests/cloud-deploy.test.ts index 87d641e7..badce1de 100644 --- a/packages/sdk/tests/cloud-deploy.test.ts +++ b/packages/sdk/tests/cloud-deploy.test.ts @@ -64,6 +64,8 @@ describe('trigger source and repository parsing', () => { ['slack:channel=#eng', { provider: 'slack', settings: { channel: '#eng' } }], ['linear:team=ENG', { provider: 'linear', settings: { team: 'ENG' } }], ['linear:team=Engineering,labels=agent,contains=urgent', { provider: 'linear', settings: { team: 'Engineering', labels: 'agent', contains: 'urgent' } }], + ['linear:events=assigned', { provider: 'linear', settings: { events: 'assigned' } }], + ['linear:events=ALL,team=TECH', { provider: 'linear', settings: { events: 'all', team: 'TECH' } }], ['jira:project=OPS,labels=agent', { provider: 'jira', settings: { project: 'OPS', labels: 'agent' } }], ['shortcut:workspace=acme,team=Platform', { provider: 'shortcut', settings: { workspace: 'acme', team: 'Platform' } }], ['gitlab:project=acme/platform/web', { provider: 'gitlab', settings: { project: 'acme/platform/web' } }], @@ -75,7 +77,7 @@ describe('trigger source and repository parsing', () => { expect(parseTriggerSource(value)).toEqual(expected); }); - it.each(['github:channel=x', 'github:labels=', 'github:labels=a,labels=b', 'slack:labels=x', 'github:events=releases', 'gitlab:events=pull_request', 'gitlab:repository=acme/web', 'github:reviews=maybe', 'linear:events=issues']) + it.each(['github:channel=x', 'github:labels=', 'github:labels=a,labels=b', 'slack:labels=x', 'github:events=releases', 'gitlab:events=pull_request', 'gitlab:repository=acme/web', 'github:reviews=maybe', 'linear:events=mentions', 'linear:events=pull_request']) ('refuses %s', (value) => { expect(() => parseTriggerSource(value)).toThrow(expect.objectContaining({ code: 'invalid_input' })); }); From 99f74a68e12c896eb1790cb3e8ecb8361990ad9e Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Thu, 24 Sep 2026 17:43:48 -0700 Subject: [PATCH 2/2] docs: align examples index badge on linear:events=assigned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The top-level examples index still pointed the software-factory badge at `on=linear:team=ENG` (issue-create events); it now carries the same `on=linear:events=assigned` the flow README ships. Describe the assignee as the connected app user — the notification fires for the OAuth app's Linear user, not any agent identity. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- examples/README.md | 2 +- examples/software-factory/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/README.md b/examples/README.md index 961b5fc0..4b91e985 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,7 +6,7 @@ flow straight from this repo, shows its steps, and asks you to connect whatever | Flow | Trigger | Deploy | |---|---|---| | [pr-review-pipeline](pr-review-pipeline/) — three review lenses in parallel, then a consensus pass | every `pull_request` | [![Deploy Flow](https://agentrelay.com/launch-agent_small.svg)](https://agentrelay.com/cloud/flows/deploy?flow=https%3A%2F%2Fgithub.com%2FAgentWorkforce%2Fflows%2Fblob%2Fmain%2Fexamples%2Fpr-review-pipeline%2Fpr-review-pipeline.flow.ts&on=github%3Aevents%3Dpull_request) | -| [software-factory](software-factory/) — implement → test → adversarial review → open a PR | every Linear issue in team `ENG` | [![Deploy Flow](https://agentrelay.com/launch-agent_small.svg)](https://agentrelay.com/cloud/flows/deploy?flow=https%3A%2F%2Fgithub.com%2FAgentWorkforce%2Fflows%2Fblob%2Fmain%2Fexamples%2Fsoftware-factory%2Fsoftware-factory.flow.ts&on=linear%3Ateam%3DENG) | +| [software-factory](software-factory/) — implement → test → adversarial review → open a PR | Linear issues assigned to the connected app user | [![Deploy Flow](https://agentrelay.com/launch-agent_small.svg)](https://agentrelay.com/cloud/flows/deploy?flow=https%3A%2F%2Fgithub.com%2FAgentWorkforce%2Fflows%2Fblob%2Fmain%2Fexamples%2Fsoftware-factory%2Fsoftware-factory.flow.ts&on=linear%3Aevents%3Dassigned) | | [stale-issues](stale-issues/) — triage every open issue, post a Slack digest | schedule (`flows schedule … --cron "0 9 * * 1-5"`) | — | Or from a checkout: `flows deploy --repo --on --approver `. diff --git a/examples/software-factory/README.md b/examples/software-factory/README.md index 8cb2e961..63b7012a 100644 --- a/examples/software-factory/README.md +++ b/examples/software-factory/README.md @@ -2,7 +2,7 @@ [![Deploy Flow](https://agentrelay.com/launch-agent_small.svg)](https://agentrelay.com/cloud/flows/deploy?flow=https%3A%2F%2Fgithub.com%2FAgentWorkforce%2Fflows%2Fblob%2Fmain%2Fexamples%2Fsoftware-factory%2Fsoftware-factory.flow.ts&on=linear%3Aevents%3Dassigned) -One click deploys this flow to [Agent Relay Cloud](https://agentrelay.com/cloud), running when a Linear issue is assigned to the agent — set your team on the deploy page. +One click deploys this flow to [Agent Relay Cloud](https://agentrelay.com/cloud), running when a Linear issue is assigned to the connected app user — set your team on the deploy page. A ticket becomes a pull request: implementation agent → deterministic tests → adversarial review agent → PR opened for a human. The review verdict is a file @@ -21,7 +21,7 @@ or `slack:channel=#eng`, and `linear` accepts `team`, `project`, `labels`, `contains` and `events` (`linear:team=TECH,labels=agent`); `team` matches the Linear team name or its key, so `team=Engineering` and `team=TECH` are the same filter. `events` is `issues` (the default — new issues), `assigned` (issues -assigned to the connected agent — the badge's choice, so assigning a ticket +assigned to the connected app user — the badge's choice, so assigning a ticket delegates it), or `all` for both. The deploy page shows every filter as an editable field — the badge only fixes the provider and `assigned`; team, project and labels are filled in there.