@sjawhar/opencode-legion-envoy 1.27.5 → 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 +115 -15
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +27 -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>.>",
|
|
@@ -13966,7 +13974,7 @@ var dispatchToolSpecs = [
|
|
|
13966
13974
|
},
|
|
13967
13975
|
{
|
|
13968
13976
|
name: "dispatch_read",
|
|
13969
|
-
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,
|
|
13977
|
+
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,
|
|
13970
13978
|
arguments: (z) => ({
|
|
13971
13979
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13972
13980
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -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
|
}
|
|
@@ -14914,6 +14925,13 @@ class DispatchClient {
|
|
|
14914
14925
|
"references"
|
|
14915
14926
|
]);
|
|
14916
14927
|
}
|
|
14928
|
+
async getReferences(query) {
|
|
14929
|
+
return this.#json("GET", ["api", "v1", "references"], undefined, {
|
|
14930
|
+
..."to" in query ? { to: query.to } : { from: query.from },
|
|
14931
|
+
...query.kind === undefined || query.kind.length === 0 ? {} : { kind: query.kind.join(",") },
|
|
14932
|
+
...query.since === undefined ? {} : { since: query.since }
|
|
14933
|
+
});
|
|
14934
|
+
}
|
|
14917
14935
|
async ensureIssue(issueReference, actor) {
|
|
14918
14936
|
if (!issueReference.includes("#"))
|
|
14919
14937
|
return issueReference;
|
|
@@ -15179,6 +15197,19 @@ async function askOwnerDetails(client, ask, resolved) {
|
|
|
15179
15197
|
const artifact = resolved?.artifact ?? await client.getArtifact(ask.artifact_id);
|
|
15180
15198
|
return { ...documentResultDetails(artifact), ask: ask.id };
|
|
15181
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
|
+
}
|
|
15182
15213
|
async function followedAskDetails(client, ask, resolved) {
|
|
15183
15214
|
return { ...await askOwnerDetails(client, ask, resolved), follows: { ask: ask.id } };
|
|
15184
15215
|
}
|
|
@@ -15189,6 +15220,7 @@ var issueFreeTools = {
|
|
|
15189
15220
|
dispatch_issue: true,
|
|
15190
15221
|
dispatch_edit_ask: true,
|
|
15191
15222
|
dispatch_resolve_ask: true,
|
|
15223
|
+
dispatch_resolve_comment: true,
|
|
15192
15224
|
dispatch_follow: true,
|
|
15193
15225
|
dispatch_search: true,
|
|
15194
15226
|
dispatch_open_asks: true
|
|
@@ -15311,6 +15343,7 @@ function parseDispatchRef(ref) {
|
|
|
15311
15343
|
}
|
|
15312
15344
|
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15313
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";
|
|
15314
15347
|
var messageIdProblem = "in_reply_to must be a full message id (uuid) or a dispatch://KEY/message/<id> reference";
|
|
15315
15348
|
function askId(args) {
|
|
15316
15349
|
const ask = stringArg(args, "ask");
|
|
@@ -15366,6 +15399,13 @@ function argumentProblems(tool, args) {
|
|
|
15366
15399
|
}
|
|
15367
15400
|
break;
|
|
15368
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
|
+
}
|
|
15369
15409
|
case "dispatch_comment": {
|
|
15370
15410
|
if (optionalString(args, "quote") !== undefined && optionalString(args, "artifact") === undefined) {
|
|
15371
15411
|
problems.push("artifact is required when quote is supplied");
|
|
@@ -15593,7 +15633,7 @@ function approvalLine(artifact) {
|
|
|
15593
15633
|
return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
|
|
15594
15634
|
}
|
|
15595
15635
|
}
|
|
15596
|
-
function issueSummary(issue, events, references) {
|
|
15636
|
+
function issueSummary(issue, events, references, graph) {
|
|
15597
15637
|
const asks = issue.open_asks;
|
|
15598
15638
|
const spec = issue.artifacts?.find((artifact) => artifact.primary);
|
|
15599
15639
|
const specApproval = spec === undefined ? undefined : approvalLine(spec);
|
|
@@ -15613,10 +15653,35 @@ function issueSummary(issue, events, references) {
|
|
|
15613
15653
|
...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}`),
|
|
15614
15654
|
...typeof references === "string" || !references.truncated ? [] : ["- more references beyond 8 hops"],
|
|
15615
15655
|
"Events:",
|
|
15616
|
-
...events.length === 0 ? ["- none"] : events.map(eventLine)
|
|
15656
|
+
...events.length === 0 ? ["- none"] : events.map(eventLine),
|
|
15657
|
+
...graph
|
|
15617
15658
|
].join(`
|
|
15618
15659
|
`);
|
|
15619
15660
|
}
|
|
15661
|
+
function referenceLines(edges) {
|
|
15662
|
+
if (typeof edges === "string")
|
|
15663
|
+
return [`- ${edges}`];
|
|
15664
|
+
if (edges.length === 0)
|
|
15665
|
+
return ["- none"];
|
|
15666
|
+
return edges.map((edge) => {
|
|
15667
|
+
const excerpt = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
|
|
15668
|
+
return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
|
|
15669
|
+
});
|
|
15670
|
+
}
|
|
15671
|
+
async function graphEdges(client, query) {
|
|
15672
|
+
try {
|
|
15673
|
+
return (await client.getReferences(query)).edges;
|
|
15674
|
+
} catch (error) {
|
|
15675
|
+
return error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
15676
|
+
}
|
|
15677
|
+
}
|
|
15678
|
+
async function graphSections(client, ref) {
|
|
15679
|
+
const [incoming, outgoing] = await Promise.all([
|
|
15680
|
+
graphEdges(client, { to: ref }),
|
|
15681
|
+
graphEdges(client, { from: ref })
|
|
15682
|
+
]);
|
|
15683
|
+
return ["Referenced by:", ...referenceLines(incoming), "Links:", ...referenceLines(outgoing)];
|
|
15684
|
+
}
|
|
15620
15685
|
function logSummary(issue, events) {
|
|
15621
15686
|
return [
|
|
15622
15687
|
`Key: ${issue.key}`,
|
|
@@ -15664,7 +15729,7 @@ function childrenSummary(issue) {
|
|
|
15664
15729
|
].join(`
|
|
15665
15730
|
`);
|
|
15666
15731
|
}
|
|
15667
|
-
function askSummary({ ask, replies }) {
|
|
15732
|
+
function askSummary({ ask, replies }, graph) {
|
|
15668
15733
|
const answer = ask.answer;
|
|
15669
15734
|
const chain = replies.flatMap((reply) => [
|
|
15670
15735
|
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
@@ -15688,7 +15753,8 @@ function askSummary({ ask, replies }) {
|
|
|
15688
15753
|
`- Reason: ${ask.resolution.reason}`
|
|
15689
15754
|
],
|
|
15690
15755
|
"Replies:",
|
|
15691
|
-
...chain.length === 0 ? ["- none"] : chain
|
|
15756
|
+
...chain.length === 0 ? ["- none"] : chain,
|
|
15757
|
+
...graph
|
|
15692
15758
|
].join(`
|
|
15693
15759
|
`);
|
|
15694
15760
|
}
|
|
@@ -15731,7 +15797,7 @@ function formatOpenAsksSummary(response, baseUrl) {
|
|
|
15731
15797
|
].join(`
|
|
15732
15798
|
`);
|
|
15733
15799
|
}
|
|
15734
|
-
function commentSummary({ comment, replies }) {
|
|
15800
|
+
function commentSummary({ comment, replies }, graph) {
|
|
15735
15801
|
const root = [
|
|
15736
15802
|
`${comment.id} \xB7 ${comment.author.kind} ${comment.author.id}`,
|
|
15737
15803
|
...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
|
|
@@ -15742,10 +15808,16 @@ function commentSummary({ comment, replies }) {
|
|
|
15742
15808
|
...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
|
|
15743
15809
|
`Body: ${reply.body}`
|
|
15744
15810
|
]);
|
|
15745
|
-
return [
|
|
15811
|
+
return [
|
|
15812
|
+
"Comment:",
|
|
15813
|
+
...root,
|
|
15814
|
+
"Reply chain:",
|
|
15815
|
+
...chain.length === 0 ? ["- none"] : chain,
|
|
15816
|
+
...graph
|
|
15817
|
+
].join(`
|
|
15746
15818
|
`);
|
|
15747
15819
|
}
|
|
15748
|
-
function messageSummary({ message, replies }) {
|
|
15820
|
+
function messageSummary({ message, replies }, graph) {
|
|
15749
15821
|
const root = [
|
|
15750
15822
|
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
15751
15823
|
`Body: ${message.body}`
|
|
@@ -15754,7 +15826,13 @@ function messageSummary({ message, replies }) {
|
|
|
15754
15826
|
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
15755
15827
|
`Body: ${reply.body}`
|
|
15756
15828
|
]);
|
|
15757
|
-
return [
|
|
15829
|
+
return [
|
|
15830
|
+
"Message:",
|
|
15831
|
+
...root,
|
|
15832
|
+
"Reply chain:",
|
|
15833
|
+
...chain.length === 0 ? ["- none"] : chain,
|
|
15834
|
+
...graph
|
|
15835
|
+
].join(`
|
|
15758
15836
|
`);
|
|
15759
15837
|
}
|
|
15760
15838
|
async function openArtifactMarks(client, resolved) {
|
|
@@ -15907,6 +15985,23 @@ async function executeDispatchTool(input) {
|
|
|
15907
15985
|
details: await askOwnerDetails(client, ask)
|
|
15908
15986
|
};
|
|
15909
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
|
+
}
|
|
15910
16005
|
case "dispatch_ask": {
|
|
15911
16006
|
const anchorArgs = asObject(args.anchor);
|
|
15912
16007
|
const owner = documentOwner();
|
|
@@ -16134,8 +16229,9 @@ ${trailer.join(`
|
|
|
16134
16229
|
const ref = ownerArguments.ref;
|
|
16135
16230
|
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"));
|
|
16136
16231
|
const askRead = await client.getAsk(id);
|
|
16232
|
+
const askRef = ref.owner.kind === "issue" ? `dispatch://${ref.owner.issue}/ask/${id}` : `dispatch://${ref.owner.project}/artifact/${ref.artifact}/ask/${id}`;
|
|
16137
16233
|
return {
|
|
16138
|
-
text: askSummary(askRead),
|
|
16234
|
+
text: askSummary(askRead, await graphSections(client, askRef)),
|
|
16139
16235
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue }
|
|
16140
16236
|
};
|
|
16141
16237
|
}
|
|
@@ -16143,8 +16239,9 @@ ${trailer.join(`
|
|
|
16143
16239
|
const ref = ownerArguments.ref;
|
|
16144
16240
|
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));
|
|
16145
16241
|
const comment = await client.getComment(id);
|
|
16242
|
+
const commentRef = ref.owner.kind === "issue" ? `dispatch://${ref.owner.issue}/comment/${id}` : `dispatch://${ref.owner.project}/artifact/${ref.artifact}/comment/${id}`;
|
|
16146
16243
|
return {
|
|
16147
|
-
text: commentSummary(comment),
|
|
16244
|
+
text: commentSummary(comment, await graphSections(client, commentRef)),
|
|
16148
16245
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: comment.comment.issue_key }
|
|
16149
16246
|
};
|
|
16150
16247
|
}
|
|
@@ -16153,19 +16250,22 @@ ${trailer.join(`
|
|
|
16153
16250
|
throw new Error("message references are issue-scoped");
|
|
16154
16251
|
}
|
|
16155
16252
|
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
16253
|
+
const messageRef = `dispatch://${ownerArguments.ref.owner.issue}/message/${ownerArguments.ref.id}`;
|
|
16156
16254
|
return {
|
|
16157
|
-
text: messageSummary(messageRead),
|
|
16255
|
+
text: messageSummary(messageRead, await graphSections(client, messageRef)),
|
|
16158
16256
|
details: { issue: messageRead.message.issue_key }
|
|
16159
16257
|
};
|
|
16160
16258
|
}
|
|
16161
16259
|
if (documentOwner().kind === "project") {
|
|
16162
16260
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
16261
|
+
const documentRef = `dispatch://${resolved.artifact.project}/artifact/${resolved.artifact.slug}`;
|
|
16163
16262
|
return {
|
|
16164
16263
|
text: [
|
|
16165
16264
|
`Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
|
|
16166
|
-
`Reference:
|
|
16265
|
+
`Reference: ${documentRef}`,
|
|
16167
16266
|
`Versions: ${resolved.artifact.versions.length}`,
|
|
16168
|
-
...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)]
|
|
16267
|
+
...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)],
|
|
16268
|
+
...await graphSections(client, documentRef)
|
|
16169
16269
|
].join(`
|
|
16170
16270
|
`),
|
|
16171
16271
|
details: {
|
|
@@ -16194,7 +16294,7 @@ ${trailer.join(`
|
|
|
16194
16294
|
references = error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
16195
16295
|
}
|
|
16196
16296
|
return {
|
|
16197
|
-
text: issueSummary(read.issue, read.events, references),
|
|
16297
|
+
text: issueSummary(read.issue, read.events, references, await graphSections(client, `dispatch://${read.issue.key}`)),
|
|
16198
16298
|
details: { issue: read.issue.key }
|
|
16199
16299
|
};
|
|
16200
16300
|
}
|
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
|
|
@@ -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
|