@sjawhar/pi-legion-envoy 1.38.5 → 1.40.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 +6 -6
- package/dist/envoy.js +115 -15
- package/dist/legion.js +9 -1
- package/dist/skills/dispatch/SKILL.md +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,10 +85,10 @@ extension files it does not contain.
|
|
|
85
85
|
|
|
86
86
|
## Native Dispatch tools
|
|
87
87
|
|
|
88
|
-
The extension registers
|
|
89
|
-
`dispatch_resolve_ask`, `dispatch_follow`, `dispatch_comment`, `dispatch_suggest`,
|
|
90
|
-
`dispatch_doc_edit`, `dispatch_doc_read`, `dispatch_request_approval`, `dispatch_artifact`,
|
|
91
|
-
`dispatch_search`, and `dispatch_open_asks`, when Dispatch configuration resolves both a base URL and bearer token.
|
|
88
|
+
The extension registers sixteen native Dispatch tools: `dispatch_issue`, `dispatch_ask`, `dispatch_edit_ask`,
|
|
89
|
+
`dispatch_resolve_ask`, `dispatch_resolve_comment`, `dispatch_follow`, `dispatch_comment`, `dispatch_suggest`,
|
|
90
|
+
`dispatch_message`, `dispatch_doc_edit`, `dispatch_doc_read`, `dispatch_request_approval`, `dispatch_artifact`,
|
|
91
|
+
`dispatch_read`, `dispatch_search`, and `dispatch_open_asks`, when Dispatch configuration resolves both a base URL and bearer token.
|
|
92
92
|
|
|
93
93
|
Configure the shared `envoy.json` with:
|
|
94
94
|
|
|
@@ -112,7 +112,7 @@ whose trimmed contents are the token — how the Legion daemon delivers it to a
|
|
|
112
112
|
pane) wins over every other token source and never falls back when unreadable.
|
|
113
113
|
Omitting `dispatch.serverUrl` while `dispatch.enabled` is true targets
|
|
114
114
|
`http://localhost:8766`, the Go server's listen address. Invalid configuration,
|
|
115
|
-
an invalid URL, or an empty token leaves the
|
|
115
|
+
an invalid URL, or an empty token leaves the sixteen tools unavailable and
|
|
116
116
|
reports the source of the error.
|
|
117
117
|
|
|
118
118
|
Owner-scoped calls use either an issue (a native `KEY` or external `owner/repo#n` reference) or
|
|
@@ -121,7 +121,7 @@ an unlinked project document (`project` plus its `artifact` slug). A Legion sess
|
|
|
121
121
|
repository. `dispatch_doc_read` and `dispatch_read` also accept `dispatch://` references,
|
|
122
122
|
including `dispatch://PROJECT/artifact/<slug>`. `dispatch_search` needs only its query.
|
|
123
123
|
`dispatch_edit_ask`, `dispatch_resolve_ask`, and `dispatch_follow` instead identify an existing
|
|
124
|
-
ask with `ask`. No result carries a subscription topic: opening an ask or replying to one with
|
|
124
|
+
ask with `ask`, and `dispatch_resolve_comment` a comment with `comment`. No result carries a subscription topic: opening an ask or replying to one with
|
|
125
125
|
`dispatch_comment({ reply_to_ask })` makes the session a follower of that ask (its answer and
|
|
126
126
|
replies reach the session directly, server-side), the result carries `details.follows.ask`, and
|
|
127
127
|
the extension's `tool_result` hook tells the model so once per ask. Whole-issue or
|
package/dist/envoy.js
CHANGED
|
@@ -29946,6 +29946,14 @@ var dispatchToolSpecs = [
|
|
|
29946
29946
|
reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
|
|
29947
29947
|
})
|
|
29948
29948
|
},
|
|
29949
|
+
{
|
|
29950
|
+
name: "dispatch_resolve_comment",
|
|
29951
|
+
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.",
|
|
29952
|
+
arguments: (z) => ({
|
|
29953
|
+
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.")
|
|
29954
|
+
}),
|
|
29955
|
+
strict: true
|
|
29956
|
+
},
|
|
29949
29957
|
{
|
|
29950
29958
|
name: "dispatch_follow",
|
|
29951
29959
|
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>.>",
|
|
@@ -30063,7 +30071,7 @@ var dispatchToolSpecs = [
|
|
|
30063
30071
|
},
|
|
30064
30072
|
{
|
|
30065
30073
|
name: "dispatch_read",
|
|
30066
|
-
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain. Do not use it for document " + "contents; use dispatch_doc_read instead. Supply ref, issue, or project plus artifact. " + OWNER_REFERENCE,
|
|
30074
|
+
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain. Do not use it for document " + "contents; use dispatch_doc_read instead. Supply ref, issue, or project plus artifact. " + "Every read ends with `Referenced by:` (what cites or hangs off this node, each with its dispatch:// address, " + "an excerpt, and when) and `Links:` (what it cites), so tracing provenance is one call. " + OWNER_REFERENCE,
|
|
30067
30075
|
arguments: (z) => ({
|
|
30068
30076
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
30069
30077
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -31965,6 +31973,9 @@ class DispatchClient {
|
|
|
31965
31973
|
async getComment(id) {
|
|
31966
31974
|
return this.#json("GET", ["api", "v1", "comments", id]);
|
|
31967
31975
|
}
|
|
31976
|
+
async resolveComment(id, actor) {
|
|
31977
|
+
return this.#json("POST", ["api", "v1", "comments", id, "resolve"], { actor });
|
|
31978
|
+
}
|
|
31968
31979
|
async getComments(issue, artifact) {
|
|
31969
31980
|
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], undefined, artifact ? { artifact } : undefined);
|
|
31970
31981
|
}
|
|
@@ -31977,6 +31988,13 @@ class DispatchClient {
|
|
|
31977
31988
|
"references"
|
|
31978
31989
|
]);
|
|
31979
31990
|
}
|
|
31991
|
+
async getReferences(query) {
|
|
31992
|
+
return this.#json("GET", ["api", "v1", "references"], undefined, {
|
|
31993
|
+
..."to" in query ? { to: query.to } : { from: query.from },
|
|
31994
|
+
...query.kind === undefined || query.kind.length === 0 ? {} : { kind: query.kind.join(",") },
|
|
31995
|
+
...query.since === undefined ? {} : { since: query.since }
|
|
31996
|
+
});
|
|
31997
|
+
}
|
|
31980
31998
|
async ensureIssue(issueReference, actor) {
|
|
31981
31999
|
if (!issueReference.includes("#"))
|
|
31982
32000
|
return issueReference;
|
|
@@ -32242,6 +32260,19 @@ async function askOwnerDetails(client, ask, resolved) {
|
|
|
32242
32260
|
const artifact = resolved?.artifact ?? await client.getArtifact(ask.artifact_id);
|
|
32243
32261
|
return { ...documentResultDetails(artifact), ask: ask.id };
|
|
32244
32262
|
}
|
|
32263
|
+
async function commentOwnerResult(client, comment, artifact) {
|
|
32264
|
+
if (comment.issue_key !== null) {
|
|
32265
|
+
return { label: comment.issue_key, details: { issue: comment.issue_key, comment: comment.id } };
|
|
32266
|
+
}
|
|
32267
|
+
if (comment.artifact_id === undefined || comment.artifact_id === null) {
|
|
32268
|
+
throw new Error("document comment is missing its artifact ID");
|
|
32269
|
+
}
|
|
32270
|
+
const owner = artifact ?? await client.getArtifact(comment.artifact_id);
|
|
32271
|
+
return {
|
|
32272
|
+
label: documentTopic(owner).label,
|
|
32273
|
+
details: { ...documentResultDetails(owner), comment: comment.id }
|
|
32274
|
+
};
|
|
32275
|
+
}
|
|
32245
32276
|
async function followedAskDetails(client, ask, resolved) {
|
|
32246
32277
|
return { ...await askOwnerDetails(client, ask, resolved), follows: { ask: ask.id } };
|
|
32247
32278
|
}
|
|
@@ -32252,6 +32283,7 @@ var issueFreeTools = {
|
|
|
32252
32283
|
dispatch_issue: true,
|
|
32253
32284
|
dispatch_edit_ask: true,
|
|
32254
32285
|
dispatch_resolve_ask: true,
|
|
32286
|
+
dispatch_resolve_comment: true,
|
|
32255
32287
|
dispatch_follow: true,
|
|
32256
32288
|
dispatch_search: true,
|
|
32257
32289
|
dispatch_open_asks: true
|
|
@@ -32374,6 +32406,7 @@ function parseDispatchRef(ref) {
|
|
|
32374
32406
|
}
|
|
32375
32407
|
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
32376
32408
|
var askIdProblem = "ask must be a bare ask id or a dispatch://.../ask/<id> reference";
|
|
32409
|
+
var commentIdProblem = "comment must be a bare comment id or a dispatch://.../comment/<id> reference";
|
|
32377
32410
|
var messageIdProblem = "in_reply_to must be a full message id (uuid) or a dispatch://KEY/message/<id> reference";
|
|
32378
32411
|
function askId(args) {
|
|
32379
32412
|
const ask = stringArg(args, "ask");
|
|
@@ -32429,6 +32462,13 @@ function argumentProblems(tool, args) {
|
|
|
32429
32462
|
}
|
|
32430
32463
|
break;
|
|
32431
32464
|
}
|
|
32465
|
+
case "dispatch_resolve_comment": {
|
|
32466
|
+
const comment = optionalString(args, "comment");
|
|
32467
|
+
if (comment?.startsWith("dispatch://") && parseDispatchRef(comment)?.kind !== "comment") {
|
|
32468
|
+
problems.push(commentIdProblem);
|
|
32469
|
+
}
|
|
32470
|
+
break;
|
|
32471
|
+
}
|
|
32432
32472
|
case "dispatch_comment": {
|
|
32433
32473
|
if (optionalString(args, "quote") !== undefined && optionalString(args, "artifact") === undefined) {
|
|
32434
32474
|
problems.push("artifact is required when quote is supplied");
|
|
@@ -32656,7 +32696,7 @@ function approvalLine(artifact) {
|
|
|
32656
32696
|
return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
|
|
32657
32697
|
}
|
|
32658
32698
|
}
|
|
32659
|
-
function issueSummary(issue, events, references) {
|
|
32699
|
+
function issueSummary(issue, events, references, graph) {
|
|
32660
32700
|
const asks = issue.open_asks;
|
|
32661
32701
|
const spec = issue.artifacts?.find((artifact) => artifact.primary);
|
|
32662
32702
|
const specApproval = spec === undefined ? undefined : approvalLine(spec);
|
|
@@ -32676,10 +32716,35 @@ function issueSummary(issue, events, references) {
|
|
|
32676
32716
|
...typeof references === "string" ? [`- ${references}`] : references.members.length === 0 ? ["- none"] : references.members.map(({ artifact, depth, via }) => `- ${artifact.project}/${artifact.slug} \xB7 depth ${depth} via ${via.kind} ${via.id}`),
|
|
32677
32717
|
...typeof references === "string" || !references.truncated ? [] : ["- more references beyond 8 hops"],
|
|
32678
32718
|
"Events:",
|
|
32679
|
-
...events.length === 0 ? ["- none"] : events.map(eventLine)
|
|
32719
|
+
...events.length === 0 ? ["- none"] : events.map(eventLine),
|
|
32720
|
+
...graph
|
|
32680
32721
|
].join(`
|
|
32681
32722
|
`);
|
|
32682
32723
|
}
|
|
32724
|
+
function referenceLines(edges) {
|
|
32725
|
+
if (typeof edges === "string")
|
|
32726
|
+
return [`- ${edges}`];
|
|
32727
|
+
if (edges.length === 0)
|
|
32728
|
+
return ["- none"];
|
|
32729
|
+
return edges.map((edge) => {
|
|
32730
|
+
const excerpt = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
|
|
32731
|
+
return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
|
|
32732
|
+
});
|
|
32733
|
+
}
|
|
32734
|
+
async function graphEdges(client, query) {
|
|
32735
|
+
try {
|
|
32736
|
+
return (await client.getReferences(query)).edges;
|
|
32737
|
+
} catch (error) {
|
|
32738
|
+
return error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
32739
|
+
}
|
|
32740
|
+
}
|
|
32741
|
+
async function graphSections(client, ref) {
|
|
32742
|
+
const [incoming, outgoing] = await Promise.all([
|
|
32743
|
+
graphEdges(client, { to: ref }),
|
|
32744
|
+
graphEdges(client, { from: ref })
|
|
32745
|
+
]);
|
|
32746
|
+
return ["Referenced by:", ...referenceLines(incoming), "Links:", ...referenceLines(outgoing)];
|
|
32747
|
+
}
|
|
32683
32748
|
function logSummary(issue, events) {
|
|
32684
32749
|
return [
|
|
32685
32750
|
`Key: ${issue.key}`,
|
|
@@ -32727,7 +32792,7 @@ function childrenSummary(issue) {
|
|
|
32727
32792
|
].join(`
|
|
32728
32793
|
`);
|
|
32729
32794
|
}
|
|
32730
|
-
function askSummary({ ask, replies }) {
|
|
32795
|
+
function askSummary({ ask, replies }, graph) {
|
|
32731
32796
|
const answer = ask.answer;
|
|
32732
32797
|
const chain = replies.flatMap((reply) => [
|
|
32733
32798
|
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
@@ -32751,7 +32816,8 @@ function askSummary({ ask, replies }) {
|
|
|
32751
32816
|
`- Reason: ${ask.resolution.reason}`
|
|
32752
32817
|
],
|
|
32753
32818
|
"Replies:",
|
|
32754
|
-
...chain.length === 0 ? ["- none"] : chain
|
|
32819
|
+
...chain.length === 0 ? ["- none"] : chain,
|
|
32820
|
+
...graph
|
|
32755
32821
|
].join(`
|
|
32756
32822
|
`);
|
|
32757
32823
|
}
|
|
@@ -32794,7 +32860,7 @@ function formatOpenAsksSummary(response, baseUrl) {
|
|
|
32794
32860
|
].join(`
|
|
32795
32861
|
`);
|
|
32796
32862
|
}
|
|
32797
|
-
function commentSummary({ comment, replies }) {
|
|
32863
|
+
function commentSummary({ comment, replies }, graph) {
|
|
32798
32864
|
const root = [
|
|
32799
32865
|
`${comment.id} \xB7 ${comment.author.kind} ${comment.author.id}`,
|
|
32800
32866
|
...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
|
|
@@ -32805,10 +32871,16 @@ function commentSummary({ comment, replies }) {
|
|
|
32805
32871
|
...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
|
|
32806
32872
|
`Body: ${reply.body}`
|
|
32807
32873
|
]);
|
|
32808
|
-
return [
|
|
32874
|
+
return [
|
|
32875
|
+
"Comment:",
|
|
32876
|
+
...root,
|
|
32877
|
+
"Reply chain:",
|
|
32878
|
+
...chain.length === 0 ? ["- none"] : chain,
|
|
32879
|
+
...graph
|
|
32880
|
+
].join(`
|
|
32809
32881
|
`);
|
|
32810
32882
|
}
|
|
32811
|
-
function messageSummary({ message, replies }) {
|
|
32883
|
+
function messageSummary({ message, replies }, graph) {
|
|
32812
32884
|
const root = [
|
|
32813
32885
|
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
32814
32886
|
`Body: ${message.body}`
|
|
@@ -32817,7 +32889,13 @@ function messageSummary({ message, replies }) {
|
|
|
32817
32889
|
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
32818
32890
|
`Body: ${reply.body}`
|
|
32819
32891
|
]);
|
|
32820
|
-
return [
|
|
32892
|
+
return [
|
|
32893
|
+
"Message:",
|
|
32894
|
+
...root,
|
|
32895
|
+
"Reply chain:",
|
|
32896
|
+
...chain.length === 0 ? ["- none"] : chain,
|
|
32897
|
+
...graph
|
|
32898
|
+
].join(`
|
|
32821
32899
|
`);
|
|
32822
32900
|
}
|
|
32823
32901
|
async function openArtifactMarks(client, resolved) {
|
|
@@ -32970,6 +33048,23 @@ async function executeDispatchTool(input) {
|
|
|
32970
33048
|
details: await askOwnerDetails(client, ask)
|
|
32971
33049
|
};
|
|
32972
33050
|
}
|
|
33051
|
+
case "dispatch_resolve_comment": {
|
|
33052
|
+
const reference = stringArg(args, "comment");
|
|
33053
|
+
const ref = reference.startsWith("dispatch://") ? parseDispatchRef(reference) : null;
|
|
33054
|
+
let id = reference;
|
|
33055
|
+
let document;
|
|
33056
|
+
if (ref?.owner.kind === "issue") {
|
|
33057
|
+
const issueKey = ref.owner.issue;
|
|
33058
|
+
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getComments(issueKey));
|
|
33059
|
+
} else if (ref !== null) {
|
|
33060
|
+
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact)).artifact;
|
|
33061
|
+
document = artifact;
|
|
33062
|
+
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getArtifactComments(artifact.id));
|
|
33063
|
+
}
|
|
33064
|
+
const comment = await client.resolveComment(id, actor);
|
|
33065
|
+
const { label, details } = await commentOwnerResult(client, comment, document);
|
|
33066
|
+
return { text: `Resolved comment ${comment.id} on ${label}.`, details };
|
|
33067
|
+
}
|
|
32973
33068
|
case "dispatch_ask": {
|
|
32974
33069
|
const anchorArgs = asObject(args.anchor);
|
|
32975
33070
|
const owner = documentOwner();
|
|
@@ -33197,8 +33292,9 @@ ${trailer.join(`
|
|
|
33197
33292
|
const ref = ownerArguments.ref;
|
|
33198
33293
|
const id = await resolveIdPrefix(input.tool, "ask", ref, async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
|
|
33199
33294
|
const askRead = await client.getAsk(id);
|
|
33295
|
+
const askRef = ref.owner.kind === "issue" ? `dispatch://${ref.owner.issue}/ask/${id}` : `dispatch://${ref.owner.project}/artifact/${ref.artifact}/ask/${id}`;
|
|
33200
33296
|
return {
|
|
33201
|
-
text: askSummary(askRead),
|
|
33297
|
+
text: askSummary(askRead, await graphSections(client, askRef)),
|
|
33202
33298
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue }
|
|
33203
33299
|
};
|
|
33204
33300
|
}
|
|
@@ -33206,8 +33302,9 @@ ${trailer.join(`
|
|
|
33206
33302
|
const ref = ownerArguments.ref;
|
|
33207
33303
|
const id = await resolveIdPrefix(input.tool, "comment", ref, async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
|
|
33208
33304
|
const comment = await client.getComment(id);
|
|
33305
|
+
const commentRef = ref.owner.kind === "issue" ? `dispatch://${ref.owner.issue}/comment/${id}` : `dispatch://${ref.owner.project}/artifact/${ref.artifact}/comment/${id}`;
|
|
33209
33306
|
return {
|
|
33210
|
-
text: commentSummary(comment),
|
|
33307
|
+
text: commentSummary(comment, await graphSections(client, commentRef)),
|
|
33211
33308
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: comment.comment.issue_key }
|
|
33212
33309
|
};
|
|
33213
33310
|
}
|
|
@@ -33216,19 +33313,22 @@ ${trailer.join(`
|
|
|
33216
33313
|
throw new Error("message references are issue-scoped");
|
|
33217
33314
|
}
|
|
33218
33315
|
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
33316
|
+
const messageRef = `dispatch://${ownerArguments.ref.owner.issue}/message/${ownerArguments.ref.id}`;
|
|
33219
33317
|
return {
|
|
33220
|
-
text: messageSummary(messageRead),
|
|
33318
|
+
text: messageSummary(messageRead, await graphSections(client, messageRef)),
|
|
33221
33319
|
details: { issue: messageRead.message.issue_key }
|
|
33222
33320
|
};
|
|
33223
33321
|
}
|
|
33224
33322
|
if (documentOwner().kind === "project") {
|
|
33225
33323
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
33324
|
+
const documentRef = `dispatch://${resolved.artifact.project}/artifact/${resolved.artifact.slug}`;
|
|
33226
33325
|
return {
|
|
33227
33326
|
text: [
|
|
33228
33327
|
`Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
|
|
33229
|
-
`Reference:
|
|
33328
|
+
`Reference: ${documentRef}`,
|
|
33230
33329
|
`Versions: ${resolved.artifact.versions.length}`,
|
|
33231
|
-
...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)]
|
|
33330
|
+
...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)],
|
|
33331
|
+
...await graphSections(client, documentRef)
|
|
33232
33332
|
].join(`
|
|
33233
33333
|
`),
|
|
33234
33334
|
details: {
|
|
@@ -33257,7 +33357,7 @@ ${trailer.join(`
|
|
|
33257
33357
|
references = error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
33258
33358
|
}
|
|
33259
33359
|
return {
|
|
33260
|
-
text: issueSummary(read.issue, read.events, references),
|
|
33360
|
+
text: issueSummary(read.issue, read.events, references, await graphSections(client, `dispatch://${read.issue.key}`)),
|
|
33261
33361
|
details: { issue: read.issue.key }
|
|
33262
33362
|
};
|
|
33263
33363
|
}
|
package/dist/legion.js
CHANGED
|
@@ -29220,6 +29220,14 @@ var dispatchToolSpecs = [
|
|
|
29220
29220
|
reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
|
|
29221
29221
|
})
|
|
29222
29222
|
},
|
|
29223
|
+
{
|
|
29224
|
+
name: "dispatch_resolve_comment",
|
|
29225
|
+
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.",
|
|
29226
|
+
arguments: (z) => ({
|
|
29227
|
+
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.")
|
|
29228
|
+
}),
|
|
29229
|
+
strict: true
|
|
29230
|
+
},
|
|
29223
29231
|
{
|
|
29224
29232
|
name: "dispatch_follow",
|
|
29225
29233
|
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>.>",
|
|
@@ -29337,7 +29345,7 @@ var dispatchToolSpecs = [
|
|
|
29337
29345
|
},
|
|
29338
29346
|
{
|
|
29339
29347
|
name: "dispatch_read",
|
|
29340
|
-
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain. Do not use it for document " + "contents; use dispatch_doc_read instead. Supply ref, issue, or project plus artifact. " + OWNER_REFERENCE,
|
|
29348
|
+
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain. Do not use it for document " + "contents; use dispatch_doc_read instead. Supply ref, issue, or project plus artifact. " + "Every read ends with `Referenced by:` (what cites or hangs off this node, each with its dispatch:// address, " + "an excerpt, and when) and `Links:` (what it cites), so tracing provenance is one call. " + OWNER_REFERENCE,
|
|
29341
29349
|
arguments: (z) => ({
|
|
29342
29350
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29343
29351
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -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
|
|
@@ -475,6 +494,14 @@ document owner or ref, it returns a document summary with `details` `{ project,
|
|
|
475
494
|
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
|
|
476
495
|
message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
477
496
|
|
|
497
|
+
Every read ends with two sections from the reference graph. `Referenced by:` lists what points at the node — every document, ask,
|
|
498
|
+
comment, or message that cites it, plus its structure: child issues, attached documents, anchored and owned asks and comments, replies,
|
|
499
|
+
followers — and `Links:` lists what it cites. Each row is `- <edge kind> <node kind> dispatch://… (<excerpt> · <when>)`; for a
|
|
500
|
+
document source the excerpt is the block containing the mention. Cross-project, always: a message on another project's issue that
|
|
501
|
+
cites an ask shows up under that ask. So "what led to this decision" is one `dispatch_read` on the ask, and "who relies on this
|
|
502
|
+
document" one read on the document. Cite with `dispatch://` references (below) whenever you name a node in a body — a bare id or
|
|
503
|
+
title is invisible to the graph.
|
|
504
|
+
|
|
478
505
|
## References
|
|
479
506
|
|
|
480
507
|
Use these in document, ask, comment, and message bodies. In the dashboard, a reference renders
|