@sjawhar/opencode-legion-envoy 1.28.0 → 1.29.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 +50 -0
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +19 -0
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ This package exposes:
|
|
|
16
16
|
- `dispatch_ask`
|
|
17
17
|
- `dispatch_edit_ask`
|
|
18
18
|
- `dispatch_resolve_ask`
|
|
19
|
+
- `dispatch_resolve_comment`
|
|
19
20
|
- `dispatch_follow`
|
|
20
21
|
- `dispatch_comment`
|
|
21
22
|
- `dispatch_suggest`
|
|
@@ -28,7 +29,7 @@ This package exposes:
|
|
|
28
29
|
- `dispatch_search`
|
|
29
30
|
- `dispatch_open_asks`
|
|
30
31
|
|
|
31
|
-
The
|
|
32
|
+
The sixteen native `dispatch_*` tools create and read Dispatch issues, asks, comments,
|
|
32
33
|
documents, and artifacts, or search all of them. They are present when `dispatch.enabled`
|
|
33
34
|
resolves a server URL and bearer token from envoy.json (`~/.config/opencode/envoy.json`, merged
|
|
34
35
|
with `<repo>/.opencode/envoy.json`) or the `DISPATCH_URL` and `DISPATCH_TOKEN` environment
|
package/dist/src/server.js
CHANGED
|
@@ -13849,6 +13849,14 @@ var dispatchToolSpecs = [
|
|
|
13849
13849
|
reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
|
|
13850
13850
|
})
|
|
13851
13851
|
},
|
|
13852
|
+
{
|
|
13853
|
+
name: "dispatch_resolve_comment",
|
|
13854
|
+
description: "Resolve a review comment thread once it has been addressed - typically your own comment " + "after the document was fixed. Any session or human may resolve any open comment on an " + "open issue or project document; reopening a resolved comment is human-only (the dashboard). " + "Not for asks: use dispatch_resolve_ask.",
|
|
13855
|
+
arguments: (z) => ({
|
|
13856
|
+
comment: z.string().describe("Comment id (uuid), or a dispatch://KEY/comment/<id> or " + "dispatch://PROJECT/artifact/<slug>/comment/<id> reference; a reference accepts an " + "8+ character id prefix that is unique on its owner.")
|
|
13857
|
+
}),
|
|
13858
|
+
strict: true
|
|
13859
|
+
},
|
|
13852
13860
|
{
|
|
13853
13861
|
name: "dispatch_follow",
|
|
13854
13862
|
description: "Follow or unfollow an ask. Every session that opens or replies to an ask follows it: its answer, " + "edits, resolution, and replies reach that session directly. Unfollow to stop; follow to rejoin or " + "to hear an ask you never wrote to. Whole-issue subscription is separate: envoy_subscribe " + "notifications.dispatch.issue.<KEY>.>",
|
|
@@ -14902,6 +14910,9 @@ class DispatchClient {
|
|
|
14902
14910
|
async getComment(id) {
|
|
14903
14911
|
return this.#json("GET", ["api", "v1", "comments", id]);
|
|
14904
14912
|
}
|
|
14913
|
+
async resolveComment(id, actor) {
|
|
14914
|
+
return this.#json("POST", ["api", "v1", "comments", id, "resolve"], { actor });
|
|
14915
|
+
}
|
|
14905
14916
|
async getComments(issue, artifact) {
|
|
14906
14917
|
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], undefined, artifact ? { artifact } : undefined);
|
|
14907
14918
|
}
|
|
@@ -15186,6 +15197,19 @@ async function askOwnerDetails(client, ask, resolved) {
|
|
|
15186
15197
|
const artifact = resolved?.artifact ?? await client.getArtifact(ask.artifact_id);
|
|
15187
15198
|
return { ...documentResultDetails(artifact), ask: ask.id };
|
|
15188
15199
|
}
|
|
15200
|
+
async function commentOwnerResult(client, comment, artifact) {
|
|
15201
|
+
if (comment.issue_key !== null) {
|
|
15202
|
+
return { label: comment.issue_key, details: { issue: comment.issue_key, comment: comment.id } };
|
|
15203
|
+
}
|
|
15204
|
+
if (comment.artifact_id === undefined || comment.artifact_id === null) {
|
|
15205
|
+
throw new Error("document comment is missing its artifact ID");
|
|
15206
|
+
}
|
|
15207
|
+
const owner = artifact ?? await client.getArtifact(comment.artifact_id);
|
|
15208
|
+
return {
|
|
15209
|
+
label: documentTopic(owner).label,
|
|
15210
|
+
details: { ...documentResultDetails(owner), comment: comment.id }
|
|
15211
|
+
};
|
|
15212
|
+
}
|
|
15189
15213
|
async function followedAskDetails(client, ask, resolved) {
|
|
15190
15214
|
return { ...await askOwnerDetails(client, ask, resolved), follows: { ask: ask.id } };
|
|
15191
15215
|
}
|
|
@@ -15196,6 +15220,7 @@ var issueFreeTools = {
|
|
|
15196
15220
|
dispatch_issue: true,
|
|
15197
15221
|
dispatch_edit_ask: true,
|
|
15198
15222
|
dispatch_resolve_ask: true,
|
|
15223
|
+
dispatch_resolve_comment: true,
|
|
15199
15224
|
dispatch_follow: true,
|
|
15200
15225
|
dispatch_search: true,
|
|
15201
15226
|
dispatch_open_asks: true
|
|
@@ -15318,6 +15343,7 @@ function parseDispatchRef(ref) {
|
|
|
15318
15343
|
}
|
|
15319
15344
|
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15320
15345
|
var askIdProblem = "ask must be a bare ask id or a dispatch://.../ask/<id> reference";
|
|
15346
|
+
var commentIdProblem = "comment must be a bare comment id or a dispatch://.../comment/<id> reference";
|
|
15321
15347
|
var messageIdProblem = "in_reply_to must be a full message id (uuid) or a dispatch://KEY/message/<id> reference";
|
|
15322
15348
|
function askId(args) {
|
|
15323
15349
|
const ask = stringArg(args, "ask");
|
|
@@ -15373,6 +15399,13 @@ function argumentProblems(tool, args) {
|
|
|
15373
15399
|
}
|
|
15374
15400
|
break;
|
|
15375
15401
|
}
|
|
15402
|
+
case "dispatch_resolve_comment": {
|
|
15403
|
+
const comment = optionalString(args, "comment");
|
|
15404
|
+
if (comment?.startsWith("dispatch://") && parseDispatchRef(comment)?.kind !== "comment") {
|
|
15405
|
+
problems.push(commentIdProblem);
|
|
15406
|
+
}
|
|
15407
|
+
break;
|
|
15408
|
+
}
|
|
15376
15409
|
case "dispatch_comment": {
|
|
15377
15410
|
if (optionalString(args, "quote") !== undefined && optionalString(args, "artifact") === undefined) {
|
|
15378
15411
|
problems.push("artifact is required when quote is supplied");
|
|
@@ -15952,6 +15985,23 @@ async function executeDispatchTool(input) {
|
|
|
15952
15985
|
details: await askOwnerDetails(client, ask)
|
|
15953
15986
|
};
|
|
15954
15987
|
}
|
|
15988
|
+
case "dispatch_resolve_comment": {
|
|
15989
|
+
const reference = stringArg(args, "comment");
|
|
15990
|
+
const ref = reference.startsWith("dispatch://") ? parseDispatchRef(reference) : null;
|
|
15991
|
+
let id = reference;
|
|
15992
|
+
let document;
|
|
15993
|
+
if (ref?.owner.kind === "issue") {
|
|
15994
|
+
const issueKey = ref.owner.issue;
|
|
15995
|
+
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getComments(issueKey));
|
|
15996
|
+
} else if (ref !== null) {
|
|
15997
|
+
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact)).artifact;
|
|
15998
|
+
document = artifact;
|
|
15999
|
+
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getArtifactComments(artifact.id));
|
|
16000
|
+
}
|
|
16001
|
+
const comment = await client.resolveComment(id, actor);
|
|
16002
|
+
const { label, details } = await commentOwnerResult(client, comment, document);
|
|
16003
|
+
return { text: `Resolved comment ${comment.id} on ${label}.`, details };
|
|
16004
|
+
}
|
|
15955
16005
|
case "dispatch_ask": {
|
|
15956
16006
|
const anchorArgs = asObject(args.anchor);
|
|
15957
16007
|
const owner = documentOwner();
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -199,6 +199,8 @@ Use `retracted` when the question is obsolete and `resolved` when you found the
|
|
|
199
199
|
in its Conversation card and reply thread. Resolution is not an answer: it never records a human decision, and an answered ask cannot be
|
|
200
200
|
resolved. A human may reply to an open or answered ask; so may you, e.g. after finding the answer — use `reply_to_ask` on
|
|
201
201
|
`dispatch_comment` (mutually exclusive with `reply_to`).
|
|
202
|
+
A review comment you opened has its own closer, `dispatch_resolve_comment` — see
|
|
203
|
+
[Comments and suggestions](#comments-and-suggestions).
|
|
202
204
|
|
|
203
205
|
A human answers or asks back from the same field; a question-shaped free-text answer is offered as a clarification first. A human
|
|
204
206
|
reply while your ask is still open (the delivered `comment.created` carries `ask_state: open`) is a request for clarification, not
|
|
@@ -353,6 +355,18 @@ the human needs to act; see [Asking](#asking). Comments are edited only by their
|
|
|
353
355
|
dashboard. A delivered `comment.created` event carries the comment `id`; reply to it with
|
|
354
356
|
`dispatch_comment({ reply_to: <id> })`.
|
|
355
357
|
|
|
358
|
+
Resolve your own review comment once you have addressed it:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
dispatch_resolve_comment({ comment })
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`comment` is the comment id, or a `dispatch://KEY/comment/<id>` or `dispatch://PROJECT/artifact/<slug>/comment/<id>` reference (an
|
|
365
|
+
8+ character id prefix is resolved against the owner's comments). It returns the owner details plus `comment`, and takes no reason —
|
|
366
|
+
say what you did in a `reply_to` first if the thread needs it. The server lets any session or human resolve any open comment, so
|
|
367
|
+
resolve only threads you opened or were asked to close; reopening a resolved thread is human-only (from the dashboard), though your
|
|
368
|
+
reply to it reopens it. Asks are closed with `dispatch_resolve_ask` instead.
|
|
369
|
+
|
|
356
370
|
Propose an exact replacement instead of describing it:
|
|
357
371
|
|
|
358
372
|
```ts
|
|
@@ -384,6 +398,11 @@ with your text. Never do that: the spec is edited in place with `dispatch_doc_ed
|
|
|
384
398
|
artifact by the slug shown in the upload result or by its filename, and a project document by its artifact id, slug, or filename; the
|
|
385
399
|
slug also arrives on `artifact.created` events.
|
|
386
400
|
|
|
401
|
+
Documents are CommonMark. A bare `<https://example.com|text>` is a CommonMark autolink and is normalised: the angle brackets are
|
|
402
|
+
dropped and the URL keeps `|text`. A backslash-escaped `\<https://example.com|text>` displays as `<https://example.com|text>` in the
|
|
403
|
+
document but comes back re-escaped (`\<`) from `dispatch_doc_read`. A Slack mrkdwn draft, or any other payload that is not Markdown,
|
|
404
|
+
still belongs inside a fenced code block, where it survives verbatim both ways.
|
|
405
|
+
|
|
387
406
|
## Structure over stream
|
|
388
407
|
|
|
389
408
|
Dispatch is a structured workspace, never a message stream (Sami, 2026-09-13, verbatim: "strange
|