@sjawhar/pi-legion-envoy 5.5.2 → 5.5.4

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
@@ -29960,7 +29960,7 @@ function dispatchToolSchema(spec, z2, opts) {
29960
29960
  const schemaOptions = strict === undefined ? undefined : { strict };
29961
29961
  return spec.validation === undefined ? z2.object(shape, schemaOptions) : z2.refineObject(shape, spec.validation.check, spec.validation.message, schemaOptions);
29962
29962
  }
29963
- var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue in the repository's dashboard-configured project or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
29963
+ var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference. An external reference addresses an existing Dispatch issue, including one linked to that GitHub pull request; only dispatch_issue with external creates a native issue.";
29964
29964
  var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
29965
29965
  function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false) {
29966
29966
  return {
@@ -30103,7 +30103,7 @@ var dispatchToolSpecs = [
30103
30103
  {
30104
30104
  name: "dispatch_ask",
30105
30105
  example: { issue: "DSP-1", question: "Ship this?" },
30106
- description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
30106
+ description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anything you are blocked on a human for, including a credential or grant to renew, an approval, or a decision, is an ask, never a message. " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
30107
30107
  arguments: (z2) => ({
30108
30108
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
30109
30109
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -30224,7 +30224,7 @@ var dispatchToolSpecs = [
30224
30224
  {
30225
30225
  name: "dispatch_message",
30226
30226
  example: { issue: "DSP-1", body: "Implementation started." },
30227
- description: "Post a note humans must read now: a reply to a human's message, a deliverable that landed, or a blocker only " + "they can clear. Never progress or status updates - Dispatch is a high-signal record, not a log. Not a decision " + "(dispatch_ask) or document feedback (dispatch_comment). To answer a human's direct message to this session - " + "one sent from the Agents page, which names no issue - pass that message's bare id as in_reply_to and no issue; " + "the reply lands in that conversation, and a second call with the same in_reply_to posts nothing because " + "Dispatch keeps the one reply per message. Every other message names its issue. " + `Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
30227
+ description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A blocker only a human can " + "clear is an ask (dispatch_ask), so it lands in their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a decision (dispatch_ask) or document feedback (dispatch_comment). To answer a human's direct message to this session - " + "one sent from the Agents page, which names no issue - pass that message's bare id as in_reply_to and no issue; " + "the reply lands in that conversation, and a second call with the same in_reply_to posts nothing because " + "Dispatch keeps the one reply per message. Every other message names its issue. " + `Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
30228
30228
  arguments: (z2) => ({
30229
30229
  issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
30230
30230
  body: z2.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
@@ -32265,7 +32265,6 @@ class DispatchClient {
32265
32265
  fetchImpl;
32266
32266
  #baseUrl;
32267
32267
  #resolvedIssues = new Map;
32268
- #creatingIssues = new Map;
32269
32268
  #signal;
32270
32269
  constructor(baseUrl, token, fetchImpl = fetch, signal) {
32271
32270
  this.token = token;
@@ -32276,6 +32275,9 @@ class DispatchClient {
32276
32275
  async issue(input) {
32277
32276
  return this.#json("POST", ["api", "v1", "issues"], input);
32278
32277
  }
32278
+ async resolveIssue(issueReference) {
32279
+ return this.#resolveIssue(issueReference);
32280
+ }
32279
32281
  async listIssues(options = {}) {
32280
32282
  return this.#json("GET", ["api", "v1", "issues"], undefined, options);
32281
32283
  }
@@ -32471,43 +32473,6 @@ class DispatchClient {
32471
32473
  ...query.since === undefined ? {} : { since: query.since }
32472
32474
  });
32473
32475
  }
32474
- async ensureIssue(issueReference, actor) {
32475
- if (!issueReference.includes("#"))
32476
- return issueReference;
32477
- try {
32478
- return await this.#resolveIssue(issueReference);
32479
- } catch (error48) {
32480
- if (!(error48 instanceof DispatchServiceError) || error48.status !== 404)
32481
- throw error48;
32482
- }
32483
- let creating = this.#creatingIssues.get(issueReference);
32484
- if (!creating) {
32485
- creating = this.#createExternalIssue(issueReference, actor);
32486
- this.#creatingIssues.set(issueReference, creating);
32487
- }
32488
- try {
32489
- return await creating;
32490
- } finally {
32491
- if (this.#creatingIssues.get(issueReference) === creating) {
32492
- this.#creatingIssues.delete(issueReference);
32493
- }
32494
- }
32495
- }
32496
- async#createExternalIssue(issueReference, actor) {
32497
- try {
32498
- const created = await this.#json("POST", ["api", "v1", "issues"], {
32499
- external: issueReference,
32500
- actor
32501
- });
32502
- this.#resolvedIssues.set(issueReference, Promise.resolve(created.key));
32503
- return created.key;
32504
- } catch (error48) {
32505
- if (error48 instanceof DispatchServiceError && (error48.status === 409 || error48.status === 500)) {
32506
- return this.#resolveIssue(issueReference);
32507
- }
32508
- throw error48;
32509
- }
32510
- }
32511
32476
  async#resolveIssue(issueReference) {
32512
32477
  if (!issueReference.includes("#"))
32513
32478
  return issueReference;
@@ -33685,7 +33650,7 @@ async function executeDispatchTool(input) {
33685
33650
  const client = dispatchClient();
33686
33651
  const owner = ownerArguments.owner?.kind === "issue" ? {
33687
33652
  kind: "issue",
33688
- issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
33653
+ issue: await resolveExistingIssue(client, ownerArguments.owner.issue)
33689
33654
  } : ownerArguments.owner;
33690
33655
  const issue2 = () => {
33691
33656
  if (owner?.kind !== "issue")
@@ -34337,13 +34302,12 @@ ${trailer.join(`
34337
34302
  throw new Error(`Unknown Dispatch tool: ${input.tool}`);
34338
34303
  }
34339
34304
  }
34340
- async function ensureIssue(client, issueReference, actor) {
34305
+ async function resolveExistingIssue(client, issueReference) {
34341
34306
  try {
34342
- return await client.ensureIssue(issueReference, actor);
34307
+ return await client.resolveIssue(issueReference);
34343
34308
  } catch (error48) {
34344
- if (error48 instanceof DispatchServiceError && error48.code === "PROJECT_UNMAPPED") {
34345
- const repository = issueReference.slice(0, issueReference.lastIndexOf("#"));
34346
- throw new Error(`repository ${repository} is not mapped in repository settings and no DISPATCH_DEFAULT_PROJECT is configured`);
34309
+ if (error48 instanceof DispatchServiceError && error48.status === 404) {
34310
+ throw new Error(`no Dispatch issue is linked to ${issueReference}; create it first with ` + `dispatch_issue({ external: "${issueReference}", ... })`);
34347
34311
  }
34348
34312
  throw error48;
34349
34313
  }
package/dist/legion.js CHANGED
@@ -16142,7 +16142,7 @@ import { logger } from "@oh-my-pi/pi-utils";
16142
16142
  // package.json
16143
16143
  var package_default = {
16144
16144
  name: "@sjawhar/pi-legion-envoy",
16145
- version: "5.5.2",
16145
+ version: "5.5.4",
16146
16146
  type: "module",
16147
16147
  omp: {
16148
16148
  extensions: [
@@ -30033,7 +30033,7 @@ function dispatchToolSchema(spec, z2, opts) {
30033
30033
  const schemaOptions = strict === undefined ? undefined : { strict };
30034
30034
  return spec.validation === undefined ? z2.object(shape, schemaOptions) : z2.refineObject(shape, spec.validation.check, spec.validation.message, schemaOptions);
30035
30035
  }
30036
- var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue in the repository's dashboard-configured project or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
30036
+ var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference. An external reference addresses an existing Dispatch issue, including one linked to that GitHub pull request; only dispatch_issue with external creates a native issue.";
30037
30037
  var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
30038
30038
  function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false) {
30039
30039
  return {
@@ -30176,7 +30176,7 @@ var dispatchToolSpecs = [
30176
30176
  {
30177
30177
  name: "dispatch_ask",
30178
30178
  example: { issue: "DSP-1", question: "Ship this?" },
30179
- description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
30179
+ description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anything you are blocked on a human for, including a credential or grant to renew, an approval, or a decision, is an ask, never a message. " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
30180
30180
  arguments: (z2) => ({
30181
30181
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
30182
30182
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -30297,7 +30297,7 @@ var dispatchToolSpecs = [
30297
30297
  {
30298
30298
  name: "dispatch_message",
30299
30299
  example: { issue: "DSP-1", body: "Implementation started." },
30300
- description: "Post a note humans must read now: a reply to a human's message, a deliverable that landed, or a blocker only " + "they can clear. Never progress or status updates - Dispatch is a high-signal record, not a log. Not a decision " + "(dispatch_ask) or document feedback (dispatch_comment). To answer a human's direct message to this session - " + "one sent from the Agents page, which names no issue - pass that message's bare id as in_reply_to and no issue; " + "the reply lands in that conversation, and a second call with the same in_reply_to posts nothing because " + "Dispatch keeps the one reply per message. Every other message names its issue. " + `Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
30300
+ description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A blocker only a human can " + "clear is an ask (dispatch_ask), so it lands in their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a decision (dispatch_ask) or document feedback (dispatch_comment). To answer a human's direct message to this session - " + "one sent from the Agents page, which names no issue - pass that message's bare id as in_reply_to and no issue; " + "the reply lands in that conversation, and a second call with the same in_reply_to posts nothing because " + "Dispatch keeps the one reply per message. Every other message names its issue. " + `Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
30301
30301
  arguments: (z2) => ({
30302
30302
  issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
30303
30303
  body: z2.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
@@ -147,8 +147,9 @@ See [Typed blocks](#typed-blocks) for the syntax and [Before you ask](#before-yo
147
147
  Every session works on an issue or project document. Legion pre-fills `issue` from `LEGION_ISSUE`: use a native issue key such as
148
148
  `LEGION-3`, an external `owner/repo#n` reference, or a bare positive number (resolved against the cwd repository). Otherwise pass
149
149
  exactly one owner to every owner-scoped tool: `issue` for an issue, or `project` and `artifact` for an unlinked project document (see
150
- [References](#references) for the resulting ref shape). On first use, an external issue reference creates its native issue in the
151
- project configured for that repository in Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
150
+ [References](#references) for the resulting ref shape). An external issue reference addresses the existing Dispatch issue linked to
151
+ that GitHub issue or pull request. Only `dispatch_issue` with `external` creates a native issue; if no issue is linked, call
152
+ `dispatch_issue({ external: "owner/repo#n", project: "<project>", title: "<title>" })` before addressing it.
152
153
 
153
154
  Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with neighboring issue keys. They also include nullable coarse priority (`P0` highest through `P3` lowest) and `assignee`: the lowercase GitHub login of the human who answers the issue's asks, or `null` when nobody holds it. `dispatch_read` of an issue prints it as `Assignee: <login>` or `Assignee: unassigned`.
154
155
 
@@ -189,8 +190,8 @@ dashboard's **Unclaimed** filter is how you find work nobody is on.
189
190
  that session (its id is in the message; `envoy_send` reaches it) or pick up something else,
190
191
  and tell the human if you believe the work should be yours. When a **human** holds it, the
191
192
  refusal names the person and says nothing about a session running, because there is none to
192
- message: ask them on the issue (`dispatch_message`) instead, and never assume their claim has
193
- lapsed — only a human releases or forces a human's claim.
193
+ message: ask them with `dispatch_ask` instead, so the open ask appears in their Inbox, and
194
+ never assume their claim has lapsed — only a human releases or forces a human's claim.
194
195
  - **`409 CLAIM_CONTENDED` means the issue changed hands twice while your call ran**, so nothing
195
196
  was applied and nobody's liveness was checked. Read the issue and decide again; it is not a
196
197
  refusal by a live holder.
@@ -421,17 +422,16 @@ Before saying you are waiting for human input, call `dispatch_open_asks`. With n
421
422
 
422
423
  **Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that Sami has not already settled, send a one-line ask before the first implementation commit. A lane's schema decision or a platform-PO contract ruling does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request; it is inferred from AGENTC-186's 2026-09-16 retro (platform PO, 2026-09-17). A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, its options): build it without an ask, as gate 4 of [Before you ask](#before-you-ask) says. This rule covers only product shape outside what he asked for, and its ask comes before the commit that sets that shape.
423
424
 
424
- **Anything that needs the human is an ask, or it does not exist.** An approval, a credential,
425
- a setting only they can change, a review click, a conflict between two of their own rules - if
426
- your work waits on it, open a `dispatch_ask` the moment you know, the action as the question. The
427
- exception is a halt condition from [Before you ask](#before-you-ask) gate 1, which goes to the
428
- platform PO over Envoy instead.
429
- Never write it into a spec, a comment reply, a message, or a
430
- pull-request body: nothing in those paths reaches the human's Inbox, and a human who is not
431
- reading your document does not know they are the blocker. Before asking, try to remove the
432
- step: a value already on the machine, a permission you already hold, an API that replaces the
433
- click. One ask per item, `urgency: "high"` when work is stopped on it; while it is open, keep
434
- working on everything that is not.
425
+ **Anything you are blocked on a human for is an open ask.** An agent waits on a human only through
426
+ an open ask. An approval, a credential or grant to renew, a setting only they can change, a review
427
+ click, a decision, or a conflict between two of their own rules: open a `dispatch_ask` the moment
428
+ you know, the action as the question. The exception is a halt condition from [Before you
429
+ ask](#before-you-ask) gate 1, which goes to the platform PO over Envoy instead. Never write it
430
+ into a spec, a comment reply, a message, or a pull-request body: nothing in those paths reaches
431
+ the human's Inbox, and a human who is not reading your document does not know they are the
432
+ blocker. Before asking, try to remove the step: a value already on the machine, a permission you
433
+ already hold, an API that replaces the click. One ask per item, `urgency: "high"` when work is
434
+ stopped on it; while it is open, keep working on everything that is not.
435
435
 
436
436
  A to-do handed to a human is an ordinary question: phrase the to-do as the question and give it
437
437
  the options that name its outcomes, in the human's words - there is no fixed vocabulary and the
@@ -724,7 +724,7 @@ once.
724
724
  ## Messages
725
725
 
726
726
  Dispatch is a high-signal record for humans, not a log of what you are doing. A message is a reply to a human's message, or a
727
- change a human must know about now: a deliverable landed, a blocker only they can clear. Nothing else — no progress updates, no
727
+ change a human must know about now: a deliverable landed. Nothing else — no progress updates, no
728
728
  "starting X", no "still working", no restating the spec, no status on a timer. Your transcript is where work is narrated; the
729
729
  pull request is where it is summarised. One message that a human reads beats ten that train them to skip you.
730
730
 
@@ -603,6 +603,11 @@ This publishes your phase's completion to the architect's role and clears the da
603
603
  record of this issue's active phase. Do not add pipeline labels, run a controller loop, or
604
604
  invent a different completion protocol — this is the whole contract.
605
605
 
606
+ A reviewer's phase ends with its completion, not with its review: submit the review on GitHub
607
+ first, then commit the handoff and complete. The daemon moves the issue once both are in — the
608
+ decision GitHub reports and your completion, in either order — so a review posted without a
609
+ completion leaves the issue in reviewing until you finish.
610
+
606
611
  **A refused completion is information, not a retry loop.** The daemon attributes your report to
607
612
  the run whose task you took, and answers with what it found. What each answer carries, and what to
608
613
  do:
@@ -620,7 +625,10 @@ do:
620
625
  has left your phase; report to the architect rather than completing again.
621
626
  - `HANDOFF_NO_RUN` — names neither: it says this claim has taken no task, so the daemon cannot
622
627
  tell which run you are reporting. Your pane is completing outside any assignment. Say so to the
623
- architect; do not re-run the phase.
628
+ architect; do not re-run the phase. The same answer comes when your turn started before your task
629
+ reached you: a notice or a message started it, and the task, refused while that turn ran, is sent
630
+ when the turn ends. When a task arrives, do what it asks; if the work it asks for is already
631
+ committed, call `handoff_complete` again, and never redo the work or write a second handoff.
624
632
  - `HANDOFF_ALREADY_RECORDED` — names your role, the phase, the review round and the commit. This
625
633
  exact call was received before, and its first answer stands: accepted, or a refusal the daemon
626
634
  records with the call — `HANDOFF_STALE_GENERATION`, `HANDOFF_NOT_CURRENT_PHASE`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.5.2",
3
+ "version": "5.5.4",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [