@sjawhar/opencode-legion-envoy 0.37.0 → 0.39.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
@@ -14,6 +14,7 @@ This package exposes:
14
14
  - `envoy_sessions`
15
15
  - `dispatch_issue`
16
16
  - `dispatch_ask`
17
+ - `dispatch_edit_ask`
17
18
  - `dispatch_resolve_ask`
18
19
  - `dispatch_comment`
19
20
  - `dispatch_suggest`
@@ -24,7 +25,7 @@ This package exposes:
24
25
  - `dispatch_read`
25
26
  - `dispatch_search`
26
27
 
27
- The eleven native `dispatch_*` tools create and read Dispatch issues, asks, comments,
28
+ The twelve native `dispatch_*` tools create and read Dispatch issues, asks, comments,
28
29
  documents, and artifacts, or search all of them. They are present when `dispatch.enabled`
29
30
  resolves a server URL and bearer token from envoy.json (`~/.config/opencode/envoy.json`, merged
30
31
  with `<repo>/.opencode/envoy.json`) or the `DISPATCH_URL` and `DISPATCH_TOKEN` environment
@@ -13553,7 +13553,9 @@ function date4(params) {
13553
13553
  config(en_default());
13554
13554
  // ../contracts/src/dispatch-api.ts
13555
13555
  var DispatchEventSchema = object({
13556
- issue_key: string2(),
13556
+ issue_key: string2().nullable(),
13557
+ artifact_id: string2().nullish(),
13558
+ project: string2().optional(),
13557
13559
  type: string2(),
13558
13560
  actor: object({ kind: string2(), id: string2().optional() }).passthrough(),
13559
13561
  payload: object({}).passthrough()
@@ -13571,7 +13573,7 @@ var ArtifactVersionEventPayloadSchema = object({
13571
13573
  version: object({ number: number2().optional(), summary: string2().nullish() }).optional(),
13572
13574
  diff: string2().optional()
13573
13575
  });
13574
- var AskEventPayloadSchema = object({
13576
+ var askEventPayloadFields = {
13575
13577
  opened_event_id: number2().int().positive(),
13576
13578
  question: string2().optional(),
13577
13579
  options: array(object({ label: string2().optional() })).nullish(),
@@ -13582,6 +13584,23 @@ var AskEventPayloadSchema = object({
13582
13584
  actor: object({ kind: string2().optional(), id: string2().optional() }).passthrough().optional(),
13583
13585
  at: string2().optional()
13584
13586
  }).nullish()
13587
+ };
13588
+ var AskEventPayloadSchema = intersection(object(askEventPayloadFields), object({
13589
+ previous: never().optional(),
13590
+ edited_by: never().optional()
13591
+ }));
13592
+ var AskEditedEventPayloadSchema = object({
13593
+ ...askEventPayloadFields,
13594
+ multiple: boolean2(),
13595
+ urgency: string2(),
13596
+ edited_at: string2().nullish(),
13597
+ previous: object({
13598
+ question: string2(),
13599
+ options: array(object({ label: string2().optional() })),
13600
+ multiple: boolean2(),
13601
+ urgency: string2()
13602
+ }),
13603
+ edited_by: object({ kind: string2(), id: string2() }).passthrough()
13585
13604
  });
13586
13605
  var CommentEventPayloadSchema = object({
13587
13606
  artifact_name: string2().optional(),
@@ -13698,6 +13717,27 @@ var dispatchToolSpecs = [
13698
13717
  }).describe("Optional document location for the question.").optional()
13699
13718
  })
13700
13719
  },
13720
+ {
13721
+ name: "dispatch_edit_ask",
13722
+ 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.",
13723
+ arguments: (z) => ({
13724
+ ask: z.string().describe("Ask id to edit."),
13725
+ question: z.string({ max: 800 }).describe("Replacement decision question, at most 800 characters.").optional(),
13726
+ options: z.array(z.object({
13727
+ label: z.string().describe("Selectable option label."),
13728
+ description: z.string().describe("Optional option context.").optional()
13729
+ }), { max: 8 }).describe("Replacement choices, at most 8.").optional(),
13730
+ multiple: z.boolean().describe("Whether multiple choices may be selected.").optional(),
13731
+ urgency: z.enum(ASK_URGENCIES).describe("Replacement decision urgency.").optional()
13732
+ }),
13733
+ validation: {
13734
+ check: (value) => {
13735
+ const input = value;
13736
+ return typeof input.question === "string" || Array.isArray(input.options) || typeof input.multiple === "boolean" || typeof input.urgency === "string";
13737
+ },
13738
+ message: "Ask edit requires at least one field besides ask."
13739
+ }
13740
+ },
13701
13741
  {
13702
13742
  name: "dispatch_resolve_ask",
13703
13743
  description: "Retract an open question that is moot or resolve one after finding the answer. This closes the question without answering it.",
@@ -14463,6 +14503,9 @@ class DispatchClient {
14463
14503
  async resolveAsk(id, input) {
14464
14504
  return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
14465
14505
  }
14506
+ async editAsk(id, input) {
14507
+ return this.#json("PATCH", ["api", "v1", "asks", id], input);
14508
+ }
14466
14509
  async comment(issue, input) {
14467
14510
  return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], input);
14468
14511
  }
@@ -14619,7 +14662,29 @@ class DispatchClient {
14619
14662
  var nativeIssueKeyPattern = /^[A-Z][A-Z0-9]{1,9}-[0-9]+$/;
14620
14663
  var externalIssueRefPattern = /^([^/\s]+)\/([^/\s#]+)#([1-9][0-9]*)$/;
14621
14664
  var bareIssueNumberPattern = /^[1-9][0-9]*$/;
14622
- var issueFreeTools = new Set(["dispatch_issue", "dispatch_resolve_ask", "dispatch_search"]);
14665
+ var issueFreeTools = new Set([
14666
+ "dispatch_issue",
14667
+ "dispatch_edit_ask",
14668
+ "dispatch_resolve_ask",
14669
+ "dispatch_search"
14670
+ ]);
14671
+ async function askResultDetails(client, ask) {
14672
+ if (ask.issue_key !== null) {
14673
+ return {
14674
+ issue: ask.issue_key,
14675
+ topic: dispatchIssueSubject(ask.issue_key, ">"),
14676
+ ask: ask.id
14677
+ };
14678
+ }
14679
+ if (ask.artifact_id === undefined || ask.artifact_id === null) {
14680
+ throw new Error("document ask is missing its artifact ID");
14681
+ }
14682
+ const artifact = await client.getArtifact(ask.artifact_id);
14683
+ return {
14684
+ topic: `notifications.dispatch.document.${artifact.project}.${artifact.slug}.>`,
14685
+ ask: ask.id
14686
+ };
14687
+ }
14623
14688
  function canonicalExternalIssueRef(value) {
14624
14689
  const match = value.trim().match(externalIssueRefPattern);
14625
14690
  return match ? `${canonicalRepo(match[1] ?? "", match[2] ?? "")}#${match[3]}` : value;
@@ -14690,6 +14755,16 @@ function parseDispatchRef(ref) {
14690
14755
  return { issue, kind: "comment", id: comment };
14691
14756
  return { issue, kind: "issue", id: issue };
14692
14757
  }
14758
+ function askId(args) {
14759
+ const ask = stringArg(args, "ask");
14760
+ if (!ask.startsWith("dispatch://"))
14761
+ return ask;
14762
+ const reference = parseDispatchRef(ask);
14763
+ if (reference?.kind !== "ask") {
14764
+ throw new Error("ask must be a bare ask id or a dispatch://.../ask/<id> reference");
14765
+ }
14766
+ return reference.id;
14767
+ }
14693
14768
  function toolSchema(tool) {
14694
14769
  const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
14695
14770
  if (!spec)
@@ -14935,11 +15010,7 @@ async function executeDispatchTool(input) {
14935
15010
  throw new Error("resolved ask is missing its resolution");
14936
15011
  return {
14937
15012
  text: `${kind === "retracted" ? "Retracted" : "Resolved"} ask ${ask.id}: ${ask.resolution.reason}`,
14938
- details: {
14939
- issue: ask.issue_key,
14940
- topic: dispatchIssueSubject(ask.issue_key, ">"),
14941
- ask: ask.id
14942
- }
15013
+ details: await askResultDetails(client, ask)
14943
15014
  };
14944
15015
  }
14945
15016
  case "dispatch_ask": {
@@ -14959,11 +15030,24 @@ async function executeDispatchTool(input) {
14959
15030
  });
14960
15031
  return {
14961
15032
  text: `Opened ask ${ask.id}: ${ask.question}`,
14962
- details: {
14963
- issue: ask.issue_key,
14964
- topic: dispatchIssueSubject(ask.issue_key, ">"),
14965
- ask: ask.id
14966
- }
15033
+ details: await askResultDetails(client, ask)
15034
+ };
15035
+ }
15036
+ case "dispatch_edit_ask": {
15037
+ const question = optionalString(args, "question");
15038
+ const options = args.options;
15039
+ const multiple = optionalBoolean(args, "multiple");
15040
+ const urgency = askUrgency(args);
15041
+ const ask = await client.editAsk(askId(args), {
15042
+ ...question === undefined ? {} : { question },
15043
+ ...Array.isArray(options) ? { options } : {},
15044
+ ...multiple === undefined ? {} : { multiple },
15045
+ ...urgency === undefined ? {} : { urgency },
15046
+ actor
15047
+ });
15048
+ return {
15049
+ text: `Ask edited: ${ask.question}`,
15050
+ details: await askResultDetails(client, ask)
14967
15051
  };
14968
15052
  }
14969
15053
  case "dispatch_comment": {
@@ -15088,7 +15172,7 @@ Open anchored asks/comments: ${marks.join(", ")}`,
15088
15172
  const askRead = await client.getAsk(issueArguments.ref.id);
15089
15173
  return {
15090
15174
  text: askSummary(askRead),
15091
- details: { issue: askRead.ask.issue_key }
15175
+ details: { issue: issueArguments.ref.issue }
15092
15176
  };
15093
15177
  }
15094
15178
  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.37.0",
3
+ "version": "0.39.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -94,6 +94,20 @@ accepts `{ artifact, mark_id }` from a browser that has already written its mark
94
94
  use the quote form. An anchor whose quote disappears becomes orphaned but remains readable against
95
95
  its original document version.
96
96
 
97
+ Correct or refine an open ask in place instead of opening a second question:
98
+ ```ts
99
+ dispatch_edit_ask({
100
+ ask,
101
+ question?,
102
+ options?: { label, description? }[],
103
+ multiple?,
104
+ urgency?,
105
+ })
106
+ ```
107
+ At least one field besides `ask` is required. Use this only while the same decision remains
108
+ open: it keeps the prior text in the event log. An answered or resolved ask cannot be edited.
109
+ If the decision is moot or superseded, retract the old ask and open a new one.
110
+
97
111
  An ask stays open until a human answers, unless its question no longer needs that answer. Retract a
98
112
  moot or superseded question, or self-resolve one after finding the answer:
99
113
  ```ts
@@ -141,11 +155,16 @@ type EditOp = {
141
155
  };
142
156
  ```
143
157
 
144
- Target edits by quote: `replace` requires `find` and `with`; `delete` requires `find`; `insert`
145
- requires `markdown` and exactly one of `after` or `before`. An insert anchor is a quote,
146
- `"start"`, `"end"`, or `"heading:Title"`. Use zero-based `occurrence` for a repeated `find`.
147
- When a decision lands, pass `summary` to name the resulting version. Never paste progress into a
148
- message.
158
+ Target `replace` and `delete` by the document's plain text: inline-code and link text match
159
+ without Markdown syntax, and a table-cell anchor is its cell text. `replace` requires `find` and
160
+ `with`; `delete` requires `find`; `insert` requires `markdown` and exactly one of `after` or
161
+ `before`. An insert anchor is a quote, `"start"`, `"end"`, or `"heading:Title"`. Every insert
162
+ creates a sibling block before or after the quote or heading's enclosing document block; `"start"`
163
+ and `"end"` select the document edges. At a table-cell quote, pipe-table body-row fragments extend
164
+ that table before or after the matched row; omit table header and delimiter rows, and do not exceed
165
+ the table width. Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated
166
+ target. When a decision lands, pass `summary` to name the resulting version. Never paste progress
167
+ into a message.
149
168
 
150
169
  ## Comments and suggestions
151
170