@sjawhar/opencode-legion-envoy 1.20.2 → 1.21.1
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/dist/src/server.js
CHANGED
|
@@ -13649,7 +13649,7 @@ var MessageEventPayloadSchema = object({
|
|
|
13649
13649
|
});
|
|
13650
13650
|
var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
|
|
13651
13651
|
id: string2(),
|
|
13652
|
-
issue_key: string2(),
|
|
13652
|
+
issue_key: string2().nullable(),
|
|
13653
13653
|
author: object({ kind: string2(), id: string2() }),
|
|
13654
13654
|
body: string2(),
|
|
13655
13655
|
target: string2(),
|
|
@@ -13662,6 +13662,7 @@ var MessageDeliveryEventPayloadSchema = object({
|
|
|
13662
13662
|
attempt: number2().int().positive().optional(),
|
|
13663
13663
|
delivery: _enum2(["btw", "aside", "steer"]).optional(),
|
|
13664
13664
|
session_id: string2().optional(),
|
|
13665
|
+
target: string2().optional(),
|
|
13665
13666
|
title: string2().optional(),
|
|
13666
13667
|
state: _enum2(["sent", "failed"]).optional(),
|
|
13667
13668
|
error: string2().optional()
|
|
@@ -15634,17 +15635,18 @@ async function executeDispatchTool(input) {
|
|
|
15634
15635
|
}
|
|
15635
15636
|
case "dispatch_message": {
|
|
15636
15637
|
const inReplyTo = messageInReplyTo(args);
|
|
15637
|
-
const
|
|
15638
|
+
const issueKey = issue();
|
|
15639
|
+
const message = await client.message(issueKey, {
|
|
15638
15640
|
body: stringArg(args, "body"),
|
|
15639
15641
|
...inReplyTo === undefined ? {} : { in_reply_to: inReplyTo },
|
|
15640
15642
|
actor
|
|
15641
15643
|
});
|
|
15642
|
-
const messageRef = `dispatch://${
|
|
15644
|
+
const messageRef = `dispatch://${issueKey}/message/${message.id}`;
|
|
15643
15645
|
return {
|
|
15644
15646
|
text: `Posted message ${message.id} (${messageRef})`,
|
|
15645
15647
|
details: {
|
|
15646
|
-
issue:
|
|
15647
|
-
topic: dispatchIssueSubject(
|
|
15648
|
+
issue: issueKey,
|
|
15649
|
+
topic: dispatchIssueSubject(issueKey, ">"),
|
|
15648
15650
|
message: message.id
|
|
15649
15651
|
}
|
|
15650
15652
|
};
|
package/package.json
CHANGED
package/skills/envoy/SKILL.md
CHANGED
|
@@ -148,11 +148,16 @@ not construct a token from a partial issue reference.
|
|
|
148
148
|
|
|
149
149
|
## Legion exception lane
|
|
150
150
|
|
|
151
|
-
`no_holder` and `
|
|
152
|
-
add a second subscriber or manually retry.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
`no_holder`, `delivery_failed`, and `receipt_timeout` for a Legion role are daemon liveness
|
|
152
|
+
signals, not reasons to add a second subscriber or manually retry. `no_holder`: no live session
|
|
153
|
+
holds the role. `delivery_failed`: the message was not forwarded at all — the holder lookup
|
|
154
|
+
failed, the holder was stale, or the publish errored. `receipt_timeout`: the listener forwarded
|
|
155
|
+
the message to a registered, live holder and no receipt arrived inside the window; its payload
|
|
156
|
+
keeps `original_topic`, `event_id`, `payload`, and the original `dedupe_key`, so the daemon can
|
|
157
|
+
re-send the same message and the receiver's dedupe drops a copy that did arrive. Treat any of
|
|
158
|
+
these wakes as the daemon's responsibility to revive or recreate the backing worker and
|
|
159
|
+
re-deliver; otherwise it resurrects the root process with derived catch-up and the workspace
|
|
160
|
+
handoffs. Raw delivery failures stay out of architect context.
|
|
156
161
|
|
|
157
162
|
## Slack
|
|
158
163
|
|
|
@@ -285,7 +285,11 @@ entire end-game sequence has completed.
|
|
|
285
285
|
## Wake routing
|
|
286
286
|
|
|
287
287
|
Handle one delivered wake by verifying the relevant live artifact and then performing the
|
|
288
|
-
corresponding lifecycle procedure.
|
|
288
|
+
corresponding lifecycle procedure. Architect-addressed wakes always reach the architect that owns
|
|
289
|
+
the payload issue: a claimed child sub-architect, otherwise the nearest claimed ancestor, then the
|
|
290
|
+
root. A wake about an architect role's own launch reaches the architect above it. `phase-complete`,
|
|
291
|
+
worker lifecycle, child lifecycle, and design-gate wakes are architect-only and never go to an
|
|
292
|
+
active phase worker.
|
|
289
293
|
|
|
290
294
|
| Wake | Procedure |
|
|
291
295
|
| --- | --- |
|
|
@@ -305,8 +309,8 @@ corresponding lifecycle procedure.
|
|
|
305
309
|
| `pr-merged` | Payload `{type:"pr-merged", pr, mergeCommitSha}`. The merge queue landed the PR. `spawn_worker` the **implementer** with the production-check task naming that merge commit (it resumes the same agent; a retired role has no live holder, so never `envoy_publish` for this). Its `phase-complete` is what brings you to step 7: verify the record on the pull request and this issue first, then sign off naming it and set the issue `done`. A merge is not the close. |
|
|
306
310
|
| `pr-closed-unmerged` | Decide from current scope whether to reopen the work, send a fresh implementer, or cancel it with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
|
|
307
311
|
| `issue-comment` | Interpret the comment in the issue's design context. Answer it, adjust the plan, or relay it via `envoy_publish` to the responsible worker's role token; scope and product decisions remain with you. |
|
|
308
|
-
| `catchup-overseer` | Verify its
|
|
309
|
-
| `worker-died` | Payload `{type:"worker-died", issue, role}`.
|
|
312
|
+
| `catchup-overseer` | Verify its child counts and PR verdicts against current artifacts, then resume the applicable lifecycle step. This is a current-state snapshot, not a raw-event replay. A root architect uses `gates[LEGION_TREE].open`: `true` means the root spec is approved and section 2 may continue; `false`, or no `open` key, means section 1 still applies. A resumed sub-architect receives `overseerCatchup(state, LEGION_ISSUE)` for its own subtree: its `gates` intentionally omits the root gate because a child spec is never gated. Do not request or register a gate; resume at section 2. Handle each `phaseCompletions` entry exactly as a `phase-complete` wake, then compare `childCounts[LEGION_ISSUE].open` with `legion state` and Dispatch before deciding the next action. |
|
|
313
|
+
| `worker-died` | Payload `{type:"worker-died", issue, role}`. Two causes, one verdict: the daemon retried this role's boot through `MAX_LAUNCH_FAILURES` attempts and could not confirm it, or the worker booted and acknowledged every prompt without ever starting a turn through `MAX_PROMPT_RETIRES` retire-and-relaunch cycles (LEGION-93) — never a raw-event replay or a silent revive. Your next `spawn_worker` for the role is the retry (one cold launch, three prompts, and `worker-died` again if the agent is still broken). Reassess the work and `spawn_worker` again for the role (it resumes the same agent via `--resume` if a session file survived) or reassign it if the failure looks environmental, not agent-specific. |
|
|
310
314
|
| `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
|
|
311
315
|
|
|
312
316
|
## Escalation judgment
|
|
@@ -45,7 +45,7 @@ before step 3. The design gate is not a substitute for review and retro.
|
|
|
45
45
|
|
|
46
46
|
1. Re-read the issue, its acceptance criteria, the PR, test evidence, and review evidence.
|
|
47
47
|
Confirm the PR carries both proofs: the implementer's own `E2E (implementer)` line and the tester's `E2E (tester)` line,
|
|
48
|
-
each naming a production-like surface (
|
|
48
|
+
each naming a production-like surface (the daemon's test harness (`packages/daemon/src/daemon/__tests__/`) and real-process fixtures; a live check at the operator's next daemon restart, recorded on the PR; a sandbox repository, a devN
|
|
49
49
|
stack, staging, or a local stack with real migrations), a command or run id, an observation, a head
|
|
50
50
|
SHA, and a negative control. If either is missing, or links only a unit suite, the retro's first
|
|
51
51
|
durable learning is that gap and the issue goes back — to the implementer for its own proof, to the
|
|
@@ -22,13 +22,14 @@ completes the boot handshake for you at session start — it registers with the
|
|
|
22
22
|
your role, and signals readiness. You never call `envoy_role_set` yourself.
|
|
23
23
|
|
|
24
24
|
Your role token is not the issue key spelled out literally. The daemon encodes it as
|
|
25
|
-
`legion-<project>-<KEY>-<role>`. For example, project `acme`, issue `LEGION-41`, role
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
been given before recomputing
|
|
25
|
+
`legion-<project>-<KEY>-<role>`. For example, project `acme`, issue `LEGION-41`, role `architect`
|
|
26
|
+
encodes to `legion-acme-LEGION-41-architect`. Never hand-format one for another role: your own role
|
|
27
|
+
topic and the topic of the architect that owns your issue are stated at the end of your system
|
|
28
|
+
prompt (a "Legion addressing" line the daemon appends: on a child issue that is the child's
|
|
29
|
+
sub-architect when one is claimed, else the root's), a sibling role's topic is yours with the
|
|
30
|
+
trailing `-<role>` replaced, and the `roleToken` helper in `@legion/contracts` computes any other
|
|
31
|
+
one exactly the way the daemon does — prefer a topic you've already been given before recomputing
|
|
32
|
+
one.
|
|
32
33
|
|
|
33
34
|
If the handshake fails (a rejected boot token, or a bootstrap failure after your role
|
|
34
35
|
registered), the extension logs it and exits the process outright — it does not retry, and
|
|
@@ -251,7 +252,7 @@ PR opens, and every later phase keeps it current rather than replacing it:
|
|
|
251
252
|
```
|
|
252
253
|
## Verification
|
|
253
254
|
|
|
254
|
-
**CI:** `Tests` run <run-id> — jobs lint,
|
|
255
|
+
**CI:** `Tests` run <run-id> — jobs lint, typecheck, test all success at <head-sha>; `PR Title` run <run-id> — job pr-title success at <head-sha>.
|
|
255
256
|
|
|
256
257
|
**Threads:** <n> resolved, 0 unresolved. Each disposed individually, never in bulk:
|
|
257
258
|
- Thread <id>: fixed in <commit-sha> — <one line>.
|
|
@@ -276,6 +277,7 @@ Verified the implementer's proof by <re-running its command | driving the same s
|
|
|
276
277
|
**Fast-follow:** <one named cleanup item and where it will land>, or "none".
|
|
277
278
|
|
|
278
279
|
**Chain:** stacked on <base bookmark> frozen at <sha> / not stacked.
|
|
280
|
+
**Retarget:** Retargeting a pull request to a new base does not re-run Tests; after a retarget, rebase onto the new base and push — the new head runs Tests against the new merge result — and cite that run in the PR body.
|
|
279
281
|
```
|
|
280
282
|
|
|
281
283
|
- **Threads are dispositioned individually, never resolved in bulk.** Every open review
|
|
@@ -301,11 +303,11 @@ Verified the implementer's proof by <re-running its command | driving the same s
|
|
|
301
303
|
changes behavior, hides an error, or breaks a gate is fixed here — never deferred.
|
|
302
304
|
Findings about naming, duplication, or wording are batched into the single `Fast-follow`
|
|
303
305
|
line instead of iterating per push.
|
|
304
|
-
- **Rebase only on a real conflict.** Sami, 2026-09-11, verbatim:
|
|
306
|
+
- **Rebase only on a real conflict, except after a base retarget.** Sami, 2026-09-11, verbatim:
|
|
305
307
|
"Please don't do unnecessary rebases (i.e. unless there are merge conflicts). The CI queue is too long and slow."
|
|
306
|
-
The implementer rebases the issue branch only when GitHub reports it `CONFLICTING
|
|
307
|
-
|
|
308
|
-
failed CI job is re-run on its own with `legion gh -- run rerun <run-id> --failed`, never by
|
|
308
|
+
The implementer rebases the issue branch only when GitHub reports it `CONFLICTING`, the controller asks
|
|
309
|
+
because of a conflict, or after the pull request is retargeted to a new base. Otherwise, never rebase to
|
|
310
|
+
pick up `main` or refresh CI. A single failed CI job is re-run on its own with `legion gh -- run rerun <run-id> --failed`, never by
|
|
309
311
|
pushing a new commit. A conflict-forced rebase that leaves the branch's diff unchanged is a
|
|
310
312
|
confirmation, not a new round (see *The unchanged-diff check* below). Before rebasing, record
|
|
311
313
|
the fingerprint at the current tip; after pushing the rebased branch, record it at the new
|
|
@@ -317,9 +319,10 @@ Verified the implementer's proof by <re-running its command | driving the same s
|
|
|
317
319
|
field names naming, duplication, or wording cleanup only; anything that changes behaviour,
|
|
318
320
|
hides an error, or breaks a gate lands in this PR.
|
|
319
321
|
- **The implementer proves the change before its phase completes, and writes the `E2E (implementer)` line when the pull request opens.**
|
|
320
|
-
The proof is the changed behaviour exercised on the surface a user reaches it through —
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
The proof is the changed behaviour exercised on the surface a user reaches it through — the daemon's
|
|
323
|
+
test harness (`packages/daemon/src/daemon/__tests__/`) and real-process fixtures; a live check at the
|
|
324
|
+
operator's next daemon restart, recorded on the PR; a sandbox repository, a real browser, a devN stack,
|
|
325
|
+
or a local stack with real migrations — with the exact command or run id, what was observed, the head SHA,
|
|
323
326
|
one negative control. The same proof goes into `.legion/implement.json` as its required `proof`
|
|
324
327
|
array (`legion handoff write --phase implement` refuses a payload without one and names the
|
|
325
328
|
field), and into the PR body, because the reviewer and the merger verify facts on GitHub and
|