@sjawhar/opencode-legion-envoy 1.0.0 → 1.2.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.
@@ -13611,6 +13611,7 @@ var CommentEventPayloadSchema = object({
13611
13611
  reply_to: string2().nullish(),
13612
13612
  ask_id: string2().nullish(),
13613
13613
  ask_question: string2().optional(),
13614
+ ask_state: _enum2(["open", "answered", "resolved"]).optional(),
13614
13615
  anchor: object({ quote: string2().optional() }).nullish(),
13615
13616
  suggestion: object({ replace_with: string2().optional() }).nullish(),
13616
13617
  author: object({ kind: string2(), id: string2() }).optional(),
@@ -14311,7 +14312,7 @@ function normalizeEnvoyUrl(value) {
14311
14312
  }
14312
14313
 
14313
14314
  // ../envoy-client/src/dispatch-config.ts
14314
- import { readFileSync } from "fs";
14315
+ import { readFileSync as readFileSync2 } from "fs";
14315
14316
  import { homedir } from "os";
14316
14317
  import * as path from "path";
14317
14318
 
@@ -14320,6 +14321,21 @@ function messageFor(error) {
14320
14321
  return error instanceof Error ? error.message : String(error);
14321
14322
  }
14322
14323
 
14324
+ // ../envoy-client/src/secret-file.ts
14325
+ import { readFileSync } from "fs";
14326
+ function readSecretFile(variable, filePath) {
14327
+ let contents;
14328
+ try {
14329
+ contents = readFileSync(filePath, "utf8");
14330
+ } catch (error) {
14331
+ throw new Error(`${variable} names ${filePath}, which could not be read: ${messageFor(error)}`);
14332
+ }
14333
+ const value = contents.trim();
14334
+ if (value.length === 0)
14335
+ throw new Error(`${variable} names ${filePath}, which is empty`);
14336
+ return value;
14337
+ }
14338
+
14323
14339
  // ../envoy-client/src/dispatch-config.ts
14324
14340
  var DEFAULT_SERVER_URL = "http://localhost:8766";
14325
14341
  function normalizeDispatchUrl(url2) {
@@ -14356,7 +14372,7 @@ function describeSchemaIssue(filePath, error) {
14356
14372
  function readEnvoyFile(filePath) {
14357
14373
  let raw;
14358
14374
  try {
14359
- raw = readFileSync(filePath, "utf-8");
14375
+ raw = readFileSync2(filePath, "utf-8");
14360
14376
  } catch {
14361
14377
  return { kind: "absent" };
14362
14378
  }
@@ -14389,8 +14405,22 @@ function resolveDispatchConfig(env, options = {}) {
14389
14405
  };
14390
14406
  const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
14391
14407
  const url2 = rawUrl ? parsedDispatchUrl(rawUrl.value, rawUrl.source) : { url: null, error: null };
14392
- const token = env.DISPATCH_TOKEN ?? merged.token ?? null;
14393
- const tokenSource = env.DISPATCH_TOKEN === undefined ? "dispatch.token" : "DISPATCH_TOKEN";
14408
+ let token;
14409
+ let tokenSource;
14410
+ if (env.DISPATCH_TOKEN_FILE !== undefined) {
14411
+ tokenSource = "DISPATCH_TOKEN_FILE";
14412
+ try {
14413
+ token = readSecretFile(tokenSource, env.DISPATCH_TOKEN_FILE);
14414
+ } catch (error) {
14415
+ return { enabled: false, url: url2.url, token: null, error: messageFor(error) };
14416
+ }
14417
+ } else if (env.DISPATCH_TOKEN !== undefined) {
14418
+ tokenSource = "DISPATCH_TOKEN";
14419
+ token = env.DISPATCH_TOKEN;
14420
+ } else {
14421
+ tokenSource = "dispatch.token";
14422
+ token = merged.token ?? null;
14423
+ }
14394
14424
  if (url2.error !== null)
14395
14425
  return { enabled: false, url: null, token, error: url2.error };
14396
14426
  if (url2.url === null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -118,6 +118,11 @@ in its Conversation card and reply thread. Resolution is not an answer: it never
118
118
  resolved. A human may reply to an open or answered ask; so may you, e.g. after finding the answer — use `reply_to_ask` on
119
119
  `dispatch_comment` (mutually exclusive with `reply_to`).
120
120
 
121
+ A human reply while your ask is still open (the delivered `comment.created` carries `ask_state: open`) is a request for
122
+ clarification, not an answer: the human did not understand the question or needs more before choosing. The ask now waits on you
123
+ in their Inbox. Answer in the same thread with `dispatch_comment({ reply_to_ask })`, or reword the question itself with
124
+ `dispatch_edit_ask` when the wording was the problem; either puts the ask back in front of them. Do not open a second ask.
125
+
121
126
  ## The Spec
122
127
 
123
128
  The spec holds requirements, design, acceptance, decisions, and rejected alternatives, structured per [Writing a spec](#writing-a-spec).
@@ -218,6 +218,7 @@ corresponding lifecycle procedure.
218
218
  | `pr-ready` | Verify the live PR head, green status, and review state. Continue the review/retro/merger order only for that current head. |
219
219
  | `pr-review` | Payload `{type:"pr-review", state, author, body}`. Delivered to whichever role is currently active for the issue, falling back to you when no worker phase is active. Follows the same verdict rule as a reviewer's `phase-complete`: `state: "changes_requested"` sends the implementer back in with the review findings, then tester, then reviewer — never the reviewer again and never retro; `state: "approved"` proceeds toward retro (step 5) once the step 6 integration/merge-gate conditions are met. |
220
220
  | `pr-blocked` | Read the failed CI evidence and recovery attempts. Assign a focused implementer or corrective child, then return it through testing and review; do not treat the blocked PR as final. |
221
+ | `pr-merged` | Payload `{type:"pr-merged", pr, mergeCommitSha}`. The merge queue landed the PR. This is your cue for step 7: post the sign-off comment naming that merge commit and set the issue `done`. Nothing else follows a merge. |
221
222
  | `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. |
222
223
  | `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. |
223
224
  | `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. 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. |
@@ -18,8 +18,9 @@ until that PR merges, this skill's contract is not yet runnable on `main`.
18
18
  The Legion extension claims `legion-<project>-controller` and registers controller readiness
19
19
  with the daemon during session startup. Do not handle a wake unless that startup succeeded.
20
20
 
21
- For an interactive takeover, start OMP with `LEGION_CONTROLLER_SECRET` and
22
- `LEGION_DAEMON_URL` in its environment, then run:
21
+ For an interactive takeover, start OMP with `LEGION_CONTROLLER_SECRET` (or
22
+ `LEGION_CONTROLLER_SECRET_FILE`, a path to a file holding it) and `LEGION_DAEMON_URL` in its
23
+ environment, then run:
23
24
 
24
25
  ```text
25
26
  /legion-claim-controller
@@ -19,8 +19,9 @@ until that PR merges, this skill's contract is not yet runnable on `main`.
19
19
 
20
20
  The daemon spawns you as a separate `omp --mode rpc` process (behind `legion worker-shim`,
21
21
  in a tmux pane) with `LEGION_TREE`, `LEGION_ISSUE`, `LEGION_ROLE`, `LEGION_GENERATION`,
22
- `LEGION_BOOT_TOKEN`, `LEGION_DAEMON_URL`, `LEGION_STATE_DIR`, and `LEGION_WORKSPACE` in your
23
- environment (`LEGION_PROJECT` is also supplied, but nothing reads it). The extension
22
+ `LEGION_BOOT_TOKEN_FILE` (a 0600 file under `$LEGION_STATE_DIR/secrets` holding your boot token;
23
+ the extension reads it for you), `LEGION_DAEMON_URL`, `LEGION_STATE_DIR`, and `LEGION_WORKSPACE`
24
+ in your environment (`LEGION_PROJECT` is also supplied, but nothing reads it). The extension
24
25
  completes the boot handshake for you at session start — it registers with the daemon, claims
25
26
  your role, and signals readiness. You never call `envoy_role_set` yourself.
26
27