@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 +2 -1
- package/dist/src/server.js +98 -14
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +24 -5
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
|
|
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
|
package/dist/src/server.js
CHANGED
|
@@ -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
|
|
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([
|
|
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
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
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:
|
|
15175
|
+
details: { issue: issueArguments.ref.issue }
|
|
15092
15176
|
};
|
|
15093
15177
|
}
|
|
15094
15178
|
if (issueArguments.ref?.kind === "comment") {
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -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
|
|
145
|
-
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|