@sjawhar/opencode-legion-envoy 1.27.5 → 1.28.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.
@@ -13966,7 +13966,7 @@ var dispatchToolSpecs = [
13966
13966
  },
13967
13967
  {
13968
13968
  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,
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. " + "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
13970
  arguments: (z) => ({
13971
13971
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
13972
13972
  project: z.string().describe("Project key owning the document.").optional(),
@@ -14914,6 +14914,13 @@ class DispatchClient {
14914
14914
  "references"
14915
14915
  ]);
14916
14916
  }
14917
+ async getReferences(query) {
14918
+ return this.#json("GET", ["api", "v1", "references"], undefined, {
14919
+ ..."to" in query ? { to: query.to } : { from: query.from },
14920
+ ...query.kind === undefined || query.kind.length === 0 ? {} : { kind: query.kind.join(",") },
14921
+ ...query.since === undefined ? {} : { since: query.since }
14922
+ });
14923
+ }
14917
14924
  async ensureIssue(issueReference, actor) {
14918
14925
  if (!issueReference.includes("#"))
14919
14926
  return issueReference;
@@ -15593,7 +15600,7 @@ function approvalLine(artifact) {
15593
15600
  return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
15594
15601
  }
15595
15602
  }
15596
- function issueSummary(issue, events, references) {
15603
+ function issueSummary(issue, events, references, graph) {
15597
15604
  const asks = issue.open_asks;
15598
15605
  const spec = issue.artifacts?.find((artifact) => artifact.primary);
15599
15606
  const specApproval = spec === undefined ? undefined : approvalLine(spec);
@@ -15613,10 +15620,35 @@ function issueSummary(issue, events, references) {
15613
15620
  ...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
15621
  ...typeof references === "string" || !references.truncated ? [] : ["- more references beyond 8 hops"],
15615
15622
  "Events:",
15616
- ...events.length === 0 ? ["- none"] : events.map(eventLine)
15623
+ ...events.length === 0 ? ["- none"] : events.map(eventLine),
15624
+ ...graph
15617
15625
  ].join(`
15618
15626
  `);
15619
15627
  }
15628
+ function referenceLines(edges) {
15629
+ if (typeof edges === "string")
15630
+ return [`- ${edges}`];
15631
+ if (edges.length === 0)
15632
+ return ["- none"];
15633
+ return edges.map((edge) => {
15634
+ const excerpt = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
15635
+ return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
15636
+ });
15637
+ }
15638
+ async function graphEdges(client, query) {
15639
+ try {
15640
+ return (await client.getReferences(query)).edges;
15641
+ } catch (error) {
15642
+ return error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
15643
+ }
15644
+ }
15645
+ async function graphSections(client, ref) {
15646
+ const [incoming, outgoing] = await Promise.all([
15647
+ graphEdges(client, { to: ref }),
15648
+ graphEdges(client, { from: ref })
15649
+ ]);
15650
+ return ["Referenced by:", ...referenceLines(incoming), "Links:", ...referenceLines(outgoing)];
15651
+ }
15620
15652
  function logSummary(issue, events) {
15621
15653
  return [
15622
15654
  `Key: ${issue.key}`,
@@ -15664,7 +15696,7 @@ function childrenSummary(issue) {
15664
15696
  ].join(`
15665
15697
  `);
15666
15698
  }
15667
- function askSummary({ ask, replies }) {
15699
+ function askSummary({ ask, replies }, graph) {
15668
15700
  const answer = ask.answer;
15669
15701
  const chain = replies.flatMap((reply) => [
15670
15702
  `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
@@ -15688,7 +15720,8 @@ function askSummary({ ask, replies }) {
15688
15720
  `- Reason: ${ask.resolution.reason}`
15689
15721
  ],
15690
15722
  "Replies:",
15691
- ...chain.length === 0 ? ["- none"] : chain
15723
+ ...chain.length === 0 ? ["- none"] : chain,
15724
+ ...graph
15692
15725
  ].join(`
15693
15726
  `);
15694
15727
  }
@@ -15731,7 +15764,7 @@ function formatOpenAsksSummary(response, baseUrl) {
15731
15764
  ].join(`
15732
15765
  `);
15733
15766
  }
15734
- function commentSummary({ comment, replies }) {
15767
+ function commentSummary({ comment, replies }, graph) {
15735
15768
  const root = [
15736
15769
  `${comment.id} \xB7 ${comment.author.kind} ${comment.author.id}`,
15737
15770
  ...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
@@ -15742,10 +15775,16 @@ function commentSummary({ comment, replies }) {
15742
15775
  ...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
15743
15776
  `Body: ${reply.body}`
15744
15777
  ]);
15745
- return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
15778
+ return [
15779
+ "Comment:",
15780
+ ...root,
15781
+ "Reply chain:",
15782
+ ...chain.length === 0 ? ["- none"] : chain,
15783
+ ...graph
15784
+ ].join(`
15746
15785
  `);
15747
15786
  }
15748
- function messageSummary({ message, replies }) {
15787
+ function messageSummary({ message, replies }, graph) {
15749
15788
  const root = [
15750
15789
  `${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
15751
15790
  `Body: ${message.body}`
@@ -15754,7 +15793,13 @@ function messageSummary({ message, replies }) {
15754
15793
  `${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
15755
15794
  `Body: ${reply.body}`
15756
15795
  ]);
15757
- return ["Message:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
15796
+ return [
15797
+ "Message:",
15798
+ ...root,
15799
+ "Reply chain:",
15800
+ ...chain.length === 0 ? ["- none"] : chain,
15801
+ ...graph
15802
+ ].join(`
15758
15803
  `);
15759
15804
  }
15760
15805
  async function openArtifactMarks(client, resolved) {
@@ -16134,8 +16179,9 @@ ${trailer.join(`
16134
16179
  const ref = ownerArguments.ref;
16135
16180
  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
16181
  const askRead = await client.getAsk(id);
16182
+ const askRef = ref.owner.kind === "issue" ? `dispatch://${ref.owner.issue}/ask/${id}` : `dispatch://${ref.owner.project}/artifact/${ref.artifact}/ask/${id}`;
16137
16183
  return {
16138
- text: askSummary(askRead),
16184
+ text: askSummary(askRead, await graphSections(client, askRef)),
16139
16185
  details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue }
16140
16186
  };
16141
16187
  }
@@ -16143,8 +16189,9 @@ ${trailer.join(`
16143
16189
  const ref = ownerArguments.ref;
16144
16190
  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
16191
  const comment = await client.getComment(id);
16192
+ const commentRef = ref.owner.kind === "issue" ? `dispatch://${ref.owner.issue}/comment/${id}` : `dispatch://${ref.owner.project}/artifact/${ref.artifact}/comment/${id}`;
16146
16193
  return {
16147
- text: commentSummary(comment),
16194
+ text: commentSummary(comment, await graphSections(client, commentRef)),
16148
16195
  details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: comment.comment.issue_key }
16149
16196
  };
16150
16197
  }
@@ -16153,19 +16200,22 @@ ${trailer.join(`
16153
16200
  throw new Error("message references are issue-scoped");
16154
16201
  }
16155
16202
  const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
16203
+ const messageRef = `dispatch://${ownerArguments.ref.owner.issue}/message/${ownerArguments.ref.id}`;
16156
16204
  return {
16157
- text: messageSummary(messageRead),
16205
+ text: messageSummary(messageRead, await graphSections(client, messageRef)),
16158
16206
  details: { issue: messageRead.message.issue_key }
16159
16207
  };
16160
16208
  }
16161
16209
  if (documentOwner().kind === "project") {
16162
16210
  const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
16211
+ const documentRef = `dispatch://${resolved.artifact.project}/artifact/${resolved.artifact.slug}`;
16163
16212
  return {
16164
16213
  text: [
16165
16214
  `Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
16166
- `Reference: dispatch://${resolved.artifact.project}/artifact/${resolved.artifact.slug}`,
16215
+ `Reference: ${documentRef}`,
16167
16216
  `Versions: ${resolved.artifact.versions.length}`,
16168
- ...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)]
16217
+ ...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)],
16218
+ ...await graphSections(client, documentRef)
16169
16219
  ].join(`
16170
16220
  `),
16171
16221
  details: {
@@ -16194,7 +16244,7 @@ ${trailer.join(`
16194
16244
  references = error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
16195
16245
  }
16196
16246
  return {
16197
- text: issueSummary(read.issue, read.events, references),
16247
+ text: issueSummary(read.issue, read.events, references, await graphSections(client, `dispatch://${read.issue.key}`)),
16198
16248
  details: { issue: read.issue.key }
16199
16249
  };
16200
16250
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "1.27.5",
3
+ "version": "1.28.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -475,6 +475,14 @@ document owner or ref, it returns a document summary with `details` `{ project,
475
475
  question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
476
476
  message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
477
477
 
478
+ Every read ends with two sections from the reference graph. `Referenced by:` lists what points at the node — every document, ask,
479
+ comment, or message that cites it, plus its structure: child issues, attached documents, anchored and owned asks and comments, replies,
480
+ followers — and `Links:` lists what it cites. Each row is `- <edge kind> <node kind> dispatch://… (<excerpt> · <when>)`; for a
481
+ document source the excerpt is the block containing the mention. Cross-project, always: a message on another project's issue that
482
+ 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
483
+ document" one read on the document. Cite with `dispatch://` references (below) whenever you name a node in a body — a bare id or
484
+ title is invisible to the graph.
485
+
478
486
  ## References
479
487
 
480
488
  Use these in document, ask, comment, and message bodies. In the dashboard, a reference renders