@sjawhar/opencode-legion-envoy 3.2.8 → 3.2.9
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
CHANGED
|
@@ -14117,7 +14117,7 @@ var dispatchToolSpecs = [
|
|
|
14117
14117
|
artifact: z2.string().describe("Project document artifact id, slug, or filename containing the quoted text; optional when ref names it.").optional(),
|
|
14118
14118
|
ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
14119
14119
|
quote: z2.string().describe("Exact document text to replace."),
|
|
14120
|
-
replace_with: z2.string().describe("Replacement text."),
|
|
14120
|
+
replace_with: z2.string().describe("Replacement text; a CR LF or a lone carriage return in it is written as a line feed."),
|
|
14121
14121
|
body: z2.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
14122
14122
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
14123
14123
|
}),
|
|
@@ -14142,7 +14142,7 @@ var dispatchToolSpecs = [
|
|
|
14142
14142
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
14143
14143
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
14144
14144
|
},
|
|
14145
|
-
description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + `For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, \`code\`) is tolerated and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose -
|
|
14145
|
+
description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + `For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, \`code\`) is tolerated and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids and their tokens come from GET /api/v1/artifacts/{artifact UUID}/blocks (the route takes the artifact UUID, not its slug). ' + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch_doc_read, or block {id, token} values from /blocks. A block guard must include every block the batch changes; Dispatch resolves quote targets and rejects an uncovered batch rather than applying it. Use a document token for insert or move, which depend on document order. Prefer block tokens when the covered content blocks are independent sections. Tokens include inline marks, so a fresh human comment also makes a stale edit fail. PRECONDITION_FAILED means re-read; EDIT_QUEUE_FULL means back off before retrying. " + "The result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch_doc_read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
14146
14146
|
arguments: (z2) => ({
|
|
14147
14147
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14148
14148
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14151,15 +14151,15 @@ var dispatchToolSpecs = [
|
|
|
14151
14151
|
ops: z2.array(z2.object({
|
|
14152
14152
|
op: z2.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
14153
14153
|
find: z2.string().describe("Text of the target as rendered, for replace or delete; inline markdown (**bold**, `code`) is tolerated and must be balanced; a leading '# ' matches a heading. A delete of a block's entire text removes the block.").optional(),
|
|
14154
|
-
with: z2.string().describe("Replacement text for replace: inside a code block, the code's literal text as sent (line breaks at its end do not survive a read); text that would read as block syntax at a line start, such as '---' over a paragraph, is stored escaped and reads back as those characters, so a rule is added with insert beside the paragraph; elsewhere parsed as inline markdown within the matched block; a marker of a different kind from the block's own is literal text, one of the same kind is refused unless it is a heading rename (where a level named by find is what lets with change it), a backslash escape keeps prose that merely looks like a marker, a block marker after a hard line break is refused because replace cannot open a new block, and any non-empty value that renders to no text is refused - only an empty value deletes the match.").optional(),
|
|
14154
|
+
with: z2.string().describe("Replacement text for replace: inside a code block, the code's literal text as sent (line breaks at its end do not survive a read); text that would read as block syntax at a line start, such as '---' over a paragraph, is stored escaped and reads back as those characters, so a rule is added with insert beside the paragraph; elsewhere parsed as inline markdown within the matched block; a marker of a different kind from the block's own is literal text, one of the same kind is refused unless it is a heading rename (where a level named by find is what lets with change it), a backslash escape keeps prose that merely looks like a marker, a block marker after a hard line break is refused because replace cannot open a new block, and any non-empty value that renders to no text is refused - only an empty value deletes the match. A CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
14155
14155
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
14156
|
-
markdown: z2.string().describe("Markdown to insert.").optional(),
|
|
14156
|
+
markdown: z2.string().describe("Markdown to insert; a CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
14157
14157
|
after: z2.string().describe(`Insert or move after this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>", "block:<id>".`).optional(),
|
|
14158
14158
|
before: z2.string().describe(`Insert or move before this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>", "block:<id>".`).optional(),
|
|
14159
14159
|
block: z2.string().describe("Block id for retype, delete, move, delete_row, or delete_column: the #id of a typed block, or an id from GET /api/v1/artifacts/{id}/blocks.").optional(),
|
|
14160
14160
|
index: z2.number({ int: true, min: 0 }).describe("Zero-based row or column index for delete_row or delete_column.").optional(),
|
|
14161
14161
|
type: z2.string().describe("Typed block name for retype.").optional(),
|
|
14162
|
-
attributes: z2.unknown().describe("Typed block attributes for retype.").optional()
|
|
14162
|
+
attributes: z2.unknown().describe("Typed block attributes for retype; a CR LF or a lone carriage return in a string value is written as a line feed.").optional()
|
|
14163
14163
|
}, { strict: true })).describe("Flat tagged edits; an operation takes only the keys below, a misspelled one is refused rather than ignored, and the server validates the fields its op requires."),
|
|
14164
14164
|
precondition: z2.object({
|
|
14165
14165
|
document: z2.string({ min: 1 }).describe("Token for the exact canonical document returned by dispatch_doc_read.").optional(),
|
|
@@ -14206,7 +14206,7 @@ var dispatchToolSpecs = [
|
|
|
14206
14206
|
project: z2.string().describe("Project key for an unlinked document.").optional(),
|
|
14207
14207
|
name: z2.string().describe("Artifact filename shown in Dispatch."),
|
|
14208
14208
|
path: z2.string().describe("Local path to the file to upload.").optional(),
|
|
14209
|
-
content: z2.string().describe("Inline text to store as a Markdown document.").optional(),
|
|
14209
|
+
content: z2.string().describe("Inline text to store as a Markdown document; a CR LF or a lone carriage return in it is stored as a line feed.").optional(),
|
|
14210
14210
|
summary: z2.string().describe("Optional version summary.").optional()
|
|
14211
14211
|
}),
|
|
14212
14212
|
validation: {
|
package/package.json
CHANGED
|
@@ -66,15 +66,17 @@ heading's actual level — `find="## Old"`, `with="### New"` retitles and makes
|
|
|
66
66
|
so `find="# Old"` renames the text and keeps whatever level it selected. `with` that forms more than one
|
|
67
67
|
paragraph is rejected (`INVALID_OP` on `with`) — see the recipe for a multi-paragraph rewrite below; so is any non-empty `with` that
|
|
68
68
|
renders to no text, which a line indented four spaces or a tab does (markdown reads that as a code block), as does whitespace
|
|
69
|
-
alone. An empty `with` deletes the matched text on purpose;
|
|
70
|
-
|
|
69
|
+
alone. An empty `with` deletes the matched text on purpose; a list item, quote, typed block or footnote definition left
|
|
70
|
+
holding only the emptied paragraph keeps it, and reads back holding it. Emptying a task item's first paragraph while
|
|
71
|
+
another block follows it in the item is `INVALID_OP`, since the browser editor reads no such item as a task. Inside a code
|
|
71
72
|
block none of this applies: `with` is the code's literal text, written as sent, whitespace, markdown syntax and
|
|
72
73
|
references included, except that line breaks at the end of the code's text, and a line holding only whitespace in a
|
|
73
74
|
list item's code, do not survive the next read. A line of colons in code inside a typed block is kept: Dispatch writes
|
|
74
75
|
that typed block's fence longer than any such line the browser editor would end it at. Text a `replace` writes that
|
|
75
76
|
would read as block syntax at a line start is stored escaped and reads back as the characters you sent: `---` over a
|
|
76
77
|
paragraph is stored `\---`, not a rule, so to add a rule, `insert` it beside the paragraph (`insert` with markdown
|
|
77
|
-
`***`).
|
|
78
|
+
`***`). A CR LF or a lone carriage return in any text you write - an insert's markdown, a `with` in text or in code,
|
|
79
|
+
a suggestion, an upload or a spec - is stored as a line feed, as the browser editor reads both. Use zero-based
|
|
78
80
|
`occurrence` for a
|
|
79
81
|
repeated target; re-read a missing or ambiguous target before retrying. Pass `summary` to name the version when recording a decision.
|
|
80
82
|
|
|
@@ -115,7 +117,7 @@ block before or after it. The delete is what costs the id (below); a typed block
|
|
|
115
117
|
carries it, which works only in that order, because an insert carrying an id the document still holds is refused.
|
|
116
118
|
Then read the document back with
|
|
117
119
|
`dispatch_doc_read` and read the passage and its neighbours, not a grep for the words you added: an empty
|
|
118
|
-
`with` deletes the matched text on purpose
|
|
120
|
+
`with` deletes the matched text on purpose, so a `replace` whose `with` you meant to fill
|
|
119
121
|
empties that paragraph — the block and its id stay, holding nothing — and only a read shows what the document now says.
|
|
120
122
|
|
|
121
123
|
A batch that leaves the document's semantic identity unchanged — including its inline anchor marks, so an edit that only orphans a
|
|
@@ -307,18 +307,46 @@ this proof.
|
|
|
307
307
|
reviewer answers each thread it opened with exactly one of `Accepted: fixed in <commit> — <one line>`,
|
|
308
308
|
`Accepted: not a defect — <reason>`, or `Still open: <what remains>`; nothing else is an
|
|
309
309
|
acceptance, and nobody replies after an `Accepted:` (any later reply that is not itself an
|
|
310
|
-
`Accepted:` — the opener's own follow-up included — leaves the thread open,
|
|
311
|
-
|
|
310
|
+
`Accepted:` — the opener's own follow-up included — leaves the thread open, because resolution
|
|
311
|
+
considers only the newest comment). The review App can reply on a thread but cannot resolve it:
|
|
312
312
|
GitHub grants resolving a review thread to the pull request's author, and the implementer opens
|
|
313
|
-
every Legion pull request (`packages/daemon/src/daemon/AGENTS.md`, GitHub Apps).
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
every Legion pull request (`packages/daemon/src/daemon/AGENTS.md`, GitHub Apps).
|
|
314
|
+
When `LEGION_GRANT_FILE` or `LEGION_GRANT` is set, use `legion threads resolve --pr <number> --repo <owner>/<repo>`; when neither is set, use `gh api graphql`
|
|
315
|
+
with the session's GitHub credential and the fallback below.
|
|
316
|
+
In a Legion pane, the **implementer** runs the command before every push that answers a review
|
|
317
|
+
(the corrective push and the final `.legion/` deletion push) and pastes its output into the
|
|
318
|
+
`Threads` section. The command resolves each unresolved thread whose newest submitted comment is
|
|
319
|
+
the opener's own `Accepted:` reply, one `resolveReviewThread` per thread, prints `resolved <url>`
|
|
320
|
+
or `left open <url> — newest reply by <login> is not an acceptance`, and exits 1 naming the
|
|
321
|
+
thread's URL and GitHub's message when GitHub refuses one.
|
|
322
|
+
|
|
323
|
+
Without a grant, page through `reviewThreads`, skip `isResolved: true`, and compare the opener
|
|
324
|
+
with the newest comment. Query shape, inside `repository { pullRequest { … } }`:
|
|
325
|
+
|
|
326
|
+
```graphql
|
|
327
|
+
reviewThreads(first: 100, after: $after) {
|
|
328
|
+
pageInfo { hasNextPage endCursor }
|
|
329
|
+
nodes {
|
|
330
|
+
id isResolved
|
|
331
|
+
opener: comments(first: 1) { nodes { author { login } } }
|
|
332
|
+
newest: comments(last: 1) { nodes { author { login } body state } }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Resolve only when the newest comment is submitted, its `author { login }` equals the opener's,
|
|
338
|
+
and its `body`, after removing leading spaces, tabs, CR, and LF, begins `Accepted:`. For each
|
|
339
|
+
such thread:
|
|
340
|
+
|
|
341
|
+
```graphql
|
|
342
|
+
mutation($threadId: ID!) {
|
|
343
|
+
resolveReviewThread(input: { threadId: $threadId }) { thread { isResolved } }
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Re-read `reviewThreads` and confirm that thread's `isResolved` is true. In either route, report
|
|
348
|
+
a refused resolution to the architect, which opens an ask for a human to resolve the thread by
|
|
349
|
+
hand — never skip it silently. The merger runs the command once more before publishing READY
|
|
322
350
|
and does not publish while any `left open` line remains.
|
|
323
351
|
- **Correctness fixes land in this PR; cleanup is one named fast-follow.** A finding that
|
|
324
352
|
changes behavior, hides an error, or breaks a gate is fixed here — never deferred.
|