@sjawhar/opencode-legion-envoy 5.2.0 → 5.2.2
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/dist/src/server.js +42 -1
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +4 -4
- package/skills/legion-worker/SKILL.md +2 -2
- package/skills/legion-worker/references/merge-gate.md +1 -1
- package/skills/legion-worker/references/pr-body.md +1 -1
- package/skills/legion-worker/references/review-threads.md +29 -17
package/dist/src/server.js
CHANGED
|
@@ -14238,7 +14238,7 @@ var dispatchToolSpecs = [
|
|
|
14238
14238
|
{
|
|
14239
14239
|
name: "dispatch_doc_read",
|
|
14240
14240
|
example: { issue: "DSP-1" },
|
|
14241
|
-
description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + OWNER_REFERENCE,
|
|
14241
|
+
description: "Read a live document or a named document version, or the text of an uploaded file at its latest or named version. " + "Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + "A file that is not UTF-8 text is described, with the route that serves its bytes. " + OWNER_REFERENCE,
|
|
14242
14242
|
arguments: (z2) => ({
|
|
14243
14243
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14244
14244
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -15026,6 +15026,20 @@ class DispatchClient {
|
|
|
15026
15026
|
async docRead(id, version2) {
|
|
15027
15027
|
return version2 === undefined ? this.#json("GET", ["api", "v1", "artifacts", id, "text"]) : this.#json("GET", ["api", "v1", "artifacts", id, "versions", String(version2)]);
|
|
15028
15028
|
}
|
|
15029
|
+
async fileVersion(id, version2) {
|
|
15030
|
+
const url2 = this.#url(["api", "v1", "artifacts", id, "versions", String(version2)]);
|
|
15031
|
+
const response = await this.fetchImpl(url2, {
|
|
15032
|
+
method: "GET",
|
|
15033
|
+
headers: { Accept: "*/*", Authorization: `Bearer ${this.token}` },
|
|
15034
|
+
signal: this.#signal
|
|
15035
|
+
});
|
|
15036
|
+
if (!response.ok)
|
|
15037
|
+
return this.#response("GET", url2, response);
|
|
15038
|
+
return {
|
|
15039
|
+
mime: response.headers.get("Content-Type") ?? "application/octet-stream",
|
|
15040
|
+
bytes: new Uint8Array(await response.arrayBuffer())
|
|
15041
|
+
};
|
|
15042
|
+
}
|
|
15029
15043
|
async artifactBlocks(id) {
|
|
15030
15044
|
return this.#json("GET", ["api", "v1", "artifacts", id, "blocks"]);
|
|
15031
15045
|
}
|
|
@@ -16308,6 +16322,30 @@ async function blockAsks(client, resolved, state) {
|
|
|
16308
16322
|
const asks = await (resolved.issue === undefined ? client.getArtifactAsks(resolved.artifact.id, state) : client.listIssueAsks(resolved.issue.key, state));
|
|
16309
16323
|
return asks.filter((ask) => typeof ask.block_id === "string" && ask.block_artifact?.id === resolved.artifact.id);
|
|
16310
16324
|
}
|
|
16325
|
+
async function readUploadedFile(client, resolved, requested) {
|
|
16326
|
+
const { artifact } = resolved;
|
|
16327
|
+
const latest = Math.max(0, ...artifact.versions.map((version2) => version2.number));
|
|
16328
|
+
const number4 = requested ?? latest;
|
|
16329
|
+
const file2 = await client.fileVersion(artifact.id, number4);
|
|
16330
|
+
const of = number4 === latest ? "" : ` of ${latest}`;
|
|
16331
|
+
const size = `${file2.bytes.length.toLocaleString("en-US")} bytes`;
|
|
16332
|
+
const details = resolved.owner.kind === "project" ? { project: artifact.project, document: documentLabel(artifact.project, artifact.slug) } : { issue: resolved.issue?.key };
|
|
16333
|
+
let text;
|
|
16334
|
+
try {
|
|
16335
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(file2.bytes);
|
|
16336
|
+
} catch {
|
|
16337
|
+
return {
|
|
16338
|
+
text: `${artifact.name} is an uploaded ${file2.mime} file (version ${number4}${of}, ${size}) that is not ` + `UTF-8 text, so dispatch_doc_read cannot show it. GET /api/v1/artifacts/${artifact.id}/versions/${number4} serves its bytes.`,
|
|
16339
|
+
details
|
|
16340
|
+
};
|
|
16341
|
+
}
|
|
16342
|
+
return {
|
|
16343
|
+
text: `File ${artifact.name}: ${file2.mime}, version ${number4}${of}, ${size}.
|
|
16344
|
+
|
|
16345
|
+
${text}`,
|
|
16346
|
+
details
|
|
16347
|
+
};
|
|
16348
|
+
}
|
|
16311
16349
|
async function refuseOpenDecisionBlocks(client, tool, resolved) {
|
|
16312
16350
|
const artifact = resolved.artifact;
|
|
16313
16351
|
const latest = artifact.approval?.latest_version;
|
|
@@ -17005,6 +17043,9 @@ ${followsAsk(askOwner)}`,
|
|
|
17005
17043
|
const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
|
|
17006
17044
|
const resolved = await resolveDocument(documentOwner(), artifactReference);
|
|
17007
17045
|
const version2 = optionalNumber(args, "version") ?? ownerArguments.ref?.version;
|
|
17046
|
+
if (resolved.artifact.kind === "file" || resolved.artifact.kind === "image") {
|
|
17047
|
+
return readUploadedFile(client, resolved, version2);
|
|
17048
|
+
}
|
|
17008
17049
|
const documentPromise = client.docRead(resolved.artifact.id, version2);
|
|
17009
17050
|
const marksPromise = openArtifactMarks(client, resolved);
|
|
17010
17051
|
const marksResultPromise = marksPromise.then((value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason }));
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -418,10 +418,10 @@ Read the current document before changing it:
|
|
|
418
418
|
```ts
|
|
419
419
|
dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
420
420
|
```
|
|
421
|
-
It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
421
|
+
It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an omitted
|
|
422
|
+
`artifact` reads the issue specification; a project needs `artifact`; and a `dispatch://PROJECT/artifact/<document-ref>`
|
|
423
|
+
ref supplies both, where `document-ref` is the slug (an id or a filename resolves when no document has that slug). A file
|
|
424
|
+
`dispatch_artifact` uploaded reads its text at the latest or named version, or a description when it is not UTF-8 text.
|
|
425
425
|
|
|
426
426
|
Editing one is [Editing a document](skill://dispatch/references/document-edits.md): the shape of `dispatch_doc_edit`,
|
|
427
427
|
how to quote the text you mean, one `replace` per paragraph, preconditions against a stale edit, and
|
|
@@ -304,8 +304,8 @@ line), the full definition of a proof, what the tester verifies, and the simplif
|
|
|
304
304
|
|
|
305
305
|
- **Review threads** are disposed of one by one, never in bulk, and only an `Accepted:` from the
|
|
306
306
|
thread's opener (or, on a bot's thread, from the Legion reviewer) closes one. The implementer
|
|
307
|
-
runs `legion threads resolve`
|
|
308
|
-
READY: `skill://legion-worker/references/review-threads.md`.
|
|
307
|
+
runs `legion threads resolve` after every push that answers a review, before its completion,
|
|
308
|
+
and the merger before READY: `skill://legion-worker/references/review-threads.md`.
|
|
309
309
|
- **No deferrals.** A finding that changes
|
|
310
310
|
behaviour, hides an error, or breaks a gate is fixed in this pull request; naming, duplication,
|
|
311
311
|
or wording cleanup is batched into the one `Fast-follow:` line instead of iterating per push.
|
|
@@ -39,7 +39,7 @@ confirmation or a new round, as the fingerprint decides (*The reviewer*, below,
|
|
|
39
39
|
nothing restarts. Different: a new round — thermo again, one review.
|
|
40
40
|
- Answer every thread you opened, and every thread a bot opened that is none of Legion's role
|
|
41
41
|
Apps, as `skill://legion-worker/references/review-threads.md` says; the same reference says
|
|
42
|
-
when every thread is settled enough to approve.
|
|
42
|
+
when every thread is settled enough to approve, and resolving one never gates your approval.
|
|
43
43
|
|
|
44
44
|
A reviewer's phase ends with its completion, not with its review. A round that writes a handoff
|
|
45
45
|
takes this order: write, commit and push the handoff; submit the review of the head that push
|
|
@@ -29,7 +29,7 @@ later phase keeps it current rather than replacing it:
|
|
|
29
29
|
**Threads:** <n> resolved, 0 unresolved. Each disposed individually, never in bulk:
|
|
30
30
|
- Thread <id>: fixed in <commit-sha> — <one line>.
|
|
31
31
|
- Thread <id>: not a defect — <reason>.
|
|
32
|
-
`legion threads resolve --pr <n> --repo <owner>/<repo
|
|
32
|
+
`legion threads resolve --pr <n> --repo <owner>/<repo>`, run after the push that made <head-sha>:
|
|
33
33
|
resolved <thread URL> — its opener's acceptance
|
|
34
34
|
resolved <thread URL> — the Legion reviewer's acceptance of a bot's thread
|
|
35
35
|
left open <thread URL> — newest reply by <login> is not an acceptance
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Review threads
|
|
2
2
|
|
|
3
3
|
Part of `skill://legion-worker`. Read it when you reply to, accept, or resolve a review thread,
|
|
4
|
-
or run `legion threads resolve`: the implementer
|
|
5
|
-
|
|
4
|
+
or run `legion threads resolve`: the implementer after every push that answers a review, the
|
|
5
|
+
merger before READY, and the reviewer, who answers threads on every re-review and runs nothing.
|
|
6
|
+
Every path it cites is in sjawhar/legion.
|
|
6
7
|
|
|
7
8
|
- **Threads are dispositioned individually, never resolved in bulk.** Every open review
|
|
8
9
|
thread gets its own line naming the fixing commit or the reason it isn't a defect. The
|
|
@@ -19,16 +20,19 @@ reviewer on every re-review, the merger before READY. Every path it cites is in
|
|
|
19
20
|
When neither is set, add `--gh` to that command, which applies the fallback's rule below through
|
|
20
21
|
your own `gh`; where no `legion` command is installed, use `gh api graphql` with the session's
|
|
21
22
|
GitHub credential and the fallback below.
|
|
22
|
-
In a Legion pane, the **implementer** runs the command
|
|
23
|
-
(the corrective push and the final `.legion/` deletion push
|
|
24
|
-
`
|
|
25
|
-
the
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
finding
|
|
23
|
+
In a Legion pane, the **implementer** runs the command after every push that answers a review
|
|
24
|
+
(the corrective push, and the final `.legion/` deletion push where the daemon has one) and
|
|
25
|
+
before its `handoff_complete`, and pastes its output, stamped with the head it just pushed, into
|
|
26
|
+
the `Threads` section. The output is then recorded against the head the reviewer will read, and
|
|
27
|
+
nothing reads thread state before the implementer's completion. The command resolves each
|
|
28
|
+
unresolved thread whose newest submitted comment is the opener's own `Accepted:` reply. On a
|
|
29
|
+
thread a bot account opened that is none of Legion's role Apps (the daemon names them, keyed by
|
|
30
|
+
App role), the Legion reviewer's `Accepted:` also closes it. GitHub cannot tell a CI bot, which
|
|
31
|
+
never accepts, from a person whose `gh` is routed to an App, so the reviewer adjudicates such a
|
|
32
|
+
finding, and it may accept one an App-routed person raised. The subject of a finding never
|
|
33
|
+
closes it: the implementer's `Fixed in <commit>: …` or `Declined: …` answers a thread and closes
|
|
34
|
+
none. A thread either Legion App opened, a reviewer's finding included, still needs its opener's
|
|
35
|
+
`Accepted:`. It makes one `resolveReviewThread` per
|
|
32
36
|
thread, prints `resolved <url> — <whose acceptance>` (its opener's, or the Legion reviewer's on a
|
|
33
37
|
bot's thread, so the ledger shows which) or `left open <url> — newest reply by <login> is …`
|
|
34
38
|
naming why, and exits 1 naming the thread's URL and GitHub's message when GitHub refuses one.
|
|
@@ -63,10 +67,18 @@ reviewer on every re-review, the merger before READY. Every path it cites is in
|
|
|
63
67
|
Re-read `reviewThreads` and confirm that thread's `isResolved` is true. In either route, report
|
|
64
68
|
a refused resolution to the architect, which opens an ask for a human to resolve the thread by
|
|
65
69
|
hand — never skip it silently. The merger runs the command once more before publishing READY
|
|
66
|
-
and does not publish while any `left open` line remains.
|
|
70
|
+
and does not publish while any `left open` line remains. That run is where every accepted
|
|
71
|
+
thread's resolution is guaranteed, since the merge queue's gate counts the unresolved threads at
|
|
72
|
+
the head. Acceptances posted after the implementer's last run are resolved here.
|
|
67
73
|
|
|
68
74
|
- **The reviewer, on a re-review.** When you re-review after a corrective push, answer every
|
|
69
|
-
thread you opened
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
thread you opened, and every thread a bot opened that is none of Legion's role Apps, in one of
|
|
76
|
+
the three forms above — `Accepted:` is the only reply `legion threads resolve` acts on. A bot's
|
|
77
|
+
finding you cannot accept becomes your own: leave it `Still open:` and request changes.
|
|
78
|
+
Approve once each of those threads has your own `Accepted:` as its newest submitted comment,
|
|
79
|
+
whether or not GitHub shows the thread resolved yet, and every other unresolved thread its
|
|
80
|
+
opener's (read the newest comments with `gh api graphql`, never from the PR body). Another
|
|
81
|
+
opener's thread that a person resolved with GitHub's button, with no `Accepted:`, gates nothing:
|
|
82
|
+
neither `legion threads resolve` nor the merge queue's gate counts a resolved thread. Resolution
|
|
83
|
+
is the pull request author's App's, so your approval never waits on it. The merger resolves
|
|
84
|
+
accepted threads that remain open before publishing READY.
|