@sjawhar/opencode-legion-envoy 1.21.0 → 1.22.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.
@@ -16031,10 +16031,16 @@ class EnvoyApiError extends Error {
16031
16031
  this.expected = parsed?.expected;
16032
16032
  }
16033
16033
  }
16034
+ function resolveEnvoyToken(env) {
16035
+ const { ENVOY_TOKEN_FILE: file, ENVOY_TOKEN: plain } = env;
16036
+ if (file !== undefined && file !== "")
16037
+ return readSecretFile("ENVOY_TOKEN_FILE", file);
16038
+ return plain || undefined;
16039
+ }
16034
16040
  function createEnvoyClient(config) {
16035
16041
  const baseUrl = normalizeEnvoyUrl(config.baseUrl);
16036
16042
  const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
16037
- const { ENVOY_TOKEN: apiToken } = process.env;
16043
+ const apiToken = resolveEnvoyToken(process.env);
16038
16044
  const request = async (path, init) => {
16039
16045
  const url = `${baseUrl}${path}`;
16040
16046
  for (let attempt = 0;attempt < 2; attempt += 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -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 `delivery_failed` for a Legion role are daemon liveness signals, not reasons to
152
- add a second subscriber or manually retry. For example, treat a `delivery_failed` wake as the
153
- daemon's responsibility to revive or recreate the backing worker and re-deliver; otherwise it
154
- resurrects the root process with derived catch-up and the workspace handoffs. Raw delivery failures
155
- stay out of architect context.
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 gates, child counts, and PR verdicts against current artifacts, then resume the applicable numbered lifecycle step. It is a current-state snapshot, not a raw-event replay. `gates[LEGION_TREE].open` is the design gate's current state: `true` means the root spec is approved at its current version and you may spawn; `false` (or no `open` key, meaning no gate is registered) means the sequence in section 1 still applies. For each entry in its `phaseCompletions` (`{issue, role, summary, at}`, phases that completed while you were not live), handle it exactly as a `phase-complete` wake. Then compare `childCounts[LEGION_ISSUE].open` (the children not `done`) with `legion state` and Dispatch: any **open** released child — `todo` through `retro` — with no architect role claim gets `spawn_worker` for its architect, a `child-adopted` or `child-status` wake you missed while not live; a `done` child gets nothing, whether or not a lingering legacy tree of its own still shows in `legion state`. |
309
- | `worker-died` | Payload `{type:"worker-died", issue, role}`. The daemon probed and retried this role's worker through `MAX_LAUNCH_FAILURES` attempts and could not confirm a boot — never a raw-event replay or a silent revive. 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. |
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
@@ -62,7 +62,8 @@ override a Sami ruling quoted here.
62
62
  | New issue created in the Dispatch project (`issue.created`, status `triage`; resync heals misses) | issue key + triage context (incl. pre-existing children) | Triage: `legion({ op: "set_status", issue, status: "todo" })` to admit, or set `backlog`/`icebox` to park |
63
63
  | Backlog eligibility | slot freed / priority change | Reconsider parked items and move the eligible root to `todo` |
64
64
  | Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; issue-scoped human Q&A goes through `dispatch_ask` from the owning architect, not here |
65
- | Resync report | artifact-driven anomaly list (zero-owner trees, untriaged-open, launch-failed) | Verify against fresh state, then heal |
65
+ | Resync report | artifact-driven anomaly list (zero-owner trees, untriaged-open, launch-failed, admission-drift) | Verify against fresh state, then heal |
66
+ | Resync report: `admission-drift` entry | issue key + whether the daemon added it to, or removed it from, its admission list (the detail says which) | No action: the daemon already repaired it in the same run. An issue that reappears in consecutive reports is a live leak — file a LEGION issue on Dispatch with both reports pasted as evidence (never a GitHub issue) |
66
67
  | `child-status` | child key + status transition | Not controller-actionable by default; if the daemon could not route it to the parent's architect role, verify the transition and forward it with `envoy_publish` |
67
68
  | Mention | Slack/GitHub PR @mention text | Answer, or route to the owning issue's architect role |
68
69
  | Closed-tree activity (comment, review, CI on a closed tree) | issue, root, event summary | Read the artifact; if work should resume, `legion({ op: "set_status", issue: root, status: "todo" })`; otherwise no action — the event is not held or redelivered |
@@ -124,7 +125,10 @@ Treat a resync report as an anomaly list, not an instruction. For every zero-own
124
125
  untriaged-open, or launch-failed issue it names, verify `legion state --json` and the
125
126
  current Dispatch issue first. Then heal the verified condition: admit an eligible root, move
126
127
  an issue back to its intended status, or use the applicable daemon control path. Do not act
127
- on stale entries until their source artifact explains the anomaly.
128
+ on stale entries until their source artifact explains the anomaly. An `admission-drift` entry
129
+ needs no healing — the daemon added the tree back to (or removed it from) its admission list in
130
+ the same run; verify only that the same issue does not recur in the next report, and file a
131
+ LEGION issue with both reports if it does.
128
132
 
129
133
  ## Mentions
130
134
 
@@ -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
- `architect` encodes to `legion-acme-LEGION-41-architect`. Never hand-format one for another
27
- role: your own role topic and your tree's architect's topic are stated at the end of your
28
- system prompt (a "Legion addressing" line the daemon appends), a sibling role's topic is
29
- yours with the trailing `-<role>` replaced, and the `roleToken` helper in `@legion/contracts`
30
- computes any other one exactly the way the daemon does — prefer a topic you've already
31
- been given before recomputing one.
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