@sjawhar/pi-legion-envoy 0.23.0 → 0.24.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/README.md CHANGED
@@ -98,7 +98,9 @@ Configure the shared `envoy.json` with:
98
98
 
99
99
  The user file is `~/.config/opencode/envoy.json`; a
100
100
  `<cwd>/.opencode/envoy.json` file shallow-merges over it. `DISPATCH_URL` and
101
- `DISPATCH_TOKEN` override the file values for one process. Invalid configuration,
101
+ `DISPATCH_TOKEN` override the file values for one process. Omitting
102
+ `dispatch.serverUrl` while `dispatch.enabled` is true targets
103
+ `http://localhost:8766`, the Go server's listen address. Invalid configuration,
102
104
  an invalid URL, or an empty token leaves the nine tools unavailable and reports
103
105
  the source of the error.
104
106
 
package/dist/envoy.js CHANGED
@@ -29677,6 +29677,8 @@ var CommentEventPayloadSchema = object({
29677
29677
  artifact_name: string2().optional(),
29678
29678
  body: string2().optional(),
29679
29679
  reply_to: string2().nullish(),
29680
+ ask_id: string2().nullish(),
29681
+ ask_question: string2().optional(),
29680
29682
  anchor: object({ quote: string2().optional() }).nullish(),
29681
29683
  suggestion: object({ replace_with: string2().optional() }).nullish()
29682
29684
  });
@@ -29727,14 +29729,15 @@ var dispatchToolSpecs = [
29727
29729
  },
29728
29730
  {
29729
29731
  name: "dispatch_comment",
29730
- description: "Add review feedback to an issue or document quote. Do not use it for an exact replacement; use " + `dispatch_suggest instead. Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
29732
+ description: "Add review feedback to an issue or document quote, or reply to a question asked with dispatch_ask. " + "Do not use it for an exact replacement; use " + `dispatch_suggest instead. Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
29731
29733
  arguments: (z) => ({
29732
29734
  issue: z.string().describe(ISSUE_REFERENCE),
29733
29735
  artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
29734
29736
  quote: z.string().describe("Optional exact quoted document text.").optional(),
29735
29737
  occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
29736
29738
  body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
29737
- reply_to: z.string().describe("Optional comment id to reply to.").optional()
29739
+ reply_to: z.string().describe("Optional comment id to reply to.").optional(),
29740
+ reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional()
29738
29741
  })
29739
29742
  },
29740
29743
  {
@@ -30809,6 +30812,12 @@ function dispatchPayload(event) {
30809
30812
  const parsed = schema.safeParse(event.payload);
30810
30813
  return parsed.success ? parsed.data : event.payload;
30811
30814
  }
30815
+ function dispatchAskQuestion(event) {
30816
+ if (event.type !== "comment.created")
30817
+ return;
30818
+ const parsed = CommentEventPayloadSchema.safeParse(event.payload);
30819
+ return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
30820
+ }
30812
30821
  function parseDispatchFrame(rawPayload) {
30813
30822
  let value;
30814
30823
  try {
@@ -30858,6 +30867,7 @@ function renderInbound(raw, sessionID, subject) {
30858
30867
  }
30859
30868
  let dispatchEvent;
30860
30869
  let dispatchIssue;
30870
+ let askQuestion;
30861
30871
  const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
30862
30872
  if (envelope.source === "dispatch") {
30863
30873
  if (envelope.payload === undefined) {
@@ -30871,6 +30881,7 @@ function renderInbound(raw, sessionID, subject) {
30871
30881
  if (frame.event.actor.kind === "session" && frame.event.actor.id === sessionID) {
30872
30882
  return { skip: true, content: "", envelope };
30873
30883
  }
30884
+ askQuestion = dispatchAskQuestion(frame.event);
30874
30885
  dispatchEvent = {
30875
30886
  issue_key: frame.event.issue_key,
30876
30887
  type: frame.event.type,
@@ -30918,7 +30929,7 @@ ${envelope.payload ?? ""}`;
30918
30929
  ...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
30919
30930
  ...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
30920
30931
  ...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
30921
- ...envelope.in_reply_to === undefined ? {} : { re: envelope.in_reply_to },
30932
+ ...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
30922
30933
  ...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
30923
30934
  ...reply === undefined ? {} : { reply_with: reply },
30924
30935
  ...role === undefined ? {} : {
@@ -31471,8 +31482,12 @@ function childrenSummary(issue) {
31471
31482
  ].join(`
31472
31483
  `);
31473
31484
  }
31474
- function askSummary(ask) {
31485
+ function askSummary({ ask, replies }) {
31475
31486
  const answer = ask.answer;
31487
+ const chain = replies.flatMap((reply) => [
31488
+ `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
31489
+ `Body: ${reply.body}`
31490
+ ]);
31476
31491
  return [
31477
31492
  `Question: ${ask.question}`,
31478
31493
  "Options:",
@@ -31483,7 +31498,9 @@ function askSummary(ask) {
31483
31498
  `- By: ${answer.user}`,
31484
31499
  `- Selected: ${answer.selected.length === 0 ? "none" : answer.selected.join(", ")}`,
31485
31500
  ...answer.text === null ? [] : [`- Text: ${answer.text}`]
31486
- ]
31501
+ ],
31502
+ "Replies:",
31503
+ ...chain.length === 0 ? ["- none"] : chain
31487
31504
  ].join(`
31488
31505
  `);
31489
31506
  }
@@ -31582,10 +31599,15 @@ async function executeDispatchTool(input) {
31582
31599
  const resolved = artifactReference ? await resolveArtifact(client, issue(), artifactReference) : undefined;
31583
31600
  const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
31584
31601
  const replyTo = optionalString(args, "reply_to");
31602
+ const replyToAsk = optionalString(args, "reply_to_ask");
31603
+ if (replyTo !== undefined && replyToAsk !== undefined) {
31604
+ throw new Error("reply_to and reply_to_ask cannot both be set");
31605
+ }
31585
31606
  const comment = await client.comment(issue(), {
31586
31607
  body: stringArg(args, "body"),
31587
31608
  ...anchored === undefined ? {} : { anchor: anchored },
31588
31609
  ...replyTo === undefined ? {} : { reply_to: replyTo },
31610
+ ...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
31589
31611
  actor
31590
31612
  });
31591
31613
  return {
@@ -31694,10 +31716,13 @@ Open anchored asks/comments: ${marks.join(", ")}`,
31694
31716
  }
31695
31717
  case "dispatch_read": {
31696
31718
  if (issueArguments.ref?.kind === "ask") {
31697
- const ask = await client.getAsk(issueArguments.ref.id);
31719
+ const askRead = await client.getAsk(issueArguments.ref.id);
31698
31720
  return {
31699
- text: askSummary(ask),
31700
- details: { issue: ask.issue_key, topic: dispatchIssueSubject(ask.issue_key, ">") }
31721
+ text: askSummary(askRead),
31722
+ details: {
31723
+ issue: askRead.ask.issue_key,
31724
+ topic: dispatchIssueSubject(askRead.ask.issue_key, ">")
31725
+ }
31701
31726
  };
31702
31727
  }
31703
31728
  if (issueArguments.ref?.kind === "comment") {
package/dist/legion.js CHANGED
@@ -29675,6 +29675,8 @@ var CommentEventPayloadSchema = object({
29675
29675
  artifact_name: string2().optional(),
29676
29676
  body: string2().optional(),
29677
29677
  reply_to: string2().nullish(),
29678
+ ask_id: string2().nullish(),
29679
+ ask_question: string2().optional(),
29678
29680
  anchor: object({ quote: string2().optional() }).nullish(),
29679
29681
  suggestion: object({ replace_with: string2().optional() }).nullish()
29680
29682
  });
@@ -29725,14 +29727,15 @@ var dispatchToolSpecs = [
29725
29727
  },
29726
29728
  {
29727
29729
  name: "dispatch_comment",
29728
- description: "Add review feedback to an issue or document quote. Do not use it for an exact replacement; use " + `dispatch_suggest instead. Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
29730
+ description: "Add review feedback to an issue or document quote, or reply to a question asked with dispatch_ask. " + "Do not use it for an exact replacement; use " + `dispatch_suggest instead. Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
29729
29731
  arguments: (z) => ({
29730
29732
  issue: z.string().describe(ISSUE_REFERENCE),
29731
29733
  artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
29732
29734
  quote: z.string().describe("Optional exact quoted document text.").optional(),
29733
29735
  occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
29734
29736
  body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
29735
- reply_to: z.string().describe("Optional comment id to reply to.").optional()
29737
+ reply_to: z.string().describe("Optional comment id to reply to.").optional(),
29738
+ reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional()
29736
29739
  })
29737
29740
  },
29738
29741
  {
@@ -31301,6 +31304,12 @@ function dispatchPayload(event) {
31301
31304
  const parsed = schema.safeParse(event.payload);
31302
31305
  return parsed.success ? parsed.data : event.payload;
31303
31306
  }
31307
+ function dispatchAskQuestion(event) {
31308
+ if (event.type !== "comment.created")
31309
+ return;
31310
+ const parsed = CommentEventPayloadSchema.safeParse(event.payload);
31311
+ return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
31312
+ }
31304
31313
  function parseDispatchFrame(rawPayload) {
31305
31314
  let value;
31306
31315
  try {
@@ -31350,6 +31359,7 @@ function renderInbound(raw, sessionID, subject) {
31350
31359
  }
31351
31360
  let dispatchEvent;
31352
31361
  let dispatchIssue;
31362
+ let askQuestion;
31353
31363
  const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
31354
31364
  if (envelope.source === "dispatch") {
31355
31365
  if (envelope.payload === undefined) {
@@ -31363,6 +31373,7 @@ function renderInbound(raw, sessionID, subject) {
31363
31373
  if (frame.event.actor.kind === "session" && frame.event.actor.id === sessionID) {
31364
31374
  return { skip: true, content: "", envelope };
31365
31375
  }
31376
+ askQuestion = dispatchAskQuestion(frame.event);
31366
31377
  dispatchEvent = {
31367
31378
  issue_key: frame.event.issue_key,
31368
31379
  type: frame.event.type,
@@ -31410,7 +31421,7 @@ ${envelope.payload ?? ""}`;
31410
31421
  ...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
31411
31422
  ...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
31412
31423
  ...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
31413
- ...envelope.in_reply_to === undefined ? {} : { re: envelope.in_reply_to },
31424
+ ...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
31414
31425
  ...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
31415
31426
  ...reply === undefined ? {} : { reply_with: reply },
31416
31427
  ...role === undefined ? {} : {
@@ -31956,8 +31967,12 @@ function childrenSummary(issue) {
31956
31967
  ].join(`
31957
31968
  `);
31958
31969
  }
31959
- function askSummary(ask) {
31970
+ function askSummary({ ask, replies }) {
31960
31971
  const answer = ask.answer;
31972
+ const chain = replies.flatMap((reply) => [
31973
+ `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
31974
+ `Body: ${reply.body}`
31975
+ ]);
31961
31976
  return [
31962
31977
  `Question: ${ask.question}`,
31963
31978
  "Options:",
@@ -31968,7 +31983,9 @@ function askSummary(ask) {
31968
31983
  `- By: ${answer.user}`,
31969
31984
  `- Selected: ${answer.selected.length === 0 ? "none" : answer.selected.join(", ")}`,
31970
31985
  ...answer.text === null ? [] : [`- Text: ${answer.text}`]
31971
- ]
31986
+ ],
31987
+ "Replies:",
31988
+ ...chain.length === 0 ? ["- none"] : chain
31972
31989
  ].join(`
31973
31990
  `);
31974
31991
  }
@@ -32067,10 +32084,15 @@ async function executeDispatchTool(input) {
32067
32084
  const resolved = artifactReference ? await resolveArtifact(client, issue(), artifactReference) : undefined;
32068
32085
  const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
32069
32086
  const replyTo = optionalString(args, "reply_to");
32087
+ const replyToAsk = optionalString(args, "reply_to_ask");
32088
+ if (replyTo !== undefined && replyToAsk !== undefined) {
32089
+ throw new Error("reply_to and reply_to_ask cannot both be set");
32090
+ }
32070
32091
  const comment = await client.comment(issue(), {
32071
32092
  body: stringArg(args, "body"),
32072
32093
  ...anchored === undefined ? {} : { anchor: anchored },
32073
32094
  ...replyTo === undefined ? {} : { reply_to: replyTo },
32095
+ ...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
32074
32096
  actor
32075
32097
  });
32076
32098
  return {
@@ -32179,10 +32201,13 @@ Open anchored asks/comments: ${marks.join(", ")}`,
32179
32201
  }
32180
32202
  case "dispatch_read": {
32181
32203
  if (issueArguments.ref?.kind === "ask") {
32182
- const ask = await client.getAsk(issueArguments.ref.id);
32204
+ const askRead = await client.getAsk(issueArguments.ref.id);
32183
32205
  return {
32184
- text: askSummary(ask),
32185
- details: { issue: ask.issue_key, topic: dispatchIssueSubject(ask.issue_key, ">") }
32206
+ text: askSummary(askRead),
32207
+ details: {
32208
+ issue: askRead.ask.issue_key,
32209
+ topic: dispatchIssueSubject(askRead.ask.issue_key, ">")
32210
+ }
32186
32211
  };
32187
32212
  }
32188
32213
  if (issueArguments.ref?.kind === "comment") {
@@ -46,6 +46,12 @@ Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occ
46
46
  zero-based and selects a repeated quote. An anchor whose quote disappears becomes orphaned but
47
47
  remains readable against its original document version. An ask stays open until a human answers;
48
48
  you cannot withdraw it. Ask once and well. The host steers you when a human answers.
49
+
50
+ An ask is a thread, not a dead end: a human can reply to it before or after answering, and you
51
+ (the asker) can reply too — e.g. acknowledging a clarifying question, or following up after the
52
+ answer. Use `reply_to_ask` on `dispatch_comment` to reply under your own ask; it is mutually
53
+ exclusive with `reply_to`.
54
+
49
55
  ## The spec is where narrative goes
50
56
 
51
57
  Read the current document before changing it:
@@ -85,11 +91,12 @@ message.
85
91
  Add feedback with:
86
92
 
87
93
  ```ts
88
- dispatch_comment({ issue, artifact?, quote?, occurrence?, body, reply_to? })
94
+ dispatch_comment({ issue, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
89
95
  ```
90
96
 
91
97
  It returns `details` `{ issue, topic, comment }`. `quote` requires `artifact`; omit both for a
92
- floating issue comment. Use `reply_to` to continue a comment.
98
+ floating issue comment. Use `reply_to` to continue a comment thread; use `reply_to_ask` to reply
99
+ directly under a question asked with `dispatch_ask`. The two are mutually exclusive.
93
100
 
94
101
  Propose an exact replacement instead of describing it:
95
102
 
@@ -149,9 +156,9 @@ dispatch_read({ issue?, ref? })
149
156
  ```
150
157
 
151
158
  With an issue ref, it returns the issue summary, open asks, and recent events with `details`
152
- `{ issue }`. With an ask ref, it returns that ask's question, options, state, and answer. With a
153
- comment ref, it returns that comment and its quoted reply chain. Use `dispatch_doc_read` for
154
- document contents.
159
+ `{ issue }`. With an ask ref, it returns that ask's question, options, state, answer, and its
160
+ reply thread. With a comment ref, it returns that comment and its quoted reply chain. Use
161
+ `dispatch_doc_read` for document contents.
155
162
 
156
163
  ## References
157
164
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [