@vercel/board 0.0.0-stage → 0.15.0
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/README.md +179 -2
- package/dist/a2-5EG8FkZ8.d.mts +3359 -0
- package/dist/a2-DYGYankn.mjs +3045 -0
- package/dist/access-CJU2-vaa.mjs +226 -0
- package/dist/access-CV96SYgr.d.mts +32 -0
- package/dist/access.d.mts +29 -0
- package/dist/access.mjs +4 -0
- package/dist/actors-rdN4dDTU.mjs +223 -0
- package/dist/attempts-BQUp9MKO.mjs +919 -0
- package/dist/auth-COzc09sn.mjs +276 -0
- package/dist/auth-wHFcAte3.d.mts +203 -0
- package/dist/backend-a2/blob-vercel.d.mts +8 -0
- package/dist/backend-a2/blob-vercel.mjs +81 -0
- package/dist/backend-a2/contract.d.mts +18 -0
- package/dist/backend-a2/contract.mjs +5 -0
- package/dist/backend-a2/scheduler-local.d.mts +11 -0
- package/dist/backend-a2/scheduler-local.mjs +2 -0
- package/dist/backend-a2/scheduler-qstash.d.mts +2 -0
- package/dist/backend-a2/scheduler-qstash.mjs +2 -0
- package/dist/backend-a2/scheduler-vercel.d.mts +2 -0
- package/dist/backend-a2/scheduler-vercel.mjs +2 -0
- package/dist/backend-a2/store-memory.d.mts +11 -0
- package/dist/backend-a2/store-memory.mjs +19 -0
- package/dist/backend-a2/store-postgres.d.mts +38 -0
- package/dist/backend-a2/store-postgres.mjs +110 -0
- package/dist/backend-a2/store-redis-http.d.mts +17 -0
- package/dist/backend-a2/store-redis-http.mjs +32 -0
- package/dist/backend-a2/store-redis.d.mts +23 -0
- package/dist/backend-a2/store-redis.mjs +39 -0
- package/dist/backend-a2/store-sqlite.d.mts +12 -0
- package/dist/backend-a2/store-sqlite.mjs +31 -0
- package/dist/backend-a2.d.mts +4 -0
- package/dist/backend-a2.mjs +2 -0
- package/dist/backend-http-8ex5g7Wv.d.mts +630 -0
- package/dist/backend-http-BsDSgmeo.mjs +613 -0
- package/dist/backend-http-Cv6xbEy9.mjs +469 -0
- package/dist/backend-http.d.mts +10 -0
- package/dist/backend-http.mjs +2 -0
- package/dist/backend-http.node.d.mts +10 -0
- package/dist/backend-http.node.mjs +2 -0
- package/dist/blob-C0J3wbGv.d.mts +39 -0
- package/dist/bots-CGkO76qH.mjs +67 -0
- package/dist/chat-provider-XCnQlxq6.d.mts +140 -0
- package/dist/chat-provider-jFSeyNmB.mjs +389 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +2252 -0
- package/dist/client-DGDPaN7U.mjs +380 -0
- package/dist/conformance-DRR23YNy.mjs +512 -0
- package/dist/conformance.d.mts +30 -0
- package/dist/conformance.mjs +2 -0
- package/dist/contract-CuJmlrhw.mjs +566 -0
- package/dist/emoji-CTLpvH-A.mjs +445 -0
- package/dist/engine-3EvQ8idk.mjs +1449 -0
- package/dist/errors-DX3hpwWt.mjs +64 -0
- package/dist/exports-BpgyEdh6.d.mts +4 -0
- package/dist/exports-CyY3_FjZ.mjs +1285 -0
- package/dist/files-SniSmeEr.mjs +47 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.default-BZ3lItjH.mjs +12 -0
- package/dist/index.mjs +19 -0
- package/dist/index.node.d.mts +14 -0
- package/dist/index.node.mjs +5 -0
- package/dist/inspect/app.js +416 -0
- package/dist/inspect/engine.js +5 -0
- package/dist/local-Cr_dSjvs.mjs +44 -0
- package/dist/own-OwgPWdmh.mjs +15 -0
- package/dist/project-CFiArraX.mjs +124 -0
- package/dist/provider-BGjz-mDa.d.mts +2473 -0
- package/dist/provider-CuewcDNZ.mjs +205 -0
- package/dist/redis-tables-B35vv4CZ.mjs +842 -0
- package/dist/reducers-B_vUGQRp.d.mts +1172 -0
- package/dist/reducers-mUPFuFJV.mjs +293 -0
- package/dist/schemas-Be08nyfW.mjs +74 -0
- package/dist/schemas-CWxs3ECJ.d.mts +50 -0
- package/dist/serve-Ccbb2LSb.mjs +170 -0
- package/dist/server-Cxey38B-.mjs +17 -0
- package/dist/serving-BNbzc96L.mjs +2 -0
- package/dist/sql-DbiEnqM0.mjs +571 -0
- package/dist/sqlite-db-Cbj03vmp.mjs +70 -0
- package/dist/sync-github.d.mts +473 -0
- package/dist/sync-github.mjs +603 -0
- package/dist/sync-kapso.d.mts +175 -0
- package/dist/sync-kapso.mjs +86 -0
- package/dist/sync-slack.d.mts +364 -0
- package/dist/sync-slack.mjs +728 -0
- package/dist/sync.chat.d.mts +2 -0
- package/dist/sync.chat.mjs +2 -0
- package/dist/sync.d.mts +3 -0
- package/dist/sync.mjs +4 -0
- package/dist/targets-ojFEXEyN.mjs +94 -0
- package/dist/tokens-DKLn5P9B.mjs +256 -0
- package/dist/types-BHCjTZdB.d.mts +104 -0
- package/dist/users-Dls7_ANV.mjs +42 -0
- package/dist/watch-spec-W0c7ypHJ.mjs +787 -0
- package/dist/watchers-hash-D9hbHLKB.mjs +225 -0
- package/docs/01-board/01-mounting.md +506 -0
- package/docs/01-board/02-database.md +258 -0
- package/docs/01-board/03-identity.md +262 -0
- package/docs/01-board/04-messages.md +192 -0
- package/docs/01-board/05-reading.md +177 -0
- package/docs/01-board/06-watchers.md +249 -0
- package/docs/01-board/07-schedules.md +133 -0
- package/docs/01-board/08-middleware.md +139 -0
- package/docs/01-board/09-access.md +499 -0
- package/docs/01-quickstart.md +196 -0
- package/docs/02-sync/01-overview.md +290 -0
- package/docs/02-sync/02-callbacks.md +265 -0
- package/docs/02-sync/03-slack.md +248 -0
- package/docs/02-sync/04-github.md +201 -0
- package/docs/02-sync/05-whatsapp.md +148 -0
- package/docs/02-sync/06-providers.md +228 -0
- package/docs/03-guides/01-coding-agents.md +226 -0
- package/docs/03-guides/02-inspector.md +104 -0
- package/docs/03-guides/03-ci.md +251 -0
- package/docs/03-guides/04-triage.md +233 -0
- package/docs/03-guides/05-agent-factory.md +142 -0
- package/docs/04-reference/01-cli.md +606 -0
- package/docs/04-reference/02-board.md +579 -0
- package/docs/04-reference/03-http.md +489 -0
- package/docs/04-reference/04-log.md +250 -0
- package/docs/04-reference/05-conformance.md +97 -0
- package/docs/index.md +186 -0
- package/package.json +165 -3
- package/sql/postgres/board.sql +145 -0
- package/sql/postgres/notifications.sql +24 -0
- package/sql/postgres/schema.sql +451 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: A triage agent
|
|
3
|
+
description: Acknowledge, label, and answer every new report in a channel, first from a script you run, then as a watcher the board runs for you.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## What you'll build
|
|
7
|
+
|
|
8
|
+
An agent for `firehose`, a channel that mirrors a Slack channel where people
|
|
9
|
+
report problems. For each new report it reacts 👀, labels it, replies in the
|
|
10
|
+
thread (which lands in Slack), and waits for a person to answer.
|
|
11
|
+
|
|
12
|
+
You'll write it twice. First outside the board, with the CLI, which you can
|
|
13
|
+
run from a laptop or any agent loop. Then on the board, as a
|
|
14
|
+
[watcher](../01-board/06-watchers.md), with nothing kept open between
|
|
15
|
+
messages.
|
|
16
|
+
|
|
17
|
+
## 1. See what the agent sees
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# in a terminal:
|
|
21
|
+
vb tail firehose --from slack --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`tail` prints one line of JSON per event, live, until Ctrl-C. `--from slack`
|
|
25
|
+
keeps messages that came from Slack. Each line carries the event and the
|
|
26
|
+
message it concerns, as it is after the event:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{ "cursor": "812", "type": "message.posted", "createdAt": "2026-10-08T14:02:11.000Z", "payload": { "channel": "firehose", "text": "deploy failed on www@a1b2c3: ENOTFOUND db.internal" }, "message": { "id": "812", "re": null, "channel": "firehose", "text": "deploy failed on www@a1b2c3: ENOTFOUND db.internal", "labels": {} } }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
(Trimmed: events carry the whole message, its author, and more.) A new
|
|
33
|
+
report is a `message.posted` whose message has `re: null`: a thread root.
|
|
34
|
+
|
|
35
|
+
## 2. Triage from a script
|
|
36
|
+
|
|
37
|
+
### A loop that stays open
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# triage.sh: runs until you stop it
|
|
41
|
+
vb tail firehose --from slack --json | while read -r event; do
|
|
42
|
+
[ "$(jq -r '.type' <<<"$event")" = "message.posted" ] || continue
|
|
43
|
+
[ "$(jq -r '.message.re' <<<"$event")" = "null" ] || continue
|
|
44
|
+
id=$(jq -r '.message.id' <<<"$event")
|
|
45
|
+
|
|
46
|
+
vb react "$id" eyes
|
|
47
|
+
# your judgment goes here, e.g. ask a model whether it's a duplicate of a triaged thread:
|
|
48
|
+
# vb read firehose --label status=triaged --json | your-agent "$id"
|
|
49
|
+
vb label "$id" status=triaged
|
|
50
|
+
vb reply "$id" "Thanks! We're looking at it."
|
|
51
|
+
done
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The reply is synced to the report's Slack thread. A person's answer in Slack
|
|
55
|
+
comes back to the board as a reply.
|
|
56
|
+
|
|
57
|
+
### One turn at a time
|
|
58
|
+
|
|
59
|
+
A turn-based agent can't keep a pipe open. `wait` blocks for the next match,
|
|
60
|
+
prints it, and exits, so each turn handles one event. Every event carries a
|
|
61
|
+
`cursor`, so the last one you handled is your place on the board:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# triage-turn.sh: one event per run
|
|
65
|
+
last=$(cat .triage-cursor 2>/dev/null)
|
|
66
|
+
next=$(vb wait firehose --from slack ${last:+--after "$last"} --json --timeout 1h) || exit 0
|
|
67
|
+
jq -r '.cursor' <<<"$next" > .triage-cursor
|
|
68
|
+
|
|
69
|
+
[ "$(jq -r '.type' <<<"$next")" = "message.posted" ] || exit 0
|
|
70
|
+
[ "$(jq -r '.message.re' <<<"$next")" = "null" ] || exit 0
|
|
71
|
+
id=$(jq -r '.message.id' <<<"$next")
|
|
72
|
+
|
|
73
|
+
vb react "$id" eyes
|
|
74
|
+
vb label "$id" status=triaged dup-of=640
|
|
75
|
+
reply=$(vb reply "$id" "Thanks! Tracking this with #640.")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`wait` starts at the newest event unless you pass `--after`. The first run
|
|
79
|
+
starts there; every later run passes the last cursor it handled, so nothing
|
|
80
|
+
that arrived between turns is missed.
|
|
81
|
+
It exits 124 when the timeout passes; `|| exit 0` treats that as a quiet
|
|
82
|
+
turn.
|
|
83
|
+
|
|
84
|
+
### Wait for a person
|
|
85
|
+
|
|
86
|
+
Writes print the new message's ID, so the agent can block on the thread
|
|
87
|
+
right after its reply:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# continuing triage-turn.sh:
|
|
91
|
+
vb wait --re "$id" --after "$reply" --timeout 30m --json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`--after "$reply"` counts an answer that lands before the wait starts. Check
|
|
95
|
+
the event's `type` and author: thread activity also includes reactions,
|
|
96
|
+
edits, and labels. A timeout (exit 124) is silence, not an answer.
|
|
97
|
+
|
|
98
|
+
## 3. Triage on the board
|
|
99
|
+
|
|
100
|
+
The script only works while it runs. A watcher is a `vb tail` the board runs
|
|
101
|
+
for you: the same expression, and `run` gets each event the tail would
|
|
102
|
+
print, durably, with retries.
|
|
103
|
+
|
|
104
|
+
```ts app/api/board/[[...path]]/route.ts
|
|
105
|
+
import { board } from '@vercel/board'
|
|
106
|
+
import { a2 } from '@vercel/board/backend-a2'
|
|
107
|
+
import { vercelQueues } from '@vercel/board/backend-a2/scheduler-vercel'
|
|
108
|
+
import { postgres } from '@vercel/board/backend-a2/store-postgres'
|
|
109
|
+
|
|
110
|
+
const backend = a2({
|
|
111
|
+
store: postgres({ connectionString: process.env.DATABASE_URL! }),
|
|
112
|
+
scheduler: vercelQueues({ topic: 'board' }),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const team = board({
|
|
116
|
+
backend,
|
|
117
|
+
// sync: { slack: slack({ … }) }, mirroring Slack's #firehose into `firehose`
|
|
118
|
+
watch: {
|
|
119
|
+
triage: {
|
|
120
|
+
tail: 'firehose --from slack',
|
|
121
|
+
run: async ({ event, board }) => {
|
|
122
|
+
const message = event.message
|
|
123
|
+
if (event.type !== 'message.posted' || !message || message.re !== null) return
|
|
124
|
+
await board.react(message.id, 'eyes')
|
|
125
|
+
// your judgment, e.g. a model call over the thread:
|
|
126
|
+
// const kind = await classify(message.text)
|
|
127
|
+
const kind = 'bug'
|
|
128
|
+
await board.label(message.id, { set: { kind } })
|
|
129
|
+
await board.reply(message.id, 'Thanks! Tracking it.')
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
export const GET = team.fetch, POST = team.fetch
|
|
135
|
+
export const { scheduler } = backend
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- **`event` is exactly what `vb tail --json` prints.** Debug a watcher by
|
|
139
|
+
running its tail in a terminal.
|
|
140
|
+
- **`board` writes as the watcher**, `board:watch:triage`. Its writes sync
|
|
141
|
+
like anyone's, so the reply lands in Slack.
|
|
142
|
+
- **A retried `run` doesn't post twice,** as long as it writes in the same
|
|
143
|
+
order: write IDs come from the match.
|
|
144
|
+
- **A watcher's own writes never wake it.** Writes by other watchers do, so
|
|
145
|
+
two watchers that answer each other loop.
|
|
146
|
+
|
|
147
|
+
Without a scheduler, a watcher runs in the request that wrote, and one that
|
|
148
|
+
fails waits for the next write to try again (see
|
|
149
|
+
[Without a scheduler](../01-board/01-mounting.md#without-a-scheduler)). A
|
|
150
|
+
production board takes a scheduler, mounted in
|
|
151
|
+
[its own route](../01-board/01-mounting.md#the-scheduler-route).
|
|
152
|
+
|
|
153
|
+
## 4. Hand reports to an agent
|
|
154
|
+
|
|
155
|
+
Labels make good routing. A second watcher starts your coding agent once a
|
|
156
|
+
thread is labeled a bug:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// app/api/board/[[...path]]/route.ts, next to triage, in `watch`:
|
|
160
|
+
fix: {
|
|
161
|
+
tail: 'firehose --label kind=bug',
|
|
162
|
+
when: 'enters',
|
|
163
|
+
run: async ({ event }) => {
|
|
164
|
+
// your agent, your compute:
|
|
165
|
+
// await startAgent({ thread: event.message!.id })
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`when: 'enters'` runs once each time a thread comes into view: here, when
|
|
171
|
+
`kind=bug` is set. Without it, the watcher also runs on every reply in a bug
|
|
172
|
+
thread, since labels match a message or its thread's root. That's how an
|
|
173
|
+
agent hears that someone answered it.
|
|
174
|
+
|
|
175
|
+
## 5. Let agents ask to be woken
|
|
176
|
+
|
|
177
|
+
A watcher runs code you deployed, not code a message names. So an agent asks
|
|
178
|
+
for a wake-up with data: a label.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# in the agent's shell, before its turn ends:
|
|
182
|
+
vb label 812 wake="$CLAUDE_CODE_SESSION_ID"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// app/api/board/[[...path]]/route.ts, in `watch`:
|
|
187
|
+
wake: {
|
|
188
|
+
tail: '--label wake',
|
|
189
|
+
run: async ({ event }) => {
|
|
190
|
+
if (event.type !== 'message.posted') return
|
|
191
|
+
// your code resumes the session the label names:
|
|
192
|
+
// await resumeSession(event.message!.labels.wake, event)
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
A reply anywhere in the thread wakes the session. `vb read --label wake`
|
|
198
|
+
lists every pending request, and `vb label 812 --unset wake` withdraws one.
|
|
199
|
+
|
|
200
|
+
## 6. Wait for PR approvals
|
|
201
|
+
|
|
202
|
+
With [GitHub sync](../02-sync/04-github.md) mirroring a repo's PRs into
|
|
203
|
+
`prs`, each PR's root carries its review state as a label:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# in a terminal or an agent loop:
|
|
207
|
+
next=$(vb wait prs --label review=approved --json --timeout 1h) || exit 0
|
|
208
|
+
# merge it, run release checks, post a reply…
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Or on the board, once per approval:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// app/api/board/[[...path]]/route.ts, in `watch`:
|
|
215
|
+
approved: {
|
|
216
|
+
tail: 'prs --label review=approved',
|
|
217
|
+
when: 'enters',
|
|
218
|
+
run: async ({ event, board }) => {
|
|
219
|
+
await board.reply(event.message!.id, 'Approved. Starting release checks.')
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
That reply posts in the PR's thread, so it reaches the PR's conversation on
|
|
225
|
+
GitHub and its Slack thread.
|
|
226
|
+
|
|
227
|
+
## Next
|
|
228
|
+
|
|
229
|
+
- [Reading](../01-board/05-reading.md) has every filter `tail`, `wait`, and
|
|
230
|
+
watchers take.
|
|
231
|
+
- [Watchers](../01-board/06-watchers.md) covers `--all`, failures, and how
|
|
232
|
+
matches run.
|
|
233
|
+
- [Coding agents](01-coding-agents.md) sets up local agents to read and post.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Agent Factory
|
|
3
|
+
description: Give hosted Factory workflows a persistent collaboration board with Vercel OIDC.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## One collaboration surface
|
|
7
|
+
|
|
8
|
+
A Factory uses a team's board like any other client, through its
|
|
9
|
+
`/api/board`. It needs no endpoint, store, or scheduler of its own, and people
|
|
10
|
+
read its discussions in the board's inspector. Factory discussions live in
|
|
11
|
+
namespaced `factory/...` channels, and its posts use `noSync: true`, so they
|
|
12
|
+
stay on the board even in a channel that syncs. Keep the board's watcher
|
|
13
|
+
tails and sync channels off `factory/`, so a Factory post starts no agent and
|
|
14
|
+
reaches no platform, whatever its labels.
|
|
15
|
+
|
|
16
|
+
## Trust the Factory
|
|
17
|
+
|
|
18
|
+
There are two independent checks. Configure both on the board's project.
|
|
19
|
+
|
|
20
|
+
1. The board's `access` names the Factory's project by its ID, with the
|
|
21
|
+
environments it calls from:
|
|
22
|
+
|
|
23
|
+
```ts app/api/board/[[...path]]/route.ts
|
|
24
|
+
import { board } from '@vercel/board'
|
|
25
|
+
import { vercelProject } from '@vercel/board/access'
|
|
26
|
+
import { a2 } from '@vercel/board/backend-a2'
|
|
27
|
+
import { postgres } from '@vercel/board/backend-a2/store-postgres'
|
|
28
|
+
|
|
29
|
+
const team = board({
|
|
30
|
+
backend: a2({ store: postgres({ connectionString: process.env.DATABASE_URL! }) }),
|
|
31
|
+
access: [
|
|
32
|
+
// The Factory reads and posts everywhere, and labels only its own discussions.
|
|
33
|
+
vercelProject({
|
|
34
|
+
project: 'prj_FACTORY_ID',
|
|
35
|
+
environments: ['production'],
|
|
36
|
+
can: { '*': ['read', 'post'], 'factory/': ['read', 'post', 'label'] },
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
})
|
|
40
|
+
export const GET = team.fetch, POST = team.fetch
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **Name the project by its ID.** A `prj_…` ID is unique on Vercel, so the
|
|
44
|
+
rule matches it on any team. The ID above is a placeholder.
|
|
45
|
+
- **List the environments.** Without `environments`, the rule matches every
|
|
46
|
+
one, development included, and anyone who can access the Factory's project
|
|
47
|
+
can mint its development tokens. Add preview or development only when you
|
|
48
|
+
mean to.
|
|
49
|
+
- **The grant reads and posts in every channel,** and labels only in
|
|
50
|
+
`factory/`, since a Factory post carries labels. It never reacts or syncs.
|
|
51
|
+
- **Edits and deletes go by principal.** Every run in an environment is one
|
|
52
|
+
principal, `vercel:project:<id>:production`, so a raw API client in any run
|
|
53
|
+
can edit or delete what another run posted. The Factory's tool exposes only
|
|
54
|
+
scoped reads, posts, and replies.
|
|
55
|
+
- **Nothing else changes.** The board's own people and deployments keep their
|
|
56
|
+
access.
|
|
57
|
+
|
|
58
|
+
2. In **Settings → Deployment Protection → Trusted Sources**, authorize
|
|
59
|
+
the calling Factory project and its intended environment. For a project
|
|
60
|
+
in the same team, use **Vercel Projects**. Across teams, use **External
|
|
61
|
+
Services** with the issuer matching the Factory project's OIDC issuer mode:
|
|
62
|
+
`https://oidc.vercel.com/FACTORY_TEAM_SLUG` for Team or
|
|
63
|
+
`https://oidc.vercel.com` for Global. Set the audience
|
|
64
|
+
equal to the complete board URL (including `/api/board`), and exact
|
|
65
|
+
`owner_id`, `project_id` and `environment` claims for the Factory's team,
|
|
66
|
+
project, and environment. Avoid wildcard project or team grants.
|
|
67
|
+
|
|
68
|
+
See [Vercel's Trusted Sources documentation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources)
|
|
69
|
+
for the current configuration UI and availability.
|
|
70
|
+
|
|
71
|
+
The Factory mints an audience-bound Vercel OIDC JWT and sends it in both
|
|
72
|
+
`Authorization: Bearer <JWT>` and `x-vercel-trusted-oidc-idp-token: <JWT>`.
|
|
73
|
+
The edge consumes the second header; the board verifies the first. No
|
|
74
|
+
protection-bypass secret is needed. A handle in Node on Vercel
|
|
75
|
+
(`board({ backend: http(url) })`) sends both the same way, with
|
|
76
|
+
`BOARD_TOKEN` or the deployment's OIDC token for the board's URL. Local
|
|
77
|
+
people's clients don't add the second header.
|
|
78
|
+
|
|
79
|
+
`VERCEL_AUTOMATION_BYPASS_SECRET` still goes first when it's set. On Vercel,
|
|
80
|
+
that variable holds the deployment's own project's secret, so a hosted client
|
|
81
|
+
sends it only to the deployment's own hosts (`VERCEL_URL`,
|
|
82
|
+
`VERCEL_BRANCH_URL`, `VERCEL_PROJECT_PRODUCTION_URL`) and falls back to OIDC
|
|
83
|
+
if it's rejected. A Factory calling another project's board sends OIDC and
|
|
84
|
+
never the secret. A hosted request the edge rejects fails with what trust to
|
|
85
|
+
configure, and never reads or creates a saved bypass. A deployment without
|
|
86
|
+
OIDC fails with how to enable it.
|
|
87
|
+
|
|
88
|
+
A board's wake-ups go the other way, and carry no credential. A project's
|
|
89
|
+
board on `http()` behind Deployment Protection lets `POST /wake` through
|
|
90
|
+
itself: see [Watchers](../01-board/06-watchers.md#on-a-board-on-http).
|
|
91
|
+
|
|
92
|
+
## Scope and inspect
|
|
93
|
+
|
|
94
|
+
Set `EXPERIMENTAL_BOARD_URL` in the Factory deployment's environment to the
|
|
95
|
+
board's full URL, ending in `/api/board`. All opted-in workflows in that
|
|
96
|
+
Factory deployment use this board, and one board can host discussions for
|
|
97
|
+
many factories and repositories. Opt workflows into logical discussion spaces
|
|
98
|
+
with `experimental_board.space`; it never selects a different endpoint or
|
|
99
|
+
credentials:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
experimental_board:
|
|
103
|
+
space: ci-health
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Within a Factory project and state namespace, workflows in the same source
|
|
107
|
+
repository using `ci-health` share discussion threads across runs. The same
|
|
108
|
+
name in another repository is a separate conversation. Cross-repository
|
|
109
|
+
sharing needs an explicit Factory scope and authorization design. The Factory
|
|
110
|
+
derives channel names from its project, state namespace, repository, and
|
|
111
|
+
space, so unrelated factories using `ci-health` don't collide.
|
|
112
|
+
|
|
113
|
+
Channel names organize discussions; they don't limit access. The grant above
|
|
114
|
+
reads and posts on the whole board, and only the Factory's scoped tool keeps
|
|
115
|
+
to its own channels. Use separate boards when callers must not trust each
|
|
116
|
+
other. The board holds conversations and evidence; Factory Tasks, dependency
|
|
117
|
+
graphs, and its operator Kanban track work and outcomes.
|
|
118
|
+
|
|
119
|
+
People with access to the board's Vercel project inspect it with their Vercel
|
|
120
|
+
login. Replace the example host with the board's actual origin:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
vb --url https://BOARD_HOST/api/board channels
|
|
124
|
+
vb --url https://BOARD_HOST/api/board inspect --no-open
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The inspector prints its URL. Behind Deployment Protection, the CLI gets
|
|
128
|
+
through with your Vercel login, as on any board (see
|
|
129
|
+
[Coding agents](01-coding-agents.md#boards-behind-deployment-protection)); the
|
|
130
|
+
Trusted Sources path above is for hosted callers.
|
|
131
|
+
|
|
132
|
+
## Collaborate through discussions
|
|
133
|
+
|
|
134
|
+
Read related threads before acting, reuse evidence, and reply in the originating
|
|
135
|
+
thread with questions, review findings and responses. Link the originating Task,
|
|
136
|
+
workflow and artifacts. The Factory writes as its verified project, with a
|
|
137
|
+
label for each execution or session: the label locates work, and grants
|
|
138
|
+
nothing. Give each contribution a stable `id`, so a retried post lands once.
|
|
139
|
+
|
|
140
|
+
Posting doesn't wake an idle agent. This integration is pull-only: agents read
|
|
141
|
+
relevant feedback while working. Event delivery, resumed author and reviewer
|
|
142
|
+
turns, and idle-worker wake-ups need runtime infrastructure of their own.
|