@sjawhar/pi-legion-envoy 1.57.0 → 1.58.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({
@@ -30052,7 +30059,7 @@ var dispatchToolSpecs = [
30052
30059
  },
30053
30060
  description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited.",
30054
30061
  arguments: (z) => ({
30055
- ask: z.string().describe("Ask id to edit."),
30062
+ ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
30056
30063
  question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
30057
30064
  options: z.array(z.object({
30058
30065
  label: z.string().describe("Selectable option label."),
@@ -30078,7 +30085,7 @@ var dispatchToolSpecs = [
30078
30085
  },
30079
30086
  description: "Retract an open question that is moot or resolve one after finding the answer. This closes the question without answering it.",
30080
30087
  arguments: (z) => ({
30081
- ask: z.string().describe("Ask id to close."),
30088
+ ask: z.string().describe("Ask id (uuid) to close; an 8+ hex prefix unique among this session's own open asks " + "works too."),
30082
30089
  kind: z.enum(["retracted", "resolved"]).describe("Whether the ask is retracted or self-resolved."),
30083
30090
  reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
30084
30091
  })
@@ -30097,7 +30104,7 @@ var dispatchToolSpecs = [
30097
30104
  example: { ask: "01234567-0000-4000-8000-000000000001", action: "follow" },
30098
30105
  description: "Follow or unfollow an ask. Every session that opens or replies to an ask follows it: its answer, " + "edits, resolution, and replies reach that session directly. Unfollow to stop; follow to rejoin or " + "to hear an ask you never wrote to. Whole-issue subscription is separate: envoy_subscribe " + "notifications.dispatch.issue.<KEY>.>",
30099
30106
  arguments: (z) => ({
30100
- ask: z.string().describe("Full ask id (uuid)."),
30107
+ ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
30101
30108
  action: z.enum(["follow", "unfollow"]).describe("follow | unfollow")
30102
30109
  }),
30103
30110
  strict: true
@@ -32863,24 +32870,51 @@ function messageIdOf(value) {
32863
32870
  return id === undefined ? undefined : normalizeUUID(id);
32864
32871
  }
32865
32872
  var idPrefixPattern = /^[0-9a-f][0-9a-f-]{7,}$/i;
32866
- async function resolveIdPrefix(tool, kind, ref, list) {
32867
- const fullID = normalizeUUID(ref.id);
32873
+ function refOwnerName(ref) {
32874
+ return ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
32875
+ }
32876
+ async function resolveIdPrefix(tool, kind, id, ownerName, list) {
32877
+ const fullID = normalizeUUID(id);
32868
32878
  if (fullID !== undefined)
32869
32879
  return fullID;
32870
- const ownerName = ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
32871
- if (!idPrefixPattern.test(ref.id)) {
32880
+ if (!idPrefixPattern.test(id)) {
32872
32881
  throw new ToolInputError(tool, [
32873
- `${kind} id ${ref.id} must be a full uuid or a prefix of at least 8 hex characters`
32882
+ `${kind} id ${id} must be a full uuid or a prefix of at least 8 hex characters`
32874
32883
  ]);
32875
32884
  }
32876
- const prefix = ref.id.toLowerCase();
32885
+ const prefix = id.toLowerCase();
32877
32886
  const matches = (await list()).filter((item) => item.id.toLowerCase().startsWith(prefix));
32878
32887
  if (matches.length === 1 && matches[0] !== undefined)
32879
32888
  return matches[0].id;
32880
32889
  throw new ToolInputError(tool, [
32881
- matches.length === 0 ? `${kind} id ${ref.id} matches none of the ${kind}s on ${ownerName}; use the full id` : `${kind} id ${ref.id} matches ${matches.length} ${kind}s on ${ownerName}; use the full id`
32890
+ matches.length === 0 ? `${kind} id ${id} matches none of the ${kind}s on ${ownerName}; use the full id` : `${kind} id ${id} matches ${matches.length} ${kind}s on ${ownerName}; use the full id`
32882
32891
  ]);
32883
32892
  }
32893
+ var askIdShapeProblem = "ask ids are uuids (a prefix of at least 8 hex characters works)";
32894
+ async function sessionOpenAsks(client, sessionId) {
32895
+ const session = sessionId?.trim();
32896
+ if (!session)
32897
+ throw new Error("host session id is required to read this session's open asks");
32898
+ return (await client.openAsks(session)).asks;
32899
+ }
32900
+ function openAskHints(asks) {
32901
+ if (asks.length === 0)
32902
+ return "you have no open asks";
32903
+ const hints = asks.slice(0, askHintLimit).map((ask) => `${ask.id.slice(0, 8)} \u2014 ${textHead(ask.question)}`);
32904
+ return `your open asks: ${hints.join("; ")}`;
32905
+ }
32906
+ async function resolveAskArgument(tool, args, client, sessionId) {
32907
+ const id = askId(args);
32908
+ if (normalizeUUID(id) === undefined && !idPrefixPattern.test(id)) {
32909
+ const asks = await sessionOpenAsks(client, sessionId).catch(() => {
32910
+ return;
32911
+ });
32912
+ throw new ToolInputError(tool, [
32913
+ asks === undefined ? askIdShapeProblem : `${askIdShapeProblem}; ${openAskHints(asks)}`
32914
+ ]);
32915
+ }
32916
+ return resolveIdPrefix(tool, "ask", id, "this session", () => sessionOpenAsks(client, sessionId));
32917
+ }
32884
32918
  function messageInReplyTo(args) {
32885
32919
  const inReplyTo = optionalString(args, "in_reply_to");
32886
32920
  return inReplyTo === undefined ? undefined : messageIdOf(inReplyTo);
@@ -32899,7 +32933,8 @@ function argumentProblems(tool, args) {
32899
32933
  break;
32900
32934
  }
32901
32935
  case "dispatch_edit_ask":
32902
- case "dispatch_resolve_ask": {
32936
+ case "dispatch_resolve_ask":
32937
+ case "dispatch_follow": {
32903
32938
  const ask = optionalString(args, "ask");
32904
32939
  if (ask?.startsWith("dispatch://") && parseDispatchRef(ask)?.kind !== "ask") {
32905
32940
  problems.push(askIdProblem);
@@ -33292,9 +33327,16 @@ function eventHead(event) {
33292
33327
  return;
33293
33328
  }
33294
33329
  }
33330
+ function actorText(actor) {
33331
+ const service = actor.kind === "session" ? actor.service : undefined;
33332
+ if (service === undefined) {
33333
+ return `${actor.kind} ${actor.id}`;
33334
+ }
33335
+ return `${actor.kind} ${actor.id} (as ${serviceSubjectLabel(service)})`;
33336
+ }
33295
33337
  function eventLine(event) {
33296
33338
  const head = eventHead(event);
33297
- return `- #${event.seq} ${event.type} \xB7 ${event.actor.kind} ${event.actor.id} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
33339
+ return `- #${event.seq} ${event.type} \xB7 ${actorText(event.actor)} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
33298
33340
  }
33299
33341
  function childrenSummary(issue) {
33300
33342
  return [
@@ -33307,7 +33349,7 @@ function childrenSummary(issue) {
33307
33349
  function askSummary({ ask, replies }, graph) {
33308
33350
  const answer = ask.answer;
33309
33351
  const chain = replies.flatMap((reply) => [
33310
- `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
33352
+ `${reply.id} \xB7 ${actorText(reply.author)}`,
33311
33353
  `Body: ${reply.body}`
33312
33354
  ]);
33313
33355
  return [
@@ -33374,12 +33416,12 @@ function formatOpenAsksSummary(response, baseUrl) {
33374
33416
  }
33375
33417
  function commentSummary({ comment, replies }, graph) {
33376
33418
  const root = [
33377
- `${comment.id} \xB7 ${comment.author.kind} ${comment.author.id}`,
33419
+ `${comment.id} \xB7 ${actorText(comment.author)}`,
33378
33420
  ...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
33379
33421
  `Body: ${comment.body}`
33380
33422
  ];
33381
33423
  const chain = replies.flatMap((reply) => [
33382
- `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
33424
+ `${reply.id} \xB7 ${actorText(reply.author)}`,
33383
33425
  ...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
33384
33426
  `Body: ${reply.body}`
33385
33427
  ]);
@@ -33393,12 +33435,9 @@ function commentSummary({ comment, replies }, graph) {
33393
33435
  `);
33394
33436
  }
33395
33437
  function messageSummary({ message, replies }, graph) {
33396
- const root = [
33397
- `${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
33398
- `Body: ${message.body}`
33399
- ];
33438
+ const root = [`${message.id} \xB7 ${actorText(message.author)}`, `Body: ${message.body}`];
33400
33439
  const chain = replies.flatMap((reply) => [
33401
- `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
33440
+ `${reply.id} \xB7 ${actorText(reply.author)}`,
33402
33441
  `Body: ${reply.body}`
33403
33442
  ]);
33404
33443
  return [
@@ -33480,9 +33519,11 @@ async function executeDispatchTool(input) {
33480
33519
  const client = dispatchClient();
33481
33520
  const identity = await client.whoami();
33482
33521
  const owner = identity.kind === "agent" ? identity.owner : identity.login.toLowerCase();
33522
+ const service = identity.kind === "agent" ? identity.service ?? null : null;
33523
+ const unowned = "no owner, so issues you create without an assignee are unassigned (or inherit their parent's).";
33483
33524
  return {
33484
- 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}.`,
33485
- details: { session: sessionId, owner }
33525
+ 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}`,
33526
+ details: { session: sessionId, owner, service }
33486
33527
  };
33487
33528
  }
33488
33529
  const args = parsed.success ? parsed.data : ownerArguments.args;
@@ -33693,7 +33734,8 @@ async function executeDispatchTool(input) {
33693
33734
  }
33694
33735
  case "dispatch_resolve_ask": {
33695
33736
  const kind = stringArg(args, "kind");
33696
- const ask = await client.resolveAsk(stringArg(args, "ask"), {
33737
+ const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
33738
+ const ask = await client.resolveAsk(id, {
33697
33739
  kind,
33698
33740
  reason: stringArg(args, "reason"),
33699
33741
  actor
@@ -33712,11 +33754,11 @@ async function executeDispatchTool(input) {
33712
33754
  let document;
33713
33755
  if (ref?.owner.kind === "issue") {
33714
33756
  const issueKey = ref.owner.issue;
33715
- id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getComments(issueKey));
33757
+ id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getComments(issueKey));
33716
33758
  } else if (ref !== null) {
33717
33759
  const artifact = (await resolveArtifact(client, ref.owner, ref.artifact)).artifact;
33718
33760
  document = artifact;
33719
- id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getArtifactComments(artifact.id));
33761
+ id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getArtifactComments(artifact.id));
33720
33762
  }
33721
33763
  const comment = await client.resolveComment(id, actor);
33722
33764
  const { label, details } = await commentOwnerResult(client, comment, document);
@@ -33760,7 +33802,8 @@ ${followsAsk(askOwner)}`,
33760
33802
  const options = args.options;
33761
33803
  const multiple = optionalBoolean(args, "multiple");
33762
33804
  const urgency = askUrgency(args);
33763
- const ask = await client.editAsk(askId(args), {
33805
+ const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
33806
+ const ask = await client.editAsk(id, {
33764
33807
  ...question === undefined ? {} : { question },
33765
33808
  ...Array.isArray(options) ? { options } : {},
33766
33809
  ...multiple === undefined ? {} : { multiple },
@@ -33987,7 +34030,7 @@ ${trailer.join(`
33987
34030
  const sessionId = input.sessionId?.trim();
33988
34031
  if (!sessionId)
33989
34032
  throw new Error("host session id is required for dispatch_follow");
33990
- const ask = askId(args);
34033
+ const ask = await resolveAskArgument(input.tool, args, client, sessionId);
33991
34034
  const action = stringArg(args, "action");
33992
34035
  if (action === "unfollow") {
33993
34036
  await client.unfollowAsk(ask, sessionId, actor);
@@ -34003,7 +34046,7 @@ ${trailer.join(`
34003
34046
  case "dispatch_read": {
34004
34047
  if (ownerArguments.ref?.kind === "ask") {
34005
34048
  const ref = ownerArguments.ref;
34006
- const id = await resolveIdPrefix(input.tool, "ask", ref, async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
34049
+ const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
34007
34050
  const askRead = await client.getAsk(id);
34008
34051
  const askRef = refTarget(ref, "ask", id);
34009
34052
  return {
@@ -34013,7 +34056,7 @@ ${trailer.join(`
34013
34056
  }
34014
34057
  if (ownerArguments.ref?.kind === "comment") {
34015
34058
  const ref = ownerArguments.ref;
34016
- const id = await resolveIdPrefix(input.tool, "comment", ref, async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
34059
+ const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
34017
34060
  const comment = await client.getComment(id);
34018
34061
  const commentRef = refTarget(ref, "comment", id);
34019
34062
  return {
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.0",
16148
+ version: "1.58.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({
@@ -29371,7 +29378,7 @@ var dispatchToolSpecs = [
29371
29378
  },
29372
29379
  description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited.",
29373
29380
  arguments: (z) => ({
29374
- ask: z.string().describe("Ask id to edit."),
29381
+ ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
29375
29382
  question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
29376
29383
  options: z.array(z.object({
29377
29384
  label: z.string().describe("Selectable option label."),
@@ -29397,7 +29404,7 @@ var dispatchToolSpecs = [
29397
29404
  },
29398
29405
  description: "Retract an open question that is moot or resolve one after finding the answer. This closes the question without answering it.",
29399
29406
  arguments: (z) => ({
29400
- ask: z.string().describe("Ask id to close."),
29407
+ ask: z.string().describe("Ask id (uuid) to close; an 8+ hex prefix unique among this session's own open asks " + "works too."),
29401
29408
  kind: z.enum(["retracted", "resolved"]).describe("Whether the ask is retracted or self-resolved."),
29402
29409
  reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
29403
29410
  })
@@ -29416,7 +29423,7 @@ var dispatchToolSpecs = [
29416
29423
  example: { ask: "01234567-0000-4000-8000-000000000001", action: "follow" },
29417
29424
  description: "Follow or unfollow an ask. Every session that opens or replies to an ask follows it: its answer, " + "edits, resolution, and replies reach that session directly. Unfollow to stop; follow to rejoin or " + "to hear an ask you never wrote to. Whole-issue subscription is separate: envoy_subscribe " + "notifications.dispatch.issue.<KEY>.>",
29418
29425
  arguments: (z) => ({
29419
- ask: z.string().describe("Full ask id (uuid)."),
29426
+ ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
29420
29427
  action: z.enum(["follow", "unfollow"]).describe("follow | unfollow")
29421
29428
  }),
29422
29429
  strict: true
@@ -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
@@ -708,7 +708,9 @@ the host tells you once per ask. Leave a thread you no longer need, or rejoin on
708
708
  dispatch_follow({ ask, action: "follow" | "unfollow" })
709
709
  ```
710
710
 
711
- `ask` is the full ask id or a `dispatch://KEY/ask/<id>` reference. A human may also remove you from the ask card; either way you are
711
+ `ask` is the full ask id or a `dispatch://KEY/ask/<id>` reference; `dispatch_follow`, `dispatch_edit_ask`, and
712
+ `dispatch_resolve_ask` also take an 8+ hex prefix that is unique among your own open asks, and refuse anything shorter by naming
713
+ those asks. A human may also remove you from the ask card; either way you are
712
714
  told with an `ask.follower_removed` notice, and a human adding you arrives as `ask.follower_added`.
713
715
 
714
716
  No write subscribes you to an issue or document. Following covers your own asks and the threads you joined; everything else on the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "1.57.0",
3
+ "version": "1.58.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [