@xpufx/paseo-forges 0.1.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/LICENSE +21 -0
- package/README.md +109 -0
- package/client/active-forge.ts +47 -0
- package/client/board-alert.tsx +236 -0
- package/client/foreign-link.tsx +33 -0
- package/client/hook-queue-panel.tsx +412 -0
- package/client/issues-pill.tsx +1976 -0
- package/client/label-chip.tsx +116 -0
- package/client/linkifier.tsx +280 -0
- package/client/pill-label.ts +183 -0
- package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
- package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
- package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
- package/client/vendor/paseo-plugin-helper/host.ts +277 -0
- package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/index.ts +28 -0
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
- package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
- package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
- package/client/vendor/paseo-plugin-helper/query.ts +66 -0
- package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
- package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
- package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
- package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
- package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
- package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
- package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
- package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
- package/client/webhook-card.tsx +198 -0
- package/docs/specs/forge-workflow-gui.md +820 -0
- package/docs/workflow.md +339 -0
- package/examples/README.md +108 -0
- package/examples/hook-service/README.md +66 -0
- package/examples/hook-service/forge-hook.service +28 -0
- package/examples/hook-service/hook-server.mjs +265 -0
- package/examples/hook-service/hook.env.example +21 -0
- package/examples/labels/README.md +64 -0
- package/examples/labels/label-base.yaml +117 -0
- package/examples/skills/coding-agent/SKILL.md +262 -0
- package/examples/skills/coding-agent-fgjx/SKILL.md +271 -0
- package/examples/skills/orchestrator/SKILL.md +133 -0
- package/examples/skills/orchestrator-fgjx/SKILL.md +139 -0
- package/examples/tools/README.md +68 -0
- package/examples/tools/fgjx +464 -0
- package/package.json +38 -0
- package/paseo-plugin.json +4 -0
- package/server/forge-client.ts +484 -0
- package/server/forge-guard.ts +70 -0
- package/server/git-origin.ts +70 -0
- package/server/hook-queue.ts +127 -0
- package/server/issues.ts +542 -0
- package/server/settings.ts +52 -0
- package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
- package/server/vendor/paseo-plugin-helper/index.ts +18 -0
- package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
- package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
- package/server/vendor/paseo-plugin-helper/network.ts +91 -0
- package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
- package/server/vendor/paseo-plugin-helper/process.ts +186 -0
- package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
- package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
- package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
- package/server/vendor/paseo-plugin-helper/system.ts +128 -0
- package/server/vendor/paseo-plugin-helper/task.ts +116 -0
- package/server/vendor/paseo-plugin-helper/version.ts +153 -0
- package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
- package/shared/hook-queue.ts +140 -0
- package/shared/issues.ts +1750 -0
- package/shared/vendor/paseo-plugin-helper/README.md +11 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
- package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
- package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
- package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
- package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
- package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
- package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
- package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
- package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
- package/shared/version.ts +2 -0
- package/shared/webhook.ts +223 -0
package/docs/workflow.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# The agent workflow around `forges`
|
|
2
|
+
|
|
3
|
+
This document describes the end-to-end loop that turns board activity in a
|
|
4
|
+
Forgejo/Gitea-family repo into work on an agent fleet, and back into comments,
|
|
5
|
+
commits, and label state on that board. It is written for an **adopter**: it
|
|
6
|
+
names the parts the `forges` plugin itself ships, the parts you must supply,
|
|
7
|
+
and the exact seams between them.
|
|
8
|
+
|
|
9
|
+
Everything here is generic. Hosts, repos, paths, unit names, tokens, and agent
|
|
10
|
+
identities are placeholders (`forge.example.com`, `your-org/your-repo`,
|
|
11
|
+
`/opt/forge-hook`, ...). Replace them with your own. The running copy of one
|
|
12
|
+
team's board is the worked example, not a supported configuration.
|
|
13
|
+
|
|
14
|
+
> New here? Start with [`../README.md`](../README.md) for install and settings,
|
|
15
|
+
> then [`../examples/README.md`](../examples/README.md) for the copy-and-adapt
|
|
16
|
+
> Skills. This file is the connective tissue between them.
|
|
17
|
+
|
|
18
|
+
## 1. The loop at a glance
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
forge.example.com Paseo daemon
|
|
22
|
+
┌───────────────┐ webhook ┌──────────────────┐ message ┌──────────────┐
|
|
23
|
+
│ issues, PRs, │ ───────────▶ │ hook service │ ──────────▶ │ orchestrator │
|
|
24
|
+
│ comments, │ │ (systemd or │ `paseo │ agent │
|
|
25
|
+
│ label writes │ │ workspace svc) │ send` └──────┬───────┘
|
|
26
|
+
└───────▲───────┘ └──────────────────┘ │ dispatch
|
|
27
|
+
│ ▼
|
|
28
|
+
│ plugin RPCs (forge.set-label / forge.add-comment) ┌──────────┐
|
|
29
|
+
└────────────────────────────────────────────────────────── │ coding │
|
|
30
|
+
labels, comments, commits, envelope reports │ agents │
|
|
31
|
+
└──────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Four moving parts:
|
|
35
|
+
|
|
36
|
+
1. **The forge** emits events (issue opened/labeled, comment posted, push, PR)
|
|
37
|
+
as webhooks. It also stores all durable state: issues, comments, labels,
|
|
38
|
+
commits.
|
|
39
|
+
2. **The hook service** receives those events, authenticates them, and forwards
|
|
40
|
+
a human-readable summary to the Paseo daemon's agent transport.
|
|
41
|
+
3. **The Paseo daemon + agents** do the work. The `forges` plugin gives the
|
|
42
|
+
agent and the operator in-client views of the board and RPCs to write back
|
|
43
|
+
(`forge.set-label`, `forge.add-comment`).
|
|
44
|
+
4. **The board** is the source of truth for what is done, claimed, blocked, or
|
|
45
|
+
waiting on a human — expressed through scoped labels and envelope-stamped
|
|
46
|
+
comments.
|
|
47
|
+
|
|
48
|
+
The plugin covers step 3's *reading and writing* of the board. Steps 1, 2, and
|
|
49
|
+
4 are adopter-supplied, with example material in [`../examples/`](../examples/).
|
|
50
|
+
|
|
51
|
+
## 2. The hook service
|
|
52
|
+
|
|
53
|
+
The plugin never talks to the forge's webhook side. It only fetches and writes
|
|
54
|
+
issues through the Gitea-family `/api/v1`. Something must bridge the forge's
|
|
55
|
+
*events* into the daemon, because a new comment or label change is what wakes
|
|
56
|
+
an idle fleet.
|
|
57
|
+
|
|
58
|
+
A minimal bridge is one HTTP listener with two endpoints. The shipped example
|
|
59
|
+
skeleton is [`../examples/hook-service/hook-server.mjs`](../examples/hook-service/hook-server.mjs);
|
|
60
|
+
its contract is:
|
|
61
|
+
|
|
62
|
+
### `POST /hook` — event delivery
|
|
63
|
+
|
|
64
|
+
- Authenticated with a **shared secret** supplied by the forge webhook config.
|
|
65
|
+
Accept any of: `Authorization: Bearer <secret>`, `x-webhook-secret: <secret>`,
|
|
66
|
+
or `X-Forgejo-Signature: <hmac-sha256-hex>` over the raw body. The secret is
|
|
67
|
+
server-side only and is never handed to agents.
|
|
68
|
+
- The body is the forge's webhook JSON. The service derives the routing key
|
|
69
|
+
from the payload's repository (`host/owner/repo`) — never from the working
|
|
70
|
+
directory, so one process can serve many repos.
|
|
71
|
+
- It renders a short human line and hands it to the daemon, e.g.:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
🔔 Forgejo webhook incoming [issues:labeled] your-org/your-repo#42 Fix the thing (by alice) https://forge.example.com/your-org/your-repo/issues/42
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The `forges` plugin parses exactly this summary line (and an optional
|
|
78
|
+
`[forgejo-hook] {json}` envelope prefix) into a timeline card — see
|
|
79
|
+
`plugins/forges/shared/webhook.ts`. Keep the shape if you want the card.
|
|
80
|
+
|
|
81
|
+
### `POST /orchestrate` — who owns this repo
|
|
82
|
+
|
|
83
|
+
A repo has at most one orchestrator at a time. The service keeps a small
|
|
84
|
+
per-repo state file recording the current orchestrator's agent id; the body is
|
|
85
|
+
`{ "agentId": "...", "repo": "host/owner/repo" }` (repo optional — derived from
|
|
86
|
+
the agent's checkout `origin` when omitted). Loopback callers need no secret so
|
|
87
|
+
a local agent can claim the role with a plain `curl`; off-box callers must
|
|
88
|
+
present the secret.
|
|
89
|
+
|
|
90
|
+
Agent **name/role labels are a projection for the UI** — the state file is the
|
|
91
|
+
authority. Treat the projection as observability, not as a lock.
|
|
92
|
+
|
|
93
|
+
### Delivery and coalescing
|
|
94
|
+
|
|
95
|
+
- Messages queue **per repo** and are delivered one at a time, because a daemon
|
|
96
|
+
rejects a second send while an agent already has an active run.
|
|
97
|
+
- A short debounce coalesces a burst on the same `(repo, issue)` into one
|
|
98
|
+
digest. **Bypass the debounce** for slash-commands in comments and for
|
|
99
|
+
urgent labels (`priority/0-SOS`, `flag/stop-work`) so those never lag.
|
|
100
|
+
- With no orchestrator registered for a repo, the queue holds rather than
|
|
101
|
+
drops. Your service should log that state loudly; it is the most common
|
|
102
|
+
"nothing happened" cause.
|
|
103
|
+
|
|
104
|
+
### Option A — systemd unit (host-wide, many repos)
|
|
105
|
+
|
|
106
|
+
Use this when the service runs once for the whole machine. The example unit is
|
|
107
|
+
[`../examples/hook-service/forge-hook.service`](../examples/hook-service/forge-hook.service)
|
|
108
|
+
with an environment file at
|
|
109
|
+
[`../examples/hook-service/hook.env.example`](../examples/hook-service/hook.env.example).
|
|
110
|
+
Install, edit the placeholders, then:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
sudo cp forge-hook.service /etc/systemd/system/
|
|
114
|
+
sudo cp hook.env.example /etc/forge-hook/hook.env # then edit
|
|
115
|
+
sudo systemctl daemon-reload
|
|
116
|
+
sudo systemctl enable --now forge-hook.service
|
|
117
|
+
journalctl -u forge-hook.service -f
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Point your forge repo's webhook at `http://127.0.0.1:8099/hook` with the
|
|
121
|
+
matching secret and the events you care about (`issues`, `issue_comment`,
|
|
122
|
+
`push`, `pull_request`, and a `ping` for setup). Keep the listener on loopback
|
|
123
|
+
unless you terminate TLS and authenticate in front of it.
|
|
124
|
+
|
|
125
|
+
### Option B — workspace-scoped Paseo service (one checkout, one repo)
|
|
126
|
+
|
|
127
|
+
If you use Paseo workspaces, you can run the same script as a managed workspace
|
|
128
|
+
service instead of a system unit. Paseo's workspace config supports service
|
|
129
|
+
scripts, so a repo can declare:
|
|
130
|
+
|
|
131
|
+
```jsonc
|
|
132
|
+
// paseo.json (workspace-scoped; paths are relative to the checkout)
|
|
133
|
+
{
|
|
134
|
+
"scripts": {
|
|
135
|
+
"hook": { "type": "service", "command": "node scripts/hook-server.mjs" }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Paseo starts/stops it with the workspace and gives you its logs in-client. The
|
|
141
|
+
trade-off: it only runs while that workspace is up, and it is scoped to that
|
|
142
|
+
checkout. The host-wide systemd unit keeps running regardless of the client.
|
|
143
|
+
Pick one; running both against the same port and secret double-delivers.
|
|
144
|
+
|
|
145
|
+
## 3. Label usage
|
|
146
|
+
|
|
147
|
+
The board is driven by **scoped labels**: a name containing `/` where the last
|
|
148
|
+
`/` separates a *scope* from a *value* (`state/1-wip`, `priority/2-normal`).
|
|
149
|
+
Labels created with `exclusive: true` are **mutually exclusive per scope**:
|
|
150
|
+
applying one evicts any existing label with the same scope, at the forge DB
|
|
151
|
+
level. No explicit remove is required for the happy path.
|
|
152
|
+
|
|
153
|
+
### The scopes
|
|
154
|
+
|
|
155
|
+
| Scope | Values | Meaning |
|
|
156
|
+
| ------------ | ----------------------------------------------------- | ----------------------------------------- |
|
|
157
|
+
| `state/` | `0-triage` `1-wip` `2-review` `3-verify` `4-done` | Execution lifecycle |
|
|
158
|
+
| `priority/` | `0-SOS` `1-high` `2-normal` `3-low` `4-backburner` | Queue ordering / urgency |
|
|
159
|
+
| `attention/` | `0-orchestrator` `1-agent` `2-user` `3-ignore` | Who acts next (action token) |
|
|
160
|
+
| `spec/` | `0-needed` `1-checklist` `2-approved` | Pre-code shaping and approval |
|
|
161
|
+
|
|
162
|
+
A generic, apply-able seed for all four scopes lives in
|
|
163
|
+
[`../examples/labels/label-base.yaml`](../examples/labels/label-base.yaml).
|
|
164
|
+
Apply it as a Forgejo label template, or create the labels in the UI/API; the
|
|
165
|
+
plugin does not install labels for you.
|
|
166
|
+
|
|
167
|
+
### How the plugin uses them
|
|
168
|
+
|
|
169
|
+
- **Live vocabulary, not a hardcoded list.** The plugin derives the scopes
|
|
170
|
+
present on the board (`liveScopesFromIssues` in
|
|
171
|
+
`plugins/forges/shared/issues.ts`) and only falls back to the canonical names
|
|
172
|
+
above. A foreign board degrades gracefully; an unknown scope shows up rather
|
|
173
|
+
than erroring.
|
|
174
|
+
- **Chips are the vocabulary made visible.** Every label renders through
|
|
175
|
+
`LabelChip` as two segments — scope + value — colored from the forge label
|
|
176
|
+
color. The chips and the sort tuple
|
|
177
|
+
(`priorityRank`, then `stateRank`, then recency) are only meaningful if the
|
|
178
|
+
labels are.
|
|
179
|
+
- **Writes add, and also remove a same-scope mate.** The plugin sends the new
|
|
180
|
+
label plus an explicit removal of any existing label in the same scope, so
|
|
181
|
+
the result is correct even on boards whose scope names differ from the
|
|
182
|
+
canonical set (where DB-level exclusivity alone would not evict).
|
|
183
|
+
|
|
184
|
+
### The cold-start gap (read this)
|
|
185
|
+
|
|
186
|
+
**The operator may apply no labels at all.** A ticket can arrive with an empty
|
|
187
|
+
label set, and that is neither an error nor a signal that the ticket is out of
|
|
188
|
+
scope. The plugin then shows the issue with no scope chips, and the board sort
|
|
189
|
+
falls back to defaults (`priority/2-normal`, unranked state).
|
|
190
|
+
|
|
191
|
+
Downstream automation must not assume a populated taxonomy:
|
|
192
|
+
|
|
193
|
+
- Missing labels are **advisory**, not a gate. Never skip a ticket solely
|
|
194
|
+
because it lacks `state/` or `spec/`.
|
|
195
|
+
- The first agent to touch a ticket should **read it, infer the state, and set
|
|
196
|
+
the labels itself** — that is how the taxonomy gets bootstrapped.
|
|
197
|
+
- Deterministic board queries (rank/filter) treat unlabeled issues as normal
|
|
198
|
+
priority with no state rank; surface them, don't hide them.
|
|
199
|
+
|
|
200
|
+
## 4. Agent responsibilities
|
|
201
|
+
|
|
202
|
+
The workflow assumes two roles. One agent can hold both, but keeping them
|
|
203
|
+
separate is what stops a fleet from colliding.
|
|
204
|
+
|
|
205
|
+
### Orchestrator
|
|
206
|
+
|
|
207
|
+
- **Triage.** Watch the `attention/` signal and incoming comments. Turn vague
|
|
208
|
+
operator input ("build's failing, fix it") into a concrete ticket with a
|
|
209
|
+
`- [ ]` checklist and boundary constraints.
|
|
210
|
+
- **Dispatch.** One ticket, one worker, isolated by package directory. Never
|
|
211
|
+
let two workers edit the same checkout at once; queue instead of colliding.
|
|
212
|
+
Instruct the worker to claim with a stamped comment and `state/1-wip`.
|
|
213
|
+
- **Pre-flight.** Before presenting anything to the operator: work committed and
|
|
214
|
+
pushed, tests/typechecks green, and the running daemon actually executing the
|
|
215
|
+
new commit (helper build fresh, plugin version stamp matching HEAD). Never
|
|
216
|
+
present unverified work.
|
|
217
|
+
- **Verify (non-binding).** `state/3-verify` is "needs a human look", not "block
|
|
218
|
+
forever". If the operator doesn't test, resolve with narration (requeue,
|
|
219
|
+
close as superseded, or verify by proxy) instead of deadlocking.
|
|
220
|
+
|
|
221
|
+
### Coding agent
|
|
222
|
+
|
|
223
|
+
- **Discover and claim.** Read the *entire* ticket and *entire* comment thread
|
|
224
|
+
first — scope is often amended in comments. Verify no peer already claimed it,
|
|
225
|
+
then post a claim comment and set `state/1-wip`.
|
|
226
|
+
- **Page every list read.** Issue lists, search results, label lists, and
|
|
227
|
+
comment lists are collections: each call returns one page, and the default
|
|
228
|
+
page size is **server-defined and can change**. Pass `limit`/`page` — or follow
|
|
229
|
+
`Link`/`X-Total-Count` — until a short page comes back before concluding "no
|
|
230
|
+
results" or claiming you have read the whole thread. One unpaged call is never
|
|
231
|
+
the full set; a plugin surface that lists results should page internally
|
|
232
|
+
rather than render a truncated set (#189). See the worked example in the
|
|
233
|
+
[examples README](../examples/README.md#paged-reads-issue--search--label--comment-lists)
|
|
234
|
+
(unpaged `fgj label list` returned 30 of 59 labels, #197).
|
|
235
|
+
- **Implement.** Work quietly in your own checkout. Stage explicit paths only;
|
|
236
|
+
never `git add -A` in a shared tree.
|
|
237
|
+
- **Hand off.** Run tests/typecheck, commit, push to the forge, then post a
|
|
238
|
+
structured report (what changed, commit SHA, test result) and advance state —
|
|
239
|
+
`state/2-review` for an internal review, or `state/3-verify` for operator
|
|
240
|
+
testing.
|
|
241
|
+
- **Stamp comments.** Because many agents may share one forge account, every
|
|
242
|
+
comment should carry a self-identification footer (an "agent envelope") so
|
|
243
|
+
attribution survives. That tooling is external to the plugin — see §6.
|
|
244
|
+
|
|
245
|
+
Missing labels in no way block either role; see the cold-start gap above.
|
|
246
|
+
|
|
247
|
+
## 5. Skills
|
|
248
|
+
|
|
249
|
+
`plugins/forges/examples/skills/` ships the workflow above as **two skill
|
|
250
|
+
sets**, adapted for publication. Each set has a coding-agent and an orchestrator
|
|
251
|
+
counterpart, and they differ only in how they touch the board:
|
|
252
|
+
|
|
253
|
+
| Zero-dependency (plugin + embedded `/api/v1`) | CLI (`fgjx` over `fgj`) |
|
|
254
|
+
| --------------------------------------------- | ---------------------------------------- |
|
|
255
|
+
| [`examples/skills/coding-agent/SKILL.md`](../examples/skills/coding-agent/SKILL.md) | [`examples/skills/coding-agent-fgjx/SKILL.md`](../examples/skills/coding-agent-fgjx/SKILL.md) |
|
|
256
|
+
| [`examples/skills/orchestrator/SKILL.md`](../examples/skills/orchestrator/SKILL.md) | [`examples/skills/orchestrator-fgjx/SKILL.md`](../examples/skills/orchestrator-fgjx/SKILL.md) |
|
|
257
|
+
|
|
258
|
+
- The **zero-dependency** set drives the board through the plugin's own
|
|
259
|
+
surfaces and its embedded Gitea-family `/api/v1` client. Nothing needs to be
|
|
260
|
+
installed; no forge CLI is present.
|
|
261
|
+
- The **CLI** set uses the vendored example wrapper
|
|
262
|
+
[`examples/tools/fgjx`](../examples/tools/fgjx), which is a thin passthrough
|
|
263
|
+
shim over **your** `fgj`: `fgj` owns the host URL + token and performs the raw
|
|
264
|
+
`/api/v1` calls, while `fgjx` adds label resolution, table/view niceties, and
|
|
265
|
+
optional envelope stamping. See
|
|
266
|
+
[`examples/tools/README.md`](../examples/tools/README.md). The envelope tool
|
|
267
|
+
is optional — without one, `fgjx` emits a generic machine-authored footer.
|
|
268
|
+
|
|
269
|
+
They are **examples, not drop-ins**. Both carry a warning banner and use
|
|
270
|
+
placeholders (`forge.example.com`, `your-org/your-repo`). Before adopting one:
|
|
271
|
+
|
|
272
|
+
1. Copy it into your skills directory (for Paseo: `.agents/skills/<name>/`).
|
|
273
|
+
2. Rewrite the host, repo, and command/tool names to match your setup. If you
|
|
274
|
+
choose the CLI set, supply `fgj` and (optionally) an envelope tool; if you
|
|
275
|
+
can't, use the zero-dependency set and the plugin surfaces / direct forge API
|
|
276
|
+
calls.
|
|
277
|
+
3. Align the label and slash-command vocabulary with your seed YAML. If you
|
|
278
|
+
only seed the four scopes in §3, drop or define any extra labels a Skill
|
|
279
|
+
mentions (the Orchestrator examples refer to a `flag/stop-work` circuit
|
|
280
|
+
breaker).
|
|
281
|
+
4. Cross-link the adapted Skills back to your board conventions so the next
|
|
282
|
+
agent inherits them.
|
|
283
|
+
|
|
284
|
+
## 6. Includes vs does-not-include
|
|
285
|
+
|
|
286
|
+
What the `forges` plugin provides versus what you must supply:
|
|
287
|
+
|
|
288
|
+
| Provided by the plugin | Supplied by the adopter |
|
|
289
|
+
| ------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
290
|
+
| Issue list/detail, comments, scoped label chips | Hook service (systemd unit **or** workspace-scoped service)|
|
|
291
|
+
| Embedded `/api/v1` fetch client; daemon-side per-host tokens | Label base (seed YAML applied to the repo) |
|
|
292
|
+
| Live label vocabulary derived from the board | Skills (adapt the examples to your tooling) |
|
|
293
|
+
| Two example skill sets (plugin/`/api/v1` and `fgjx`) + README | `fgj`, only if you adopt the CLI skill set |
|
|
294
|
+
| Bundled, sanitized `examples/tools/fgjx` wrapper | Envelope tool for comment stamps (optional, CLI set only) |
|
|
295
|
+
| `forge.set-label` / `forge.add-comment` write RPCs | The forge itself (Forgejo/Gitea-family host + repo) |
|
|
296
|
+
| Webhook timeline card parser (`shared/webhook.ts`) | Webhook secret/config on the forge repo |
|
|
297
|
+
|
|
298
|
+
Explicit non-goals (do not expect the plugin to do these):
|
|
299
|
+
|
|
300
|
+
- **No webhook receiver.** The plugin is not an HTTP endpoint and does not
|
|
301
|
+
register webhooks for you.
|
|
302
|
+
- **No label install.** The optional label-set install is operator-gated and
|
|
303
|
+
excluded from the release surface; seed labels yourself.
|
|
304
|
+
- **No envelope tooling shipped.** Comment attribution/self-stamping is
|
|
305
|
+
external. The bundled `examples/tools/fgjx` can call an adopter-supplied
|
|
306
|
+
envelope tool for `--envelope`, but the plugin ships none and the core
|
|
307
|
+
workflow never needs one.
|
|
308
|
+
- **No orchestration.** Dispatch, pre-flight, and verify are agent behaviour
|
|
309
|
+
encoded in the example Skills, not plugin features.
|
|
310
|
+
- **No CLI dependency in the plugin runtime.** The plugin works on a machine
|
|
311
|
+
that has never had a forge CLI installed. The `fgjx` wrapper is a bundled
|
|
312
|
+
*example* under `examples/tools/` for adopters who already run `fgj`; it is
|
|
313
|
+
never loaded by the plugin.
|
|
314
|
+
|
|
315
|
+
### 6.1 Minimum token scopes (write-enabled forges)
|
|
316
|
+
|
|
317
|
+
A token the host accepts is not automatically write-capable. Edit surfaces
|
|
318
|
+
(labels, comments, label-set install) require the repo to report write
|
|
319
|
+
permission; the plugin reads that from the repo response
|
|
320
|
+
(`permissions.push`/`admin` on Forgejo/GitHub, `access_level >= 30` on GitLab)
|
|
321
|
+
and otherwise falls back to token validity. A valid but under-scoped token is
|
|
322
|
+
reported as **"token lacks write scope"**, not rejected.
|
|
323
|
+
|
|
324
|
+
Forgejo/Gitea PAT: `read:user` (identity probe), `read:repository` (repo
|
|
325
|
+
metadata), and `write:issue` (issues, comments, labels). Add
|
|
326
|
+
`write:repository` if label management still 403s. GitHub needs `read:user`
|
|
327
|
+
plus `repo`; GitLab needs `read_user`, `read_api`, and `api`.
|
|
328
|
+
|
|
329
|
+
## 7. Adopter checklist
|
|
330
|
+
|
|
331
|
+
- [ ] Create the board labels from `examples/labels/label-base.yaml`.
|
|
332
|
+
- [ ] Stand up the hook service (systemd unit or workspace service) and point a
|
|
333
|
+
forge webhook at it with a shared secret.
|
|
334
|
+
- [ ] Confirm deliveries arrive (send a `ping` event) and an orchestrator is
|
|
335
|
+
registered for the repo.
|
|
336
|
+
- [ ] Copy and adapt `examples/skills/` into your skills directory.
|
|
337
|
+
- [ ] Add an envelope/attribution step if multiple agents share one account.
|
|
338
|
+
- [ ] Install the plugin, set the per-host token in Settings, and verify the
|
|
339
|
+
issue pill renders your board.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Examples — adapt, do not drop in
|
|
2
|
+
|
|
3
|
+
This directory ships the maintainers' own agent workflow as **examples** so a
|
|
4
|
+
team adopting the `forges` plugin has a working starting point. They are not a
|
|
5
|
+
supported configuration and they are not drop-in: every host, repo, CLI tool,
|
|
6
|
+
label, and convention below reflects one team's setup.
|
|
7
|
+
|
|
8
|
+
**You must adjust them to fit your own workflow.** The end-to-end picture — how
|
|
9
|
+
webhooks reach the daemon, how labels drive the board, and what each agent role
|
|
10
|
+
owns — is in [`../docs/workflow.md`](../docs/workflow.md).
|
|
11
|
+
|
|
12
|
+
## What is here
|
|
13
|
+
|
|
14
|
+
- `hook-service/` — a sanitized systemd unit, a generic webhook bridge script
|
|
15
|
+
(`hook-server.mjs`), and an environment-file example. One listener with
|
|
16
|
+
`POST /hook` and `POST /orchestrate`; placeholders only. See its `README.md`.
|
|
17
|
+
- `labels/label-base.yaml` — a generic, apply-able seed for the scoped labels
|
|
18
|
+
the workflow uses (`state/`, `priority/`, `attention/`, `spec/`, plus an
|
|
19
|
+
optional `flag/stop-work`). Apply it as a Forgejo label template or create
|
|
20
|
+
the labels via the API/UI.
|
|
21
|
+
- `tools/fgjx` — a sanitized, vendored copy of one team's `fgjx` wrapper. It is
|
|
22
|
+
a passthrough shim over the `fgj` CLI (label resolution, table/view niceties,
|
|
23
|
+
optional envelope stamping) and **requires the adopter's own `fgj`**, pointed
|
|
24
|
+
at their forge; it fails loudly without it. `tools/README.md` explains the
|
|
25
|
+
fgj-vs-fgjx split and the optional envelope tool.
|
|
26
|
+
- `skills/coding-agent/SKILL.md` — the **zero-dependency** variant: workflow,
|
|
27
|
+
issue conventions, and reporting standards, driving the board through the
|
|
28
|
+
plugin's own surfaces and embedded Gitea-family `/api/v1` client. No forge CLI.
|
|
29
|
+
- `skills/coding-agent-fgjx/SKILL.md` — the **CLI** variant of the same
|
|
30
|
+
workflow, using the vendored `fgjx` (hence an adopter-supplied `fgj`) and an
|
|
31
|
+
optional envelope self-stamp so comment attribution survives across many
|
|
32
|
+
agents sharing one account.
|
|
33
|
+
- `skills/orchestrator/SKILL.md` and `skills/orchestrator-fgjx/SKILL.md` —
|
|
34
|
+
dispatch, pre-flight, and human-in-the-loop signoff rules for the agent
|
|
35
|
+
coordinating the others, split the same way (plugin + `/api/v1` vs `fgjx`).
|
|
36
|
+
|
|
37
|
+
Every file carries an `EXAMPLE` warning at the top. To use the Skills, copy a
|
|
38
|
+
`SKILL.md` into your own skills directory (for Paseo: `.agents/skills/<name>/`)
|
|
39
|
+
and rewrite the placeholders (`forge.example.com`, `your-org/your-repo`,
|
|
40
|
+
`fgj`/`fgjx`, `envelope-tool`) to match your tooling. If you have no forge CLI,
|
|
41
|
+
start from the `coding-agent`/`orchestrator` variants — they need none.
|
|
42
|
+
|
|
43
|
+
## The label taxonomy the skills assume
|
|
44
|
+
|
|
45
|
+
The scoped label set below is what the skills and the plugin's label chips use,
|
|
46
|
+
and what `labels/label-base.yaml` seeds. The plugin no longer ships a label-set
|
|
47
|
+
install in the UI, so create these labels on your repo yourself (with the seed
|
|
48
|
+
file, by hand, or via the Forgejo API):
|
|
49
|
+
|
|
50
|
+
| Scope | Labels |
|
|
51
|
+
| ----------- | ------------------------------------------------------------- |
|
|
52
|
+
| `state/` | `0-triage`, `1-wip`, `2-review`, `3-verify`, `4-done` |
|
|
53
|
+
| `priority/` | `0-SOS`, `1-high`, `2-normal`, `3-low`, `4-backburner` |
|
|
54
|
+
| `attention/`| `0-orchestrator`, `1-agent`, `2-user`, `3-ignore` |
|
|
55
|
+
| `spec/` | `0-needed`, `1-checklist`, `2-approved` |
|
|
56
|
+
|
|
57
|
+
Gitea-family scoped labels are exclusive per scope, so applying one evicts the
|
|
58
|
+
previous mate in the same scope. The plugin's label chips rely on this
|
|
59
|
+
vocabulary to advance `state/`, `priority/`, `attention/`, and `spec/`.
|
|
60
|
+
|
|
61
|
+
The operator may apply **no labels at all**; that is the normal starting state,
|
|
62
|
+
not an error. Automation must treat missing labels as advisory and bootstrap the
|
|
63
|
+
taxonomy on first touch (see `docs/workflow.md` §3).
|
|
64
|
+
|
|
65
|
+
## Paged reads (issue / search / label / comment lists)
|
|
66
|
+
|
|
67
|
+
Every list either skill set reads is **paged**. A Gitea-family collection
|
|
68
|
+
endpoint returns one page per call, and the default page size is
|
|
69
|
+
**server-defined and can change** — on both the plugin's embedded `/api/v1`
|
|
70
|
+
client and the `fgj`/`fgjx` CLI. This applies to the **issue list, search
|
|
71
|
+
results, label list, and comment list**.
|
|
72
|
+
|
|
73
|
+
- **Always page**, then aggregate: pass `limit`/`page` and keep going while a
|
|
74
|
+
full page comes back, or follow the `Link` header / `X-Total-Count` when the
|
|
75
|
+
server sends them.
|
|
76
|
+
- **Never treat page 1 as complete**, and never conclude "no results" — or
|
|
77
|
+
"labels not found" — from one unpaged call.
|
|
78
|
+
- If a forges UI surface lists results, it should **page internally** rather
|
|
79
|
+
than render a truncated set (#189).
|
|
80
|
+
|
|
81
|
+
Worked example: an unpaged `fgj label list` returned **30 of 59** labels, which
|
|
82
|
+
surfaced as false "labels not found" errors (#197). Both skill sets carry the
|
|
83
|
+
same caveat at their list paths.
|
|
84
|
+
|
|
85
|
+
## Two skill sets, and what they need
|
|
86
|
+
|
|
87
|
+
The plugin itself assumes no CLI. The Skills come in two variants so you can
|
|
88
|
+
pick the one that fits your host:
|
|
89
|
+
|
|
90
|
+
- **Plugin + embedded `/api/v1`** — `skills/coding-agent` and
|
|
91
|
+
`skills/orchestrator`. Zero external dependencies: list and read issues
|
|
92
|
+
through the issues pill/modal, change scoped labels through the Labels tab /
|
|
93
|
+
label chips, post through the quick-comment composer, all backed by the
|
|
94
|
+
daemon-side `/api/v1` client. Scripting can call the forge API directly with
|
|
95
|
+
your own token.
|
|
96
|
+
- **`fgjx` CLI** — `skills/coding-agent-fgjx` and `skills/orchestrator-fgjx`.
|
|
97
|
+
The richer path, using the bundled `tools/fgjx`. That wrapper needs **your**
|
|
98
|
+
`fgj` (the authenticated transport: it owns the host URL + token and does the
|
|
99
|
+
raw `/api/v1` calls) and, **optionally**, an envelope tool for comment
|
|
100
|
+
stamps. Neither ships with the plugin.
|
|
101
|
+
|
|
102
|
+
Both variants expose the same board: the plugin's equivalents for a CLI step
|
|
103
|
+
are the issues pill/modal (list/detail), the Labels tab (scoped label changes),
|
|
104
|
+
and the quick-comment composer (steering). Map the CLI steps onto those (or the
|
|
105
|
+
plugin's `forge.board-overview` / `forge.issue-detail` / `forge.set-label` /
|
|
106
|
+
`forge.add-comment` RPCs) when you have no CLI. See
|
|
107
|
+
[`../docs/workflow.md`](../docs/workflow.md) §6 for the full includes-vs-excludes
|
|
108
|
+
table.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Hook service (example)
|
|
2
|
+
|
|
3
|
+
The `forges` plugin reads and writes the forge API, but it does not receive
|
|
4
|
+
webhooks. Something has to turn "a label changed on `your-org/your-repo`" into a
|
|
5
|
+
message the Paseo daemon delivers to an agent. This directory is a **sanitized
|
|
6
|
+
skeleton** for that bridge: systemd unit, environment file, and a generic
|
|
7
|
+
script. See [`../../docs/workflow.md`](../../docs/workflow.md) §2 for the full
|
|
8
|
+
contract.
|
|
9
|
+
|
|
10
|
+
> **Not a supported configuration.** Replace every placeholder and review the
|
|
11
|
+
> TODOs in `hook-server.mjs` (secret rotation, durable queueing, coalescing,
|
|
12
|
+
> payload validation) before relying on it.
|
|
13
|
+
|
|
14
|
+
## Files
|
|
15
|
+
|
|
16
|
+
| File | Purpose |
|
|
17
|
+
| ------------------- | -------------------------------------------------------------- |
|
|
18
|
+
| `hook-server.mjs` | Generic listener: `POST /hook` + `POST /orchestrate` |
|
|
19
|
+
| `forge-hook.service`| systemd unit for a host-wide deploy (many repos) |
|
|
20
|
+
| `hook.env.example` | Environment placeholders consumed by the script |
|
|
21
|
+
|
|
22
|
+
## Option A — systemd (host-wide)
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
sudo install -d -o forge-hook -g forge-hook /opt/forge-hook /etc/forge-hook /var/lib/forge-hook
|
|
26
|
+
sudo cp hook-server.mjs /opt/forge-hook/
|
|
27
|
+
sudo cp hook.env.example /etc/forge-hook/hook.env # then edit
|
|
28
|
+
sudo chmod 600 /etc/forge-hook/hook.env
|
|
29
|
+
sudo cp forge-hook.service /etc/systemd/system/
|
|
30
|
+
sudo systemctl daemon-reload
|
|
31
|
+
sudo systemctl enable --now forge-hook.service
|
|
32
|
+
systemctl status forge-hook.service
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then point the repo's webhook at `http://127.0.0.1:8099/hook`, set the same
|
|
36
|
+
secret, and send a `ping` event to verify delivery.
|
|
37
|
+
|
|
38
|
+
## Option B — Paseo workspace service
|
|
39
|
+
|
|
40
|
+
Run the same script as a managed workspace service instead of a host unit:
|
|
41
|
+
|
|
42
|
+
```jsonc
|
|
43
|
+
// paseo.json in the workspace/checkout
|
|
44
|
+
{
|
|
45
|
+
"scripts": {
|
|
46
|
+
"hook": { "type": "service", "command": "node plugins/forges/examples/hook-service/hook-server.mjs" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Paseo starts/stops it with the workspace and surfaces its logs. It only runs
|
|
52
|
+
while that workspace is up; a host-wide unit does not. Run one, not both.
|
|
53
|
+
|
|
54
|
+
## Registering an orchestrator
|
|
55
|
+
|
|
56
|
+
Until a repo has an orchestrator, deliveries for it are held. Register one from
|
|
57
|
+
the agent's own checkout over loopback (no secret needed):
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
curl -s -X POST http://127.0.0.1:8099/orchestrate \
|
|
61
|
+
-H 'content-type: application/json' \
|
|
62
|
+
-d '{"agentId":"<your-agent-id>","repo":"forge.example.com/your-org/your-repo"}'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Off-box callers must send the shared secret. The `repo` field is a
|
|
66
|
+
forge-qualified `host/owner/repo` key.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Forge webhook receiver (forges plugin bridge)
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
# Replace with the unprivileged account that owns the secret and state dir.
|
|
9
|
+
User=forge-hook
|
|
10
|
+
Group=forge-hook
|
|
11
|
+
WorkingDirectory=/opt/forge-hook
|
|
12
|
+
# Copy hook.env.example to /etc/forge-hook/hook.env and edit the placeholders.
|
|
13
|
+
EnvironmentFile=/etc/forge-hook/hook.env
|
|
14
|
+
ExecStart=/usr/bin/node /opt/forge-hook/hook-server.mjs
|
|
15
|
+
Restart=on-failure
|
|
16
|
+
RestartSec=5
|
|
17
|
+
|
|
18
|
+
# Hardening. Keep this after you have confirmed the paths exist.
|
|
19
|
+
NoNewPrivileges=true
|
|
20
|
+
PrivateTmp=true
|
|
21
|
+
ProtectSystem=strict
|
|
22
|
+
ProtectHome=true
|
|
23
|
+
# The state dir must be writable; match HOOK_STATE_DIR in the environment file.
|
|
24
|
+
ReadWritePaths=/var/lib/forge-hook
|
|
25
|
+
StateDirectory=forge-hook
|
|
26
|
+
|
|
27
|
+
[Install]
|
|
28
|
+
WantedBy=multi-user.target
|