@sjawhar/pi-legion-envoy 1.57.1 → 1.59.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
@@ -29649,6 +29649,13 @@ function date4(params) {
29649
29649
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
29650
29650
  config(en_default());
29651
29651
  // ../contracts/src/dispatch-api.ts
29652
+ function serviceSubjectLabel(subject) {
29653
+ const parts = subject.split(":");
29654
+ if (parts.length === 4 && parts[0] === "system" && parts[1] === "serviceaccount" && parts[2] !== "" && parts[3] !== "") {
29655
+ return `${parts[2]}/${parts[3]}`;
29656
+ }
29657
+ return subject;
29658
+ }
29652
29659
  var ASK_TURNS = ["human", "agent"];
29653
29660
  var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
29654
29661
  var DispatchEventSchema = object({
@@ -32244,6 +32251,9 @@ class DispatchClient {
32244
32251
  async syncArchitectureSource(project) {
32245
32252
  return this.#json("POST", ["api", "v1", "projects", project, "architecture-source", "sync"]);
32246
32253
  }
32254
+ async getArchitectureSource(project) {
32255
+ return this.#json("GET", ["api", "v1", "projects", project, "architecture-source"]);
32256
+ }
32247
32257
  async resolveAsk(id, input) {
32248
32258
  return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
32249
32259
  }
@@ -32739,6 +32749,20 @@ function componentsChange(input, after) {
32739
32749
  return "components -> inherit";
32740
32750
  }
32741
32751
  }
32752
+ var architectureComponentsAction = 'Review the `dispatch` skill, "Architecture components", to attach it to the parts it changes or mark it as non-architectural with a reason.';
32753
+ async function architectureGuidance(client, project, components) {
32754
+ if (components.mode === "none" || components.ids.length > 0)
32755
+ return;
32756
+ try {
32757
+ await client.getArchitectureSource(project);
32758
+ return `Project ${project} has an architecture model, but this issue is not linked to any of its current components. ${architectureComponentsAction}`;
32759
+ } catch (error) {
32760
+ if (error instanceof DispatchServiceError && error.status === 404 && error.code === "SOURCE_NOT_FOUND") {
32761
+ return;
32762
+ }
32763
+ return `Could not check whether project ${project} has an architecture model: ${messageFor(error)}. ${architectureComponentsAction}`;
32764
+ }
32765
+ }
32742
32766
  function isDuplicateCandidate(value) {
32743
32767
  if (typeof value !== "object" || value === null)
32744
32768
  return false;
@@ -33320,9 +33344,16 @@ function eventHead(event) {
33320
33344
  return;
33321
33345
  }
33322
33346
  }
33347
+ function actorText(actor) {
33348
+ const service = actor.kind === "session" ? actor.service : undefined;
33349
+ if (service === undefined) {
33350
+ return `${actor.kind} ${actor.id}`;
33351
+ }
33352
+ return `${actor.kind} ${actor.id} (as ${serviceSubjectLabel(service)})`;
33353
+ }
33323
33354
  function eventLine(event) {
33324
33355
  const head = eventHead(event);
33325
- return `- #${event.seq} ${event.type} \xB7 ${event.actor.kind} ${event.actor.id} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
33356
+ return `- #${event.seq} ${event.type} \xB7 ${actorText(event.actor)} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
33326
33357
  }
33327
33358
  function childrenSummary(issue) {
33328
33359
  return [
@@ -33335,7 +33366,7 @@ function childrenSummary(issue) {
33335
33366
  function askSummary({ ask, replies }, graph) {
33336
33367
  const answer = ask.answer;
33337
33368
  const chain = replies.flatMap((reply) => [
33338
- `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
33369
+ `${reply.id} \xB7 ${actorText(reply.author)}`,
33339
33370
  `Body: ${reply.body}`
33340
33371
  ]);
33341
33372
  return [
@@ -33402,12 +33433,12 @@ function formatOpenAsksSummary(response, baseUrl) {
33402
33433
  }
33403
33434
  function commentSummary({ comment, replies }, graph) {
33404
33435
  const root = [
33405
- `${comment.id} \xB7 ${comment.author.kind} ${comment.author.id}`,
33436
+ `${comment.id} \xB7 ${actorText(comment.author)}`,
33406
33437
  ...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
33407
33438
  `Body: ${comment.body}`
33408
33439
  ];
33409
33440
  const chain = replies.flatMap((reply) => [
33410
- `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
33441
+ `${reply.id} \xB7 ${actorText(reply.author)}`,
33411
33442
  ...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
33412
33443
  `Body: ${reply.body}`
33413
33444
  ]);
@@ -33421,12 +33452,9 @@ function commentSummary({ comment, replies }, graph) {
33421
33452
  `);
33422
33453
  }
33423
33454
  function messageSummary({ message, replies }, graph) {
33424
- const root = [
33425
- `${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
33426
- `Body: ${message.body}`
33427
- ];
33455
+ const root = [`${message.id} \xB7 ${actorText(message.author)}`, `Body: ${message.body}`];
33428
33456
  const chain = replies.flatMap((reply) => [
33429
- `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
33457
+ `${reply.id} \xB7 ${actorText(reply.author)}`,
33430
33458
  `Body: ${reply.body}`
33431
33459
  ]);
33432
33460
  return [
@@ -33508,9 +33536,11 @@ async function executeDispatchTool(input) {
33508
33536
  const client = dispatchClient();
33509
33537
  const identity = await client.whoami();
33510
33538
  const owner = identity.kind === "agent" ? identity.owner : identity.login.toLowerCase();
33539
+ const service = identity.kind === "agent" ? identity.service ?? null : null;
33540
+ const unowned = "no owner, so issues you create without an assignee are unassigned (or inherit their parent's).";
33511
33541
  return {
33512
- text: owner === null ? `Session ${sessionId} runs under the shared token: no owner, so issues you create without an assignee are unassigned (or inherit their parent's).` : `Session ${sessionId} acts for ${owner}: issues you create without an assignee are assigned to ${owner}.`,
33513
- details: { session: sessionId, owner }
33542
+ text: owner !== null ? `Session ${sessionId} acts for ${owner}: issues you create without an assignee are assigned to ${owner}.` : service !== null ? `Session ${sessionId} runs as service ${serviceSubjectLabel(service)}: ${unowned}` : `Session ${sessionId} runs under the shared token: ${unowned}`,
33543
+ details: { session: sessionId, owner, service }
33514
33544
  };
33515
33545
  }
33516
33546
  const args = parsed.success ? parsed.data : ownerArguments.args;
@@ -33556,9 +33586,13 @@ async function executeDispatchTool(input) {
33556
33586
  ...Array.isArray(labels) ? { labels } : {},
33557
33587
  actor
33558
33588
  });
33559
- const adviceLines = renderAdvice(input.tool, created.key, created.advice, {
33560
- isPrimarySpec: spec !== undefined
33561
- });
33589
+ const componentGuidance = await architectureGuidance(client, created.project, created.components);
33590
+ const adviceLines = [
33591
+ ...renderAdvice(input.tool, created.key, created.advice, {
33592
+ isPrimarySpec: spec !== undefined
33593
+ }),
33594
+ ...componentGuidance === undefined ? [] : [componentGuidance]
33595
+ ];
33562
33596
  return {
33563
33597
  text: [
33564
33598
  `Created ${created.key}: ${created.title} ${notSubscribed(issueTopic(created.key))}`,
package/dist/legion.js CHANGED
@@ -16145,7 +16145,7 @@ import { logger } from "@oh-my-pi/pi-utils";
16145
16145
  // package.json
16146
16146
  var package_default = {
16147
16147
  name: "@sjawhar/pi-legion-envoy",
16148
- version: "1.57.1",
16148
+ version: "1.59.0",
16149
16149
  type: "module",
16150
16150
  omp: {
16151
16151
  extensions: [
@@ -28968,6 +28968,13 @@ function preprocess(fn, schema) {
28968
28968
  return pipe(transform(fn), schema);
28969
28969
  }
28970
28970
  // ../contracts/src/dispatch-api.ts
28971
+ function serviceSubjectLabel(subject) {
28972
+ const parts = subject.split(":");
28973
+ if (parts.length === 4 && parts[0] === "system" && parts[1] === "serviceaccount" && parts[2] !== "" && parts[3] !== "") {
28974
+ return `${parts[2]}/${parts[3]}`;
28975
+ }
28976
+ return subject;
28977
+ }
28971
28978
  var ASK_TURNS = ["human", "agent"];
28972
28979
  var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
28973
28980
  var DispatchEventSchema = object({
@@ -148,7 +148,7 @@ An ask goes to the issue's assignee: their Inbox opens on **Mine**, which lists
148
148
  ```ts
149
149
  dispatch_whoami({})
150
150
  ```
151
- It returns `details` `{ session, owner }`: `owner` is the lowercase login of the human whose personal token you run under, or `null` under the shared token. An issue you create without `assignee` goes to your owner; under the shared token it inherits its parent's assignee, or stays unassigned without a parent. If an issue you are asking on is unassigned and the answer matters, assign it to your owner (`PATCH /api/v1/issues/{key}` with `{"assignee": "<login>"}`; any authenticated caller may reassign, and an unlisted login is refused with `ASSIGNEE_NOT_ALLOWED`) or name in the question who should answer it. Never reassign an issue a human holds to get an answer faster: that is the human's call.
151
+ It returns `details` `{ session, owner, service }`: `owner` is the lowercase login of the human whose personal token you run under, or `null` under the shared token or a verified service token, in which case `service` is that token's subject (`system:serviceaccount:<namespace>:<name>`, `null` otherwise) and every write you make is rendered `(as <namespace>/<name>)` — the namespace is kept because every namespace has a `default` service account, and a subject that is not a Kubernetes one is shown whole. An issue you create without `assignee` goes to your owner; with no owner it inherits its parent's assignee, or stays unassigned without a parent. If an issue you are asking on is unassigned and the answer matters, assign it to your owner (`PATCH /api/v1/issues/{key}` with `{"assignee": "<login>"}`; any authenticated caller may reassign, and an unlisted login is refused with `ASSIGNEE_NOT_ALLOWED`) or name in the question who should answer it. Never reassign an issue a human holds to get an answer faster: that is the human's call.
152
152
 
153
153
  Architects create newly tracked child work with:
154
154
  ```ts
@@ -302,7 +302,9 @@ that follow from it:
302
302
  the question carries `dispatch://KEY/artifact/<slug>` (or `ref`), never just its filename. Text
303
303
  they must read to decide belongs in the spec in the first place — see [Artifacts](#artifacts).
304
304
 
305
- Attach an issue to the architecture components it changes, before decomposing it — children inherit the parent's attachment unless they choose their own, so attaching the root once classifies the whole tree:
305
+ ## Architecture components
306
+
307
+ Attach every architectural issue to the components it changes. Attach the root before decomposing it: children inherit the root's effective attachment unless they deliberately set their own, so one root attachment classifies the whole tree.
306
308
 
307
309
  ```
308
310
  dispatch_issue({ project: "CORE", title: "...", components: { mode: "explicit", ids: ["dispatch-server", "web"] } })
@@ -311,7 +313,9 @@ dispatch_issue_update({ issue: "CORE-13", components: { mode: "none", reason: "h
311
313
  dispatch_issue_update({ issue: "CORE-14", components: { mode: "inherit" } }) // back to the parent chain's attachment
312
314
  ```
313
315
 
314
- Component ids are the file names under the repository's `.dispatch/architecture/` (`web.md` → `web`); an id the model lacks, or an `external` component, is refused with `COMPONENTS_INPUT`. A closed issue can be classified without reopening. Two rules: declare and attach before decomposing work, and change code and its architecture description (`.dispatch/architecture/<id>.md`) in the same review — the tree at `GET /api/v1/projects/{key}/architecture` counts every issue whose effective set names a component or anything it contains.
316
+ No native Dispatch tool lists component ids. Read the configured model with `GET /api/v1/projects/{key}/architecture` and use each non-external `components[].id`; when its source repository is your checkout, those ids are the file names under `.dispatch/architecture/` (`web.md` → `web`). An id absent from the model, or an `external` component, is refused with `COMPONENTS_INPUT`.
317
+
318
+ Use `mode: "none"` with a concrete reason only when the work is genuinely non-architectural (for example hiring, process, or operations work). A closed issue can be classified without reopening. Change code and its architecture description (`.dispatch/architecture/<id>.md`) in the same review.
315
319
 
316
320
  Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
317
321
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "1.57.1",
3
+ "version": "1.59.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [