@sjawhar/opencode-legion-envoy 0.25.0 → 0.27.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
@@ -25,7 +25,8 @@ This package exposes:
25
25
  The native `dispatch_*` tools create and read Dispatch issues, asks, comments, documents, and
26
26
  artifacts. They are present when `dispatch.enabled` resolves a server URL and bearer token from
27
27
  envoy.json (`~/.config/opencode/envoy.json`, merged with `<repo>/.opencode/envoy.json`) or the
28
- `DISPATCH_URL` and `DISPATCH_TOKEN` environment variables. Each call fills the target issue from
28
+ `DISPATCH_URL` and `DISPATCH_TOKEN` environment variables; `dispatch.enabled: true` without
29
+ `dispatch.serverUrl` targets `http://localhost:8766`. Each call fills the target issue from
29
30
  the session working directory, stamps it with the OpenCode session id and title, and stores
30
31
  `details.topic` as tool metadata so a successful mutation subscribes to that exact Dispatch topic.
31
32
 
@@ -13580,6 +13580,8 @@ var CommentEventPayloadSchema = object({
13580
13580
  artifact_name: string2().optional(),
13581
13581
  body: string2().optional(),
13582
13582
  reply_to: string2().nullish(),
13583
+ ask_id: string2().nullish(),
13584
+ ask_question: string2().optional(),
13583
13585
  anchor: object({ quote: string2().optional() }).nullish(),
13584
13586
  suggestion: object({ replace_with: string2().optional() }).nullish()
13585
13587
  });
@@ -13630,14 +13632,15 @@ var dispatchToolSpecs = [
13630
13632
  },
13631
13633
  {
13632
13634
  name: "dispatch_comment",
13633
- 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}`,
13635
+ 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}`,
13634
13636
  arguments: (z) => ({
13635
13637
  issue: z.string().describe(ISSUE_REFERENCE),
13636
13638
  artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
13637
13639
  quote: z.string().describe("Optional exact quoted document text.").optional(),
13638
13640
  occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
13639
13641
  body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
13640
- reply_to: z.string().describe("Optional comment id to reply to.").optional()
13642
+ reply_to: z.string().describe("Optional comment id to reply to.").optional(),
13643
+ reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional()
13641
13644
  })
13642
13645
  },
13643
13646
  {
@@ -14319,6 +14322,9 @@ class DispatchClient {
14319
14322
  async issue(input) {
14320
14323
  return this.#json("POST", ["api", "v1", "issues"], input);
14321
14324
  }
14325
+ async listIssues(options = {}) {
14326
+ return this.#json("GET", ["api", "v1", "issues"], undefined, options);
14327
+ }
14322
14328
  async getIssue(issue) {
14323
14329
  return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue)]);
14324
14330
  }
@@ -14462,8 +14468,11 @@ class DispatchClient {
14462
14468
  #url(path, query) {
14463
14469
  const url = new URL(`${path.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
14464
14470
  if (query) {
14465
- for (const [name, value] of Object.entries(query))
14466
- url.searchParams.set(name, String(value));
14471
+ for (const [name, value] of Object.entries(query)) {
14472
+ if (typeof value === "string" || typeof value === "number") {
14473
+ url.searchParams.set(name, String(value));
14474
+ }
14475
+ }
14467
14476
  }
14468
14477
  return url.toString();
14469
14478
  }
@@ -14639,8 +14648,12 @@ function childrenSummary(issue) {
14639
14648
  ].join(`
14640
14649
  `);
14641
14650
  }
14642
- function askSummary(ask) {
14651
+ function askSummary({ ask, replies }) {
14643
14652
  const answer = ask.answer;
14653
+ const chain = replies.flatMap((reply) => [
14654
+ `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
14655
+ `Body: ${reply.body}`
14656
+ ]);
14644
14657
  return [
14645
14658
  `Question: ${ask.question}`,
14646
14659
  "Options:",
@@ -14651,7 +14664,9 @@ function askSummary(ask) {
14651
14664
  `- By: ${answer.user}`,
14652
14665
  `- Selected: ${answer.selected.length === 0 ? "none" : answer.selected.join(", ")}`,
14653
14666
  ...answer.text === null ? [] : [`- Text: ${answer.text}`]
14654
- ]
14667
+ ],
14668
+ "Replies:",
14669
+ ...chain.length === 0 ? ["- none"] : chain
14655
14670
  ].join(`
14656
14671
  `);
14657
14672
  }
@@ -14750,10 +14765,15 @@ async function executeDispatchTool(input) {
14750
14765
  const resolved = artifactReference ? await resolveArtifact(client, issue(), artifactReference) : undefined;
14751
14766
  const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
14752
14767
  const replyTo = optionalString(args, "reply_to");
14768
+ const replyToAsk = optionalString(args, "reply_to_ask");
14769
+ if (replyTo !== undefined && replyToAsk !== undefined) {
14770
+ throw new Error("reply_to and reply_to_ask cannot both be set");
14771
+ }
14753
14772
  const comment = await client.comment(issue(), {
14754
14773
  body: stringArg(args, "body"),
14755
14774
  ...anchored === undefined ? {} : { anchor: anchored },
14756
14775
  ...replyTo === undefined ? {} : { reply_to: replyTo },
14776
+ ...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
14757
14777
  actor
14758
14778
  });
14759
14779
  return {
@@ -14862,10 +14882,13 @@ Open anchored asks/comments: ${marks.join(", ")}`,
14862
14882
  }
14863
14883
  case "dispatch_read": {
14864
14884
  if (issueArguments.ref?.kind === "ask") {
14865
- const ask = await client.getAsk(issueArguments.ref.id);
14885
+ const askRead = await client.getAsk(issueArguments.ref.id);
14866
14886
  return {
14867
- text: askSummary(ask),
14868
- details: { issue: ask.issue_key, topic: dispatchIssueSubject(ask.issue_key, ">") }
14887
+ text: askSummary(askRead),
14888
+ details: {
14889
+ issue: askRead.ask.issue_key,
14890
+ topic: dispatchIssueSubject(askRead.ask.issue_key, ">")
14891
+ }
14869
14892
  };
14870
14893
  }
14871
14894
  if (issueArguments.ref?.kind === "comment") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -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