@sjawhar/pi-legion-envoy 0.23.0 → 0.25.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 ? {} : {
@@ -31151,6 +31162,9 @@ class DispatchClient {
31151
31162
  async issue(input) {
31152
31163
  return this.#json("POST", ["api", "v1", "issues"], input);
31153
31164
  }
31165
+ async listIssues(options = {}) {
31166
+ return this.#json("GET", ["api", "v1", "issues"], undefined, options);
31167
+ }
31154
31168
  async getIssue(issue) {
31155
31169
  return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue)]);
31156
31170
  }
@@ -31294,8 +31308,11 @@ class DispatchClient {
31294
31308
  #url(path, query) {
31295
31309
  const url = new URL(`${path.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
31296
31310
  if (query) {
31297
- for (const [name, value] of Object.entries(query))
31298
- url.searchParams.set(name, String(value));
31311
+ for (const [name, value] of Object.entries(query)) {
31312
+ if (typeof value === "string" || typeof value === "number") {
31313
+ url.searchParams.set(name, String(value));
31314
+ }
31315
+ }
31299
31316
  }
31300
31317
  return url.toString();
31301
31318
  }
@@ -31471,8 +31488,12 @@ function childrenSummary(issue) {
31471
31488
  ].join(`
31472
31489
  `);
31473
31490
  }
31474
- function askSummary(ask) {
31491
+ function askSummary({ ask, replies }) {
31475
31492
  const answer = ask.answer;
31493
+ const chain = replies.flatMap((reply) => [
31494
+ `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
31495
+ `Body: ${reply.body}`
31496
+ ]);
31476
31497
  return [
31477
31498
  `Question: ${ask.question}`,
31478
31499
  "Options:",
@@ -31483,7 +31504,9 @@ function askSummary(ask) {
31483
31504
  `- By: ${answer.user}`,
31484
31505
  `- Selected: ${answer.selected.length === 0 ? "none" : answer.selected.join(", ")}`,
31485
31506
  ...answer.text === null ? [] : [`- Text: ${answer.text}`]
31486
- ]
31507
+ ],
31508
+ "Replies:",
31509
+ ...chain.length === 0 ? ["- none"] : chain
31487
31510
  ].join(`
31488
31511
  `);
31489
31512
  }
@@ -31582,10 +31605,15 @@ async function executeDispatchTool(input) {
31582
31605
  const resolved = artifactReference ? await resolveArtifact(client, issue(), artifactReference) : undefined;
31583
31606
  const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
31584
31607
  const replyTo = optionalString(args, "reply_to");
31608
+ const replyToAsk = optionalString(args, "reply_to_ask");
31609
+ if (replyTo !== undefined && replyToAsk !== undefined) {
31610
+ throw new Error("reply_to and reply_to_ask cannot both be set");
31611
+ }
31585
31612
  const comment = await client.comment(issue(), {
31586
31613
  body: stringArg(args, "body"),
31587
31614
  ...anchored === undefined ? {} : { anchor: anchored },
31588
31615
  ...replyTo === undefined ? {} : { reply_to: replyTo },
31616
+ ...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
31589
31617
  actor
31590
31618
  });
31591
31619
  return {
@@ -31694,10 +31722,13 @@ Open anchored asks/comments: ${marks.join(", ")}`,
31694
31722
  }
31695
31723
  case "dispatch_read": {
31696
31724
  if (issueArguments.ref?.kind === "ask") {
31697
- const ask = await client.getAsk(issueArguments.ref.id);
31725
+ const askRead = await client.getAsk(issueArguments.ref.id);
31698
31726
  return {
31699
- text: askSummary(ask),
31700
- details: { issue: ask.issue_key, topic: dispatchIssueSubject(ask.issue_key, ">") }
31727
+ text: askSummary(askRead),
31728
+ details: {
31729
+ issue: askRead.ask.issue_key,
31730
+ topic: dispatchIssueSubject(askRead.ask.issue_key, ">")
31731
+ }
31701
31732
  };
31702
31733
  }
31703
31734
  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 ? {} : {
@@ -31636,6 +31647,9 @@ class DispatchClient {
31636
31647
  async issue(input) {
31637
31648
  return this.#json("POST", ["api", "v1", "issues"], input);
31638
31649
  }
31650
+ async listIssues(options = {}) {
31651
+ return this.#json("GET", ["api", "v1", "issues"], undefined, options);
31652
+ }
31639
31653
  async getIssue(issue) {
31640
31654
  return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue)]);
31641
31655
  }
@@ -31779,8 +31793,11 @@ class DispatchClient {
31779
31793
  #url(path, query) {
31780
31794
  const url = new URL(`${path.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
31781
31795
  if (query) {
31782
- for (const [name, value] of Object.entries(query))
31783
- url.searchParams.set(name, String(value));
31796
+ for (const [name, value] of Object.entries(query)) {
31797
+ if (typeof value === "string" || typeof value === "number") {
31798
+ url.searchParams.set(name, String(value));
31799
+ }
31800
+ }
31784
31801
  }
31785
31802
  return url.toString();
31786
31803
  }
@@ -31956,8 +31973,12 @@ function childrenSummary(issue) {
31956
31973
  ].join(`
31957
31974
  `);
31958
31975
  }
31959
- function askSummary(ask) {
31976
+ function askSummary({ ask, replies }) {
31960
31977
  const answer = ask.answer;
31978
+ const chain = replies.flatMap((reply) => [
31979
+ `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
31980
+ `Body: ${reply.body}`
31981
+ ]);
31961
31982
  return [
31962
31983
  `Question: ${ask.question}`,
31963
31984
  "Options:",
@@ -31968,7 +31989,9 @@ function askSummary(ask) {
31968
31989
  `- By: ${answer.user}`,
31969
31990
  `- Selected: ${answer.selected.length === 0 ? "none" : answer.selected.join(", ")}`,
31970
31991
  ...answer.text === null ? [] : [`- Text: ${answer.text}`]
31971
- ]
31992
+ ],
31993
+ "Replies:",
31994
+ ...chain.length === 0 ? ["- none"] : chain
31972
31995
  ].join(`
31973
31996
  `);
31974
31997
  }
@@ -32067,10 +32090,15 @@ async function executeDispatchTool(input) {
32067
32090
  const resolved = artifactReference ? await resolveArtifact(client, issue(), artifactReference) : undefined;
32068
32091
  const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
32069
32092
  const replyTo = optionalString(args, "reply_to");
32093
+ const replyToAsk = optionalString(args, "reply_to_ask");
32094
+ if (replyTo !== undefined && replyToAsk !== undefined) {
32095
+ throw new Error("reply_to and reply_to_ask cannot both be set");
32096
+ }
32070
32097
  const comment = await client.comment(issue(), {
32071
32098
  body: stringArg(args, "body"),
32072
32099
  ...anchored === undefined ? {} : { anchor: anchored },
32073
32100
  ...replyTo === undefined ? {} : { reply_to: replyTo },
32101
+ ...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
32074
32102
  actor
32075
32103
  });
32076
32104
  return {
@@ -32179,10 +32207,13 @@ Open anchored asks/comments: ${marks.join(", ")}`,
32179
32207
  }
32180
32208
  case "dispatch_read": {
32181
32209
  if (issueArguments.ref?.kind === "ask") {
32182
- const ask = await client.getAsk(issueArguments.ref.id);
32210
+ const askRead = await client.getAsk(issueArguments.ref.id);
32183
32211
  return {
32184
- text: askSummary(ask),
32185
- details: { issue: ask.issue_key, topic: dispatchIssueSubject(ask.issue_key, ">") }
32212
+ text: askSummary(askRead),
32213
+ details: {
32214
+ issue: askRead.ask.issue_key,
32215
+ topic: dispatchIssueSubject(askRead.ask.issue_key, ">")
32216
+ }
32186
32217
  };
32187
32218
  }
32188
32219
  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.25.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [