@sjawhar/pi-legion-envoy 5.22.0 → 5.23.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/dist/envoy.js CHANGED
@@ -30069,6 +30069,8 @@ var SPEC_SECTIONS = [
30069
30069
  var SPEC_WRITING_GUIDANCE = `When writing a spec, use these sections in order: ${SPEC_SECTIONS.join(", ")}. ` + "Write for a reader who has not seen the code: plain sentences, every identifier expanded on " + "first use, no coined shorthand; see skills/dispatch Writing for the human and Writing a spec.";
30070
30070
  var ASK_URGENCIES = ["low", "med", "high", "blocking"];
30071
30071
  var ASK_QUESTION_MAX = 800;
30072
+ var SEARCH_QUERY_MAX = 1000;
30073
+ var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
30072
30074
  var ISSUE_STATUSES = [
30073
30075
  "triage",
30074
30076
  "icebox",
@@ -30384,7 +30386,11 @@ var dispatchToolSpecs = [
30384
30386
  example: { query: "astrolabe" },
30385
30387
  description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
30386
30388
  arguments: (z2) => ({
30387
- query: z2.string({ min: 2 }).describe("Keyword, phrase, or websearch expression; at least 2 characters."),
30389
+ query: z2.string({
30390
+ min: 2,
30391
+ max: SEARCH_QUERY_MAX,
30392
+ maxHint: SEARCH_QUERY_HINT
30393
+ }).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
30388
30394
  project: z2.string().describe("Optional project key to search within.").optional(),
30389
30395
  limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
30390
30396
  })
@@ -30903,8 +30909,9 @@ function zodSchemaApi(zod) {
30903
30909
  schema = schema.min(opts.min);
30904
30910
  if (opts.max !== undefined) {
30905
30911
  const max = opts.max;
30912
+ const hint = opts.maxHint === undefined ? "" : `; ${opts.maxHint}`;
30906
30913
  schema = schema.max(max, {
30907
- error: (issue2) => overCapMessage(typeof issue2.input === "string" ? issue2.input.length : max + 1, max)
30914
+ error: (issue2) => overCapMessage(typeof issue2.input === "string" ? issue2.input.length : max + 1, max) + hint
30908
30915
  });
30909
30916
  }
30910
30917
  return schema;
package/dist/legion.js CHANGED
@@ -30238,6 +30238,8 @@ var SPEC_SECTIONS = [
30238
30238
  var SPEC_WRITING_GUIDANCE = `When writing a spec, use these sections in order: ${SPEC_SECTIONS.join(", ")}. ` + "Write for a reader who has not seen the code: plain sentences, every identifier expanded on " + "first use, no coined shorthand; see skills/dispatch Writing for the human and Writing a spec.";
30239
30239
  var ASK_URGENCIES = ["low", "med", "high", "blocking"];
30240
30240
  var ASK_QUESTION_MAX = 800;
30241
+ var SEARCH_QUERY_MAX = 1000;
30242
+ var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
30241
30243
  var ISSUE_STATUSES = [
30242
30244
  "triage",
30243
30245
  "icebox",
@@ -30553,7 +30555,11 @@ var dispatchToolSpecs = [
30553
30555
  example: { query: "astrolabe" },
30554
30556
  description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
30555
30557
  arguments: (z2) => ({
30556
- query: z2.string({ min: 2 }).describe("Keyword, phrase, or websearch expression; at least 2 characters."),
30558
+ query: z2.string({
30559
+ min: 2,
30560
+ max: SEARCH_QUERY_MAX,
30561
+ maxHint: SEARCH_QUERY_HINT
30562
+ }).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
30557
30563
  project: z2.string().describe("Optional project key to search within.").optional(),
30558
30564
  limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
30559
30565
  })
@@ -31072,8 +31078,9 @@ function zodSchemaApi(zod) {
31072
31078
  schema = schema.min(opts.min);
31073
31079
  if (opts.max !== undefined) {
31074
31080
  const max = opts.max;
31081
+ const hint = opts.maxHint === undefined ? "" : `; ${opts.maxHint}`;
31075
31082
  schema = schema.max(max, {
31076
- error: (issue2) => overCapMessage(typeof issue2.input === "string" ? issue2.input.length : max + 1, max)
31083
+ error: (issue2) => overCapMessage(typeof issue2.input === "string" ? issue2.input.length : max + 1, max) + hint
31077
31084
  });
31078
31085
  }
31079
31086
  return schema;
@@ -33495,7 +33502,7 @@ import { logger } from "@oh-my-pi/pi-utils";
33495
33502
  // package.json
33496
33503
  var package_default = {
33497
33504
  name: "@sjawhar/pi-legion-envoy",
33498
- version: "5.22.0",
33505
+ version: "5.23.0",
33499
33506
  type: "module",
33500
33507
  omp: {
33501
33508
  extensions: [
@@ -34028,7 +34035,8 @@ var LegionGoControllerRegisterResponse = exports_external.strictObject({
34028
34035
  secret: nonEmptyString2
34029
34036
  });
34030
34037
  var LegionGoControllerSecretResponse = exports_external.strictObject({
34031
- secret: nonEmptyString2
34038
+ secret: nonEmptyString2,
34039
+ designGate: exports_external.enum(["root-issues", "off"])
34032
34040
  });
34033
34041
  var LegionGoErrorResponse = exports_external.union([
34034
34042
  exports_external.strictObject({ error: nonEmptyString2 }),
@@ -165,10 +165,12 @@ do with each hit. What it leaves out:
165
165
  dispatch_search({ query, project?, limit? })
166
166
  ```
167
167
  Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`. It returns the best `limit` hits (20
168
- by default, 50 at most) across issues, documents, comments, asks, and messages. Issue-owned hit
169
- lines start with the issue key; standalone project-document hit lines start with
170
- `dispatch://PROJECT/artifact/<slug>`, followed by the absolute link. Cite the hit you build on
171
- (`dispatch://KEY` or the document reference), or state "no prior issue" in the spec.
168
+ by default, 50 at most) across issues, documents, comments, asks, and messages. A query over
169
+ 1,000 characters is refused before it is sent: search with the few words `skill://dispatch-first`
170
+ describes, never a pasted passage. Issue-owned hit lines start with the issue key; standalone
171
+ project-document hit lines start with `dispatch://PROJECT/artifact/<slug>`, followed by the
172
+ absolute link. Cite the hit you build on (`dispatch://KEY` or the document reference), or state
173
+ "no prior issue" in the spec.
172
174
 
173
175
  `dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
174
176
  Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
@@ -43,8 +43,7 @@ separate coordinator to finish necessary work.
43
43
 
44
44
  Deployment instructions, when present, are the operator's standing rules for this repository —
45
45
  required checks, deploy/smoke commands, code-owner expectations, standing roles you may consult,
46
- the merge credential. They override this skill's defaults where they conflict; they never
47
- override a Sami ruling quoted here.
46
+ the merge credential. They override this skill's defaults where they conflict.
48
47
 
49
48
  ## 1. Decompose or adopt
50
49
 
@@ -89,7 +89,10 @@ mints a new secret, so your grants stop working and the role moves to the new se
89
89
  The Go daemon's controller topic is a wake for a session that is running when it is published.
90
90
  Envoy hands an Oh My Pi session no retained copy of a notice published before it subscribed, so a
91
91
  hold, a tree architect's failed claim, a new triage root, or a freed slot from while no controller
92
- ran never arrives as a wake. At every start, before anything else:
92
+ ran never arrives as a wake. `legion controller start` opens your first turn with a start message
93
+ (`Legion controller start: …`), so every start and restart runs this procedure with nothing typed.
94
+ At every start, after the claim recheck ([Turn discipline](#turn-discipline)) and before anything
95
+ else:
93
96
 
94
97
  1. Read `legion state --json` and handle each issue whose `issues.<KEY>.phase` is `held` (its
95
98
  `issues.<KEY>.holdReason` is `escalated` when its architect sent it to you, and absent while the
@@ -132,15 +135,37 @@ you hand over or file for Legion to run carries it first (`labels` in `dispatch_
132
135
  `dispatch_issue`). Taking the label off a waiting root drops it from the waiting line; taking it
133
136
  off an admitted tree does not stop it.
134
137
 
138
+ ## Trees waiting on a root claim (Go daemon)
139
+
140
+ A Go root architect whose claim on its root issue was refused starts nothing and waits, holding
141
+ its slot, until the claim is free; nothing tells it when a session holder lets go without
142
+ replying. So every turn rechecks them ([Turn discipline](#turn-discipline)), the daemon's `tick`
143
+ included, which comes on its interval even with every slot taken: read each root in
144
+ `admission.active` whose `issues.<KEY>.phase` is still `admitted` with `dispatch_read`. When its
145
+ `Claimed by:` line is `nobody` or ends `· not running`, tell that tree's architect to claim again
146
+ with `envoy_publish` to `notifications.role.` followed by its claim token,
147
+ `issues.<KEY>.architect.locator.claim` in `legion state --json`. A claim that is its architect's
148
+ own, or one that still holds, needs nothing.
149
+
135
150
  ## Keeping the slots full (Go daemon)
136
151
 
137
152
  Picking the next work is your job: nobody hand-feeds issues to Legion. Keep every admission slot
138
153
  filled with the highest-priority concrete issue Legion can take. The unit of Legion work is a
139
154
  leaf, an issue with no children, never an umbrella that holds other issues.
140
155
 
141
- **When.** At every start (step 3 above), and on each `slot-free on <KEY>` wake: the daemon
142
- released `<KEY>`'s slot, because its tree finished or left the workflow, and no waiting root took
143
- it.
156
+ **When.** At every start (step 3 above), and on each of the Go daemon's walk wakes. The daemon
157
+ sends each only while a controller is registered, and none while one of the same kind is still
158
+ unsent:
159
+
160
+ - `slot-free on <KEY>`: the daemon released `<KEY>`'s slot, because its tree finished or left the
161
+ workflow, and the slot is free by **How many** below.
162
+ - `todo on <KEY>`: `<KEY>`, an issue nobody handed to Legion, changed while in `todo` and a slot
163
+ was free, so it may be a new candidate. The daemon holds it back half a minute and folds the
164
+ events of that window into it. Walk the whole list, not only `<KEY>`.
165
+ - `tick on <PROJECT>`: the daemon's periodic wake, a minute after it starts and then every
166
+ `controller_wake_interval_seconds` (an hour by default), whatever the slots. An earlier walk
167
+ that found nothing, a day with no event, and [a tree waiting on a root
168
+ claim](#trees-waiting-on-a-root-claim-go-daemon) all get a turn from it.
144
169
 
145
170
  **Scope first.** The scope the deployment instructions state decides which issues are candidates
146
171
  at all, before anything below. When they say you hand Legion no issue yourself, or that Legion
@@ -154,10 +179,10 @@ scope.
154
179
  you add). With none free, stop.
155
180
 
156
181
  **Candidates.** The project's open `todo` issues, roots and children alike, that have no children
157
- at all and do not carry the `legion` label. `todo` alone, as Sami ruled for choosing work on
158
- 2026-09-27 (`skill://dispatch`, "Choosing what to work on"): take the top ready issue, "status
159
- `todo`, highest priority first, then board rank"; an issue that waits on a deploy or a decision
160
- belongs in `backlog`, so the walk takes nothing from `backlog` or `triage`. The Go daemon runs
182
+ at all and do not carry the `legion` label. Ready work is `todo` (`skill://dispatch`, "Choosing
183
+ what to work on"): take the top ready issue, highest priority first, then board rank. An issue that
184
+ waits on a deploy or a decision belongs in `backlog`, so the walk takes nothing from `backlog` or
185
+ `triage`. The Go daemon runs
161
186
  only labelled roots, so every root it ran since the daemon required the label carries it: a
162
187
  labelled root in `todo` is the daemon's to admit or queue, one in `triage` is yours to triage
163
188
  (step 2 above), and one anywhere else was parked by Legion or by a person. A child you take becomes
@@ -208,12 +233,12 @@ leans on `External links:`, and the label row is the one that never depends on h
208
233
 
209
234
  2. It is already in `todo`, so the label admits it: the daemon records it and gives it the free
210
235
  slot, or queues it in `admission.waiting`. It needs no status write.
211
- 3. Post one short comment that says Legion took it and names who is asked at its design gate, from
212
- the `Assignee:` line, with the assignee sentence [New issue triage](#new-issue-triage) step 4
213
- gives:
236
+ 3. Post one short comment that says Legion took it, who is asked at its design gate, and how to
237
+ undo the take, naming the assignee with the sentence [New issue triage](#new-issue-triage)
238
+ step 4 gives (which follows the design gate policy):
214
239
 
215
240
  ```text
216
- dispatch_comment({ issue: "<KEY>", body: "Legion took this issue: it was the highest-priority open issue nobody else was working on. Assigned to <login>, who will get this tree's questions and its design approval." })
241
+ dispatch_comment({ issue: "<KEY>", body: "Legion took this issue: it was the highest-priority open issue nobody else was working on. Assigned to <login>, who will get this tree's questions and its design approval. To stop Legion, move the issue to backlog. To keep Legion off it for good, also take the legion label off; taking the label off alone does not stop a tree that has started." })
217
242
  ```
218
243
 
219
244
  The daemon admits each root when Dispatch's event reaches it; the next `legion state --json`
@@ -237,9 +262,9 @@ legion status <report KEY> icebox
237
262
  ```
238
263
 
239
264
  **When.** On your first turn of each UTC day, whatever it is: your start, or a wake of any kind.
240
- Read the report issue with `dispatch_read`, and post when its `Events:` show no `message.created`
241
- from today. You have no clock of your own, so a day with no turn has no report; the next one
242
- covers it.
265
+ While you are registered, the daemon's `tick` gives you a turn at least every
266
+ `controller_wake_interval_seconds`. After the turn's own work, read the report issue with
267
+ `dispatch_read`, and post when its `Events:` show no `message.created` from today.
243
268
 
244
269
  **What.** One `dispatch_message({ issue: "<report KEY>", body })` of at most 2,000 characters,
245
270
  written as `skill://dispatch`'s "Writing for the human" says: every issue by its key and title,
@@ -253,7 +278,13 @@ every pull request by its URL.
253
278
  - **Closed without a change.** Those with no pull request, each with the reason its closing
254
279
  message gave (the `message.created` just before `issue.closed` among `Events:`).
255
280
  - **Running.** Each root in `admission.active` with its `issues.<KEY>.phase`, and the roots in
256
- `admission.waiting`.
281
+ `admission.waiting`. A root whose architect told you its claim was refused is named as waiting
282
+ on that holder: its architect started nothing and asked them to release it or take the issue
283
+ back. Nothing in `legion state` records that, so read the tree's issue (its `Claimed by:` line
284
+ and the ask or message the architect opened) before you name it.
285
+ - **The slots and the walk.** The free slots, as **How many** counts them, then this turn's walk
286
+ when it made one: what it took, and how many candidates each row of the table skipped, so a
287
+ reader can see why a free slot stays empty.
257
288
 
258
289
  A day with nothing finished says so in one sentence. When the lists do not fit, keep the counts
259
290
  and the highest-priority issues.
@@ -262,16 +293,19 @@ and the highest-priority issues.
262
293
 
263
294
  Deployment instructions, when present, are the operator's standing rules for this repository —
264
295
  required checks, deploy/smoke commands, code-owner expectations, and standing roles you may
265
- consult. They override this skill's defaults where they conflict; they never override a Sami ruling
266
- quoted here.
296
+ consult. They override this skill's defaults where they conflict, and may narrow which issues the
297
+ walk takes, but never widen it past `todo` issues or change the order it takes them in: highest
298
+ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-full-go-daemon)).
267
299
 
268
300
  ## Turn discipline
269
301
 
270
302
  - **Direct user message always first.** If this turn includes a direct user message, answer
271
303
  it before handling every other wake.
272
304
  - **One wake = one turn.** Handle exactly the wake's implication, then end the turn. Never
273
- poll, idle-loop, or wait for another event. The one addition: your first turn of each UTC day,
274
- whatever woke you, also posts the day's report ([Daily report](#daily-report-go-daemon)).
305
+ poll, idle-loop, or wait for another event. Two additions, after any direct user message: every
306
+ turn under the Go daemon first rechecks the [trees waiting on a root
307
+ claim](#trees-waiting-on-a-root-claim-go-daemon), and your first turn of each UTC day, whatever
308
+ woke you, also posts the day's report ([Daily report](#daily-report-go-daemon)).
275
309
  - **Wakes are advisory.** Before any side effect, verify the current daemon state and the
276
310
  relevant Dispatch issue. A stale or duplicate wake may cost a read, never a wrong action.
277
311
  - **Controller state is disposable.** Do not reconstruct or preserve local controller
@@ -288,6 +322,8 @@ quoted here.
288
322
  | New issue created in the Dispatch project (`issue.created`, status `triage`; under the TypeScript daemon resync heals misses, under the Go daemon the boot step above does). From the Go daemon: `triage on <KEY>` (payload `{kind: "triage"}`) on the controller topic, for an unrecorded root carrying the `legion` label only ("Issues handed to Legion" above) | issue key + triage context (incl. pre-existing children) | Triage: `legion status <KEY> todo` to admit, or set `backlog`/`icebox` to park |
289
323
  | Backlog eligibility (TypeScript daemon) | slot freed / priority change | Reconsider parked items and move the eligible root to `todo` |
290
324
  | `slot-free on <KEY>` from the Go daemon (payload `{kind: "slot-free"}`) | the root whose slot the daemon released with no waiting root to take it | Verify a free slot in `legion state --json`, then fill it ([Keeping the slots full](#keeping-the-slots-full-go-daemon)) |
325
+ | `todo on <KEY>` from the Go daemon (payload `{kind: "todo"}`) | an issue not handed to Legion that changed while in `todo` and a slot stood free, sent half a minute later | Verify a free slot, then walk the whole `todo` list ([Keeping the slots full](#keeping-the-slots-full-go-daemon)) |
326
+ | `tick on <PROJECT>` from the Go daemon (payload `{kind: "tick"}`) | the project key; the daemon's periodic wake, whatever the slots | Recheck the trees waiting on a claim, then walk if a slot is free; post the day's report if this is the day's first turn |
291
327
  | 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 |
292
328
  | Resync report | artifact-driven anomaly list (zero-owner trees, untriaged-open, launch-failed, admission-drift) | Verify against fresh state, then heal |
293
329
  | 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) |
@@ -330,15 +366,19 @@ quoted here.
330
366
  why), name who will be asked with the assignee sentence: `Assigned to <login>, who will get
331
367
  this tree's questions and its design approval`, or, when the `Assignee:` line says
332
368
  `unassigned`, `Unassigned — nobody's Inbox shows this tree's questions or its design approval
333
- until someone takes it from the issue header (Assignee, beside Priority)`. An unassigned root
369
+ until someone takes it from the issue header (Assignee, beside Priority)`. The design approval
370
+ is promised only when the `Design gate policy:` line of your system prompt, which
371
+ `legion controller start` writes from the daemon's own configuration, says
372
+ `gates.design: root-issues`. Under `gates.design: off` nobody approves a design, so drop "and
373
+ its design approval" (and "or its design approval") from the sentence. An unassigned root
334
374
  still runs; the architect's asks wait in every Inbox's Unassigned band.
335
375
 
336
376
  ## Backlog eligibility
337
377
 
338
378
  Under the Go daemon nothing reconsiders `backlog` or `icebox` on its own: [Keeping the slots
339
379
  full](#keeping-the-slots-full-go-daemon) takes only `todo` issues, since an issue that waits on a
340
- deploy or a decision belongs in `backlog` (Sami's ruling of 2026-09-27, `skill://dispatch`,
341
- "Choosing what to work on"). A handed-over root waits for a slot in `todo`, where the daemon's
380
+ deploy or a decision belongs in `backlog` (`skill://dispatch`, "Choosing what to work on"). A
381
+ handed-over root waits for a slot in `todo`, where the daemon's
342
382
  admission queue holds it (`admission.waiting`), so a park means "should not run now", and a
343
383
  parked root keeps its `legion` label. A parked issue runs again when a person sets it to `todo`,
344
384
  or when a wake tells you to re-admit it (`worker-died`, closed-tree activity).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.22.0",
3
+ "version": "5.23.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [