@sjawhar/opencode-legion-envoy 1.3.0 → 1.4.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.
@@ -13574,6 +13574,14 @@ var ArtifactVersionEventPayloadSchema = object({
13574
13574
  version: object({ number: number2().optional(), summary: string2().nullish() }).optional(),
13575
13575
  diff: string2().optional()
13576
13576
  });
13577
+ var ArtifactReviewEventPayloadSchema = object({
13578
+ artifact_id: string2().optional(),
13579
+ name: string2().optional(),
13580
+ version: number2().int().optional(),
13581
+ actor: object({ kind: string2(), id: string2() }).passthrough().optional(),
13582
+ reason: string2().nullish(),
13583
+ ask_id: string2().nullish()
13584
+ });
13577
13585
  var askEventPayloadFields = {
13578
13586
  id: string2().optional(),
13579
13587
  opened_event_id: number2().int().positive(),
@@ -13855,6 +13863,16 @@ var dispatchToolSpecs = [
13855
13863
  }),
13856
13864
  validation: documentOwnerValidation(true)
13857
13865
  },
13866
+ {
13867
+ name: "dispatch_request_approval",
13868
+ description: "Ask a human to approve a document at its current version - the exception path for a spec " + "that departs from what was settled or proposes children, not a step for every issue. Opens an " + "approval ask (Approve / Request changes) in the human's Inbox; the answer pins a review to the " + "document version and arrives as artifact.approved or artifact.changes_requested. A later edit " + "makes an approval stale; request again for the new version. Idempotent while a request is open. " + OWNER_REFERENCE,
13869
+ arguments: (z) => ({
13870
+ issue: z.string().describe(ISSUE_REFERENCE).optional(),
13871
+ project: z.string().describe("Project key owning the document.").optional(),
13872
+ artifact: z.string().describe("Artifact slug or id; primary document by default for an issue.").optional()
13873
+ }),
13874
+ validation: documentOwnerValidation(true)
13875
+ },
13858
13876
  {
13859
13877
  name: "dispatch_artifact",
13860
13878
  description: "Attach a local file or inline text as an issue artifact or project document. Do not use it to edit a live document; use " + `dispatch_doc_edit instead. Exactly one of path or content is required; artifacts are limited to 25 MiB. ${OWNER_REFERENCE}`,
@@ -14598,6 +14616,9 @@ class DispatchClient {
14598
14616
  async resolveAsk(id, input) {
14599
14617
  return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
14600
14618
  }
14619
+ async requestApproval(artifactID, input) {
14620
+ return this.#json("POST", ["api", "v1", "artifacts", artifactID, "approval-requests"], input);
14621
+ }
14601
14622
  async editAsk(id, input) {
14602
14623
  return this.#json("PATCH", ["api", "v1", "asks", id], input);
14603
14624
  }
@@ -15083,13 +15104,31 @@ function toolActor(origin, input) {
15083
15104
  }
15084
15105
  };
15085
15106
  }
15107
+ function approvalLine(artifact) {
15108
+ const approval = artifact.approval;
15109
+ if (approval === undefined || approval.state === "draft")
15110
+ return;
15111
+ switch (approval.state) {
15112
+ case "awaiting":
15113
+ return `Approval: awaiting (requested by ${approval.requested_by?.id ?? "unknown"}, ask ${approval.ask_id ?? "?"})`;
15114
+ case "approved":
15115
+ return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}`;
15116
+ case "stale":
15117
+ return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}, edited since (now v${approval.latest_version}) - request approval again`;
15118
+ case "changes_requested":
15119
+ return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
15120
+ }
15121
+ }
15086
15122
  function issueSummary(issue, events, references) {
15087
15123
  const asks = issue.open_asks;
15124
+ const spec = issue.artifacts?.find((artifact) => artifact.primary);
15125
+ const specApproval = spec === undefined ? undefined : approvalLine(spec);
15088
15126
  return [
15089
15127
  `Title: ${issue.title}`,
15090
15128
  `Key: ${issue.key}`,
15091
15129
  `Status: ${issue.status}`,
15092
15130
  `Route: ${issue.route ?? "none"}`,
15131
+ ...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
15093
15132
  "Open asks:",
15094
15133
  ...asks.length === 0 ? ["- none"] : asks.map((ask) => `- ${ask.id}: ${ask.question}`),
15095
15134
  "References:",
@@ -15416,16 +15455,42 @@ async function executeDispatchTool(input) {
15416
15455
  const version = optionalNumber(args, "version") ?? ownerArguments.ref?.version;
15417
15456
  const document = await client.docRead(resolved.artifact.id, version);
15418
15457
  const marks = await openArtifactMarks(client, resolved);
15458
+ const approval = approvalLine(resolved.artifact);
15459
+ const trailer = [
15460
+ ...marks.length === 0 ? [] : [`Open anchored asks/comments: ${marks.join(", ")}`],
15461
+ ...approval === undefined ? [] : [approval]
15462
+ ];
15419
15463
  return {
15420
- text: marks.length === 0 ? document.markdown : `${document.markdown}
15464
+ text: trailer.length === 0 ? document.markdown : `${document.markdown}
15421
15465
 
15422
- Open anchored asks/comments: ${marks.join(", ")}`,
15466
+ ${trailer.join(`
15467
+ `)}`,
15423
15468
  details: resolved.owner.kind === "project" ? {
15424
15469
  project: resolved.artifact.project,
15425
15470
  document: `${resolved.artifact.project}/${resolved.artifact.slug}`
15426
15471
  } : { issue: resolved.issue?.key }
15427
15472
  };
15428
15473
  }
15474
+ case "dispatch_request_approval": {
15475
+ const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
15476
+ const resolved = await resolveArtifact(client, documentOwner(), artifactReference);
15477
+ const result = await client.requestApproval(resolved.artifact.id, { actor });
15478
+ if (result.ask === null) {
15479
+ return {
15480
+ text: `${resolved.artifact.name} is already approved at version ${result.version} by ${result.approval.by?.id ?? "unknown"}; no new request was opened. An edit after approval makes it stale, so request again only for a new version.`,
15481
+ details: {
15482
+ ...resolved.owner.kind === "project" ? documentResultDetails(resolved.artifact) : { issue: resolved.issue?.key },
15483
+ artifact: resolved.artifact.id,
15484
+ version: result.version
15485
+ }
15486
+ };
15487
+ }
15488
+ const details = await askResultDetails(client, result.ask, resolved);
15489
+ return {
15490
+ text: `Approval requested for ${resolved.artifact.name} at version ${result.version} (ask ${result.ask.id}). The answer arrives as artifact.approved or artifact.changes_requested; an edit after approval makes it stale, so request again for the new version.`,
15491
+ details: { ...details, artifact: resolved.artifact.id, version: result.version }
15492
+ };
15493
+ }
15429
15494
  case "dispatch_artifact": {
15430
15495
  const summary = optionalString(args, "summary");
15431
15496
  const name = stringArg(args, "name");
@@ -15488,7 +15553,8 @@ Open anchored asks/comments: ${marks.join(", ")}`,
15488
15553
  text: [
15489
15554
  `Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
15490
15555
  `Reference: dispatch://${resolved.artifact.project}/artifact/${resolved.artifact.slug}`,
15491
- `Versions: ${resolved.artifact.versions.length}`
15556
+ `Versions: ${resolved.artifact.versions.length}`,
15557
+ ...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)]
15492
15558
  ].join(`
15493
15559
  `),
15494
15560
  details: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -123,6 +123,22 @@ clarification, not an answer: the human did not understand the question or needs
123
123
  in their Inbox. Answer in the same thread with `dispatch_comment({ reply_to_ask })`, or reword the question itself with
124
124
  `dispatch_edit_ask` when the wording was the problem; either puts the ask back in front of them. Do not open a second ask.
125
125
 
126
+ ## Approval of a spec
127
+
128
+ Approval is a property of a document, not a question you phrase: a human approves a specific version, the way a pull-request
129
+ review approves a commit, and any later edit makes that approval stale. It is the exception, not a step for every issue - reach
130
+ for it when a spec departs from what the human already settled, proposes children, or when the project has armed a design gate.
131
+
132
+ ```
133
+ dispatch_request_approval({ issue?, project?, artifact? })
134
+ ```
135
+
136
+ Opens (or returns the open) approval ask for the document at its latest version - options `Approve` and `Request changes`, in
137
+ the human's Inbox like any ask. The answer reaches you as `artifact.approved` or `artifact.changes_requested` with the pinned
138
+ `version`; `changes_requested` carries the reason, which is your next piece of work. `dispatch_read` and `dispatch_doc_read` show
139
+ the document's approval state; `stale` means it was approved and then edited - request again for the new version. Never write
140
+ "Approve" options into an ordinary `dispatch_ask`, and never approve anything yourself: only humans review.
141
+
126
142
  ## The Spec
127
143
 
128
144
  The spec holds requirements, design, acceptance, decisions, and rejected alternatives, structured per [Writing a spec](#writing-a-spec).