@sjawhar/opencode-legion-envoy 1.14.0 → 1.14.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.
@@ -13663,7 +13663,9 @@ var ChildStatusEventPayloadSchema = object({
13663
13663
  var SubscriptionRemovedEventPayloadSchema = object({
13664
13664
  session_id: string2().optional(),
13665
13665
  by: object({ kind: string2(), id: string2().optional() }).passthrough().optional(),
13666
- topics: array(string2()).optional()
13666
+ topics: array(string2()).optional(),
13667
+ pending: boolean2().optional(),
13668
+ request_event_id: number2().int().positive().optional()
13667
13669
  });
13668
13670
  // ../contracts/src/dispatch-snippet.ts
13669
13671
  var HTML_ENTITIES = [
@@ -14616,6 +14618,11 @@ function asErrorShape(value) {
14616
14618
  function isJson(response) {
14617
14619
  return response.headers.get("content-type")?.includes("application/json") ?? false;
14618
14620
  }
14621
+ var DISPATCH_TOOL_DEADLINE_MS = 60000;
14622
+ function requestSignal(signal) {
14623
+ const deadline = AbortSignal.timeout(DISPATCH_TOOL_DEADLINE_MS);
14624
+ return signal === undefined ? deadline : AbortSignal.any([signal, deadline]);
14625
+ }
14619
14626
 
14620
14627
  class DispatchClient {
14621
14628
  token;
@@ -14623,10 +14630,12 @@ class DispatchClient {
14623
14630
  #baseUrl;
14624
14631
  #resolvedIssues = new Map;
14625
14632
  #creatingIssues = new Map;
14626
- constructor(baseUrl, token, fetchImpl = fetch) {
14633
+ #signal;
14634
+ constructor(baseUrl, token, fetchImpl = fetch, signal) {
14627
14635
  this.token = token;
14628
14636
  this.fetchImpl = fetchImpl;
14629
14637
  this.#baseUrl = baseUrl.replace(/\/+$/, "");
14638
+ this.#signal = requestSignal(signal);
14630
14639
  }
14631
14640
  async issue(input) {
14632
14641
  return this.#json("POST", ["api", "v1", "issues"], input);
@@ -14838,6 +14847,7 @@ class DispatchClient {
14838
14847
  const response = await this.fetchImpl(this.#url(path, query), {
14839
14848
  method,
14840
14849
  headers,
14850
+ signal: this.#signal,
14841
14851
  ...body === undefined ? {} : { body: JSON.stringify(body) }
14842
14852
  });
14843
14853
  return this.#response(response);
@@ -14846,7 +14856,8 @@ class DispatchClient {
14846
14856
  const response = await this.fetchImpl(this.#url(path), {
14847
14857
  method,
14848
14858
  headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
14849
- body
14859
+ body,
14860
+ signal: this.#signal
14850
14861
  });
14851
14862
  return this.#response(response);
14852
14863
  }
@@ -15311,7 +15322,7 @@ async function executeDispatchTool(input) {
15311
15322
  const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
15312
15323
  const args = toolSchema(input.tool).parse(ownerArguments.args);
15313
15324
  const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
15314
- const client = new DispatchClient(configUrl, configToken, input.fetchImpl);
15325
+ const client = new DispatchClient(configUrl, configToken, input.fetchImpl, input.signal);
15315
15326
  const owner = ownerArguments.owner?.kind === "issue" ? {
15316
15327
  kind: "issue",
15317
15328
  issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
@@ -15894,7 +15905,7 @@ class EnvoyApiError extends Error {
15894
15905
  function createEnvoyClient(config) {
15895
15906
  const baseUrl = normalizeEnvoyUrl(config.baseUrl);
15896
15907
  const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
15897
- const apiToken = process.env["ENVOY_TOKEN"];
15908
+ const { ENVOY_TOKEN: apiToken } = process.env;
15898
15909
  const request = async (path, init) => {
15899
15910
  const url = `${baseUrl}${path}`;
15900
15911
  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.14.0",
3
+ "version": "1.14.1",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -165,7 +165,8 @@ dispatch_edit_ask({
165
165
  })
166
166
  ```
167
167
  At least one field besides `ask` is required. Use this only while the same decision remains open: it keeps the prior text in the event
168
- log. An answered or resolved ask cannot be edited. If the decision is moot or superseded, retract the old ask and open a new one.
168
+ log and invalidates any answer draft against the prior `edited_at` revision, so the human sees the new wording and explicitly reconfirms.
169
+ An answered or resolved ask cannot be edited. If the decision is moot or superseded, retract the old ask and open a new one.
169
170
 
170
171
  An ask stays open until a human answers, unless its question no longer needs that answer. Retract a moot or superseded question, or
171
172
  self-resolve one after finding the answer:
@@ -127,10 +127,12 @@ legion({
127
127
 
128
128
  The daemon spawns that sub-architect as its own process with the child's context already
129
129
  in its environment; a resume of an existing role continues the same process instead of
130
- starting a fresh one. Keep the returned session identifiers, because retro and adjustment
131
- use those live sessions. Park while children are in flight. On each child closure,
132
- re-scope open work, close obsolete work with a reason, and release the next wave only
133
- when it now makes sense. There is no inter-child dependency mechanism to encode.
130
+ starting a fresh one. Keep the returned session identifiers; retro and adjustment resume
131
+ those same sessions through `spawn_worker` (a finished worker is retired after
132
+ `worker_idle_retire_seconds` and comes back from its session file). Park while children are
133
+ in flight. On each child closure, re-scope open work, close obsolete work with a reason, and
134
+ release the next wave only when it now makes sense. There is no inter-child dependency
135
+ mechanism to encode.
134
136
 
135
137
  ## 3. Children complete
136
138
 
@@ -160,18 +162,24 @@ review and the merge-gate sequence.
160
162
 
161
163
  ## 5. Retro
162
164
 
163
- Retro is mandatory for every issue that passed review, before merge. Message the
164
- implementer's live session (idle since it completed its phase; the daemon never tears
165
- it down) with `envoy_publish` to its role token, naming the skill:
165
+ Retro is mandatory for every issue that passed review, before merge. Send the implementer
166
+ back in through the daemon `spawn_worker` on the implementer carrying the retro task. This
167
+ resumes the same agent whether its pane is still live or the daemon has already retired it
168
+ idle (a finished worker is retired after `worker_idle_retire_seconds`, default 600 s, and
169
+ resumed from its session file on its next assignment). Never `envoy_publish` to a finished
170
+ worker's role topic for this: a retired role has no live holder and the publish is rejected
171
+ with 404.
166
172
 
167
173
  ```text
168
- envoy_publish({
169
- topic: "notifications.role.<implementer's encoded token>",
170
- message: "Run the legion-retro skill now. Capture durable learnings and post the issue comment; do not create a .legion handoff file."
174
+ legion({
175
+ op: "spawn_worker",
176
+ issue: "LEGION-40",
177
+ role: "implementer",
178
+ task: "Run the legion-retro skill now. Capture durable learnings and post the issue comment; do not create a .legion handoff file."
171
179
  })
172
180
  ```
173
181
 
174
- Wait for the messaged implementer to report its durable retro result. Retro output is
182
+ Wait for the implementer to report its durable retro result. Retro output is
175
183
  `docs/solutions/` plus an issue comment; it must not create a `.legion` file or change
176
184
  the reviewer-approved head after cleanup.
177
185
 
@@ -189,7 +197,8 @@ Preserve this order exactly:
189
197
  head. The deletion must land before that approval, which is head-pinned. An implementer
190
198
  completion always writes the issue's status as `testing`; this one is not a test round,
191
199
  so on its `phase-complete` wake call `legion({ op: "set_status", issue, status: "retro" })`
192
- before messaging the reviewer to approve;
200
+ before `spawn_worker` on the reviewer to approve that head (a finished reviewer may already
201
+ be retired; `spawn_worker` resumes it);
193
202
  3. retro completes without dirtying the branch beyond `docs/solutions/`;
194
203
  4. the merger verifies the current head is the reviewer-approved head plus only the retro
195
204
  commits and publishes `READY #<n> at <sha>` to `notifications.role.pr-queue`; it never
@@ -61,7 +61,9 @@ Reach any live role on this issue the same way you reach the architect: `envoy_p
61
61
  `notifications.role.` followed by that role's encoded token. Use it when you need context an
62
62
  earlier phase has that its handoff doesn't cover — ask the planner why a constraint was
63
63
  scoped that way, ask the implementer what a commit actually did. A role that finished its
64
- phase is still alive and idle in its pane; it answers.
64
+ phase stays idle in its pane for the daemon's idle-retire window and answers; once retired (no
65
+ live holder, a publish is rejected 404), read its committed handoff or ask the architect to
66
+ `spawn_worker` it.
65
67
 
66
68
  ## Workspace and handoff precedence
67
69
 
@@ -316,7 +318,9 @@ record of this issue's active phase. Do not add pipeline labels, run a controlle
316
318
  invent a different completion protocol — this is the whole contract.
317
319
 
318
320
  **Stay in this session afterward.** Your process does not exit when your phase completes;
319
- it goes idle in its pane. Other roles on this issue may reach you through Envoy with
321
+ it goes idle in its pane, and after `worker_idle_retire_seconds` (default 600 s) idle with no
322
+ active phase the daemon retires it — your next assignment resumes this same session from its
323
+ session file, so it is still you. Other roles on this issue may reach you through Envoy with
320
324
  questions about the work you did — answer them, reading `$LEGION_WORKSPACE` and your own
321
325
  committed handoff as needed, without mutating anything (see Workspace and handoff
322
326
  precedence above). You will also be the one resumed, with a new prompt in this same