@sjawhar/pi-legion-envoy 7.4.6 → 7.5.1
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/envoy.js
CHANGED
|
@@ -30316,7 +30316,7 @@ var dispatchToolSpecs = [
|
|
|
30316
30316
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
30317
30317
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
30318
30318
|
},
|
|
30319
|
-
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. A delete or retype that would take an ask block out of the document while its ask is open is refused, with nothing sent. 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. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
|
|
30319
|
+
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. A task item (\`- [ ]\` / \`- [x]\`) is ticked the same way: when find matches from the start of the item's text and with opens with '[x] ' or '[ ] ', the opening sets the item's box and the rest is its new text (replace(find="Write it", with="[x] Write it") ticks it); the same opening over a plain list item or a paragraph stays literal text. 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. A delete or retype that would take an ask block out of the document while its ask is open is refused, with nothing sent. 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. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
|
|
30320
30320
|
arguments: (z2) => ({
|
|
30321
30321
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
30322
30322
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -30325,7 +30325,7 @@ var dispatchToolSpecs = [
|
|
|
30325
30325
|
ops: z2.array(z2.object({
|
|
30326
30326
|
op: z2.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
30327
30327
|
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(),
|
|
30328
|
-
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(),
|
|
30328
|
+
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 leading '[x] ' or '[ ] ' over a task item's text matched from its start sets the item's checkbox, 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(),
|
|
30329
30329
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
30330
30330
|
markdown: z2.string().describe("Markdown to insert; a CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
30331
30331
|
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(),
|
|
@@ -30395,7 +30395,7 @@ var dispatchToolSpecs = [
|
|
|
30395
30395
|
{
|
|
30396
30396
|
name: "dispatch_read",
|
|
30397
30397
|
example: { issue: "DSP-1" },
|
|
30398
|
-
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch_doc_read instead. Supply ref, issue, " + "or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). " + "An anchored comment or ask also says where its quote sits, as `Position:`: the block's path from the top, " + "and in a table the row (0 is the header), the cells before the anchored one, and the column's header; " + "`Position: unavailable (<code>)` when Dispatch could not read the document: `DOC_SERVICE_UNAVAILABLE` " + "(try again shortly), `DOC_SCHEMA` (the document needs repair), `DOCUMENT_UNLOADABLE` (the document " + "needs a rebuild) or `INTERNAL`. " + "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,
|
|
30398
|
+
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch_doc_read instead. Supply ref, issue, " + "or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). " + "An anchored comment or ask also says where its quote sits, as `Position:`: the block's path from the top, " + "and in a table the row (0 is the header), the cells before the anchored one, and the column's header; " + "`Position: unavailable (<code>)` when Dispatch could not read the document: `DOC_SERVICE_UNAVAILABLE` " + "(try again shortly), `DOC_SCHEMA` (the document needs repair), `DOCUMENT_UNLOADABLE` (the document " + "needs a rebuild) or `INTERNAL`. " + "An issue read carries a `Progress:` line, `tasks 3/7, children 2/5`: its spec's task-list items " + "(`- [ ]` / `- [x]`, nested lists included) and its direct children, each done of total and each only " + "when it has any (`Progress: none` otherwise). " + "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,
|
|
30399
30399
|
arguments: (z2) => ({
|
|
30400
30400
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
30401
30401
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -30429,7 +30429,7 @@ var dispatchToolSpecs = [
|
|
|
30429
30429
|
{
|
|
30430
30430
|
name: "dispatch_issues",
|
|
30431
30431
|
example: { project: "PROJ", limit: 250, offset: 250 },
|
|
30432
|
-
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch_search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never.",
|
|
30432
|
+
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, progress (its spec's task-list items and " + "its direct children, each done of total, as `tasks 3/7 \xB7 children 2/5`, each only when it has " + "any), and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch_search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never.",
|
|
30433
30433
|
arguments: (z2) => ({
|
|
30434
30434
|
project: z2.string().describe("Project key to list issues from."),
|
|
30435
30435
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -33355,6 +33355,17 @@ function routeText(issue2, titles) {
|
|
|
33355
33355
|
};
|
|
33356
33356
|
return issue2.route + (issue2.route_status == null ? "" : reach[issue2.route_status]);
|
|
33357
33357
|
}
|
|
33358
|
+
function progressText(progress, sep) {
|
|
33359
|
+
if (progress === undefined)
|
|
33360
|
+
return "";
|
|
33361
|
+
const parts = [];
|
|
33362
|
+
if (progress.tasks !== null)
|
|
33363
|
+
parts.push(`tasks ${progress.tasks.done}/${progress.tasks.total}`);
|
|
33364
|
+
if (progress.children !== null) {
|
|
33365
|
+
parts.push(`children ${progress.children.done}/${progress.children.total}`);
|
|
33366
|
+
}
|
|
33367
|
+
return parts.join(sep);
|
|
33368
|
+
}
|
|
33358
33369
|
function issueSummary(issue2, events, references, graph, titles) {
|
|
33359
33370
|
const asks = issue2.open_asks;
|
|
33360
33371
|
const spec = issue2.artifacts?.find((artifact) => artifact.primary);
|
|
@@ -33380,6 +33391,7 @@ function issueSummary(issue2, events, references, graph, titles) {
|
|
|
33380
33391
|
`Labels: ${issue2.labels.length === 0 ? "none" : issue2.labels.join(", ")}`,
|
|
33381
33392
|
componentsLine(issue2.components),
|
|
33382
33393
|
`Route: ${routeText(issue2, titles)}`,
|
|
33394
|
+
`Progress: ${progressText(issue2.progress, ", ") || "none"}`,
|
|
33383
33395
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
33384
33396
|
"External links:",
|
|
33385
33397
|
...issue2.external_links.length === 0 ? ["- none"] : issue2.external_links.map((link) => `- ${link.url}${link.kind === undefined ? "" : ` (${link.kind})`}`),
|
|
@@ -34067,7 +34079,8 @@ async function executeDispatchTool(input) {
|
|
|
34067
34079
|
route: row.route ?? null,
|
|
34068
34080
|
route_status: row.route_status ?? null,
|
|
34069
34081
|
route_holder: row.route_holder ?? null,
|
|
34070
|
-
updated_at: row.updated_at
|
|
34082
|
+
updated_at: row.updated_at,
|
|
34083
|
+
progress: row.progress
|
|
34071
34084
|
}));
|
|
34072
34085
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
34073
34086
|
const isPartial = offset !== 0 || rows.length !== total;
|
|
@@ -34075,7 +34088,10 @@ async function executeDispatchTool(input) {
|
|
|
34075
34088
|
return {
|
|
34076
34089
|
text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
|
|
34077
34090
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
|
|
34078
|
-
...rows.map((row) =>
|
|
34091
|
+
...rows.map((row) => {
|
|
34092
|
+
const progress = progressText(row.progress, " \xB7 ");
|
|
34093
|
+
return `${row.key} [${row.status}]${row.priority === null ? "" : ` P${row.priority}`} ${row.title}` + (row.open_asks === 0 ? "" : ` \xB7 ${row.open_asks} open ${row.open_asks === 1 ? "ask" : "asks"}`) + (row.claim === null ? "" : ` \xB7 claimed by ${claimText(row.claim, titles)}`) + (progress === "" ? "" : ` \xB7 ${progress}`) + (row.route === null || row.route_status === "live" || row.route_status === null ? "" : ` \xB7 route ${routeText(row)}`);
|
|
34094
|
+
})
|
|
34079
34095
|
].join(`
|
|
34080
34096
|
`),
|
|
34081
34097
|
details: { issues: rows, total, offset, limit }
|
package/dist/legion.js
CHANGED
|
@@ -14403,7 +14403,7 @@ var dispatchToolSpecs = [
|
|
|
14403
14403
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
14404
14404
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
14405
14405
|
},
|
|
14406
|
-
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. A delete or retype that would take an ask block out of the document while its ask is open is refused, with nothing sent. 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. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
|
|
14406
|
+
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. A task item (\`- [ ]\` / \`- [x]\`) is ticked the same way: when find matches from the start of the item's text and with opens with '[x] ' or '[ ] ', the opening sets the item's box and the rest is its new text (replace(find="Write it", with="[x] Write it") ticks it); the same opening over a plain list item or a paragraph stays literal text. 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. A delete or retype that would take an ask block out of the document while its ask is open is refused, with nothing sent. 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. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
|
|
14407
14407
|
arguments: (z2) => ({
|
|
14408
14408
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14409
14409
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14412,7 +14412,7 @@ var dispatchToolSpecs = [
|
|
|
14412
14412
|
ops: z2.array(z2.object({
|
|
14413
14413
|
op: z2.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
14414
14414
|
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(),
|
|
14415
|
-
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(),
|
|
14415
|
+
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 leading '[x] ' or '[ ] ' over a task item's text matched from its start sets the item's checkbox, 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(),
|
|
14416
14416
|
occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
14417
14417
|
markdown: z2.string().describe("Markdown to insert; a CR LF or a lone carriage return in it is written as a line feed.").optional(),
|
|
14418
14418
|
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(),
|
|
@@ -14482,7 +14482,7 @@ var dispatchToolSpecs = [
|
|
|
14482
14482
|
{
|
|
14483
14483
|
name: "dispatch_read",
|
|
14484
14484
|
example: { issue: "DSP-1" },
|
|
14485
|
-
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch_doc_read instead. Supply ref, issue, " + "or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). " + "An anchored comment or ask also says where its quote sits, as `Position:`: the block's path from the top, " + "and in a table the row (0 is the header), the cells before the anchored one, and the column's header; " + "`Position: unavailable (<code>)` when Dispatch could not read the document: `DOC_SERVICE_UNAVAILABLE` " + "(try again shortly), `DOC_SCHEMA` (the document needs repair), `DOCUMENT_UNLOADABLE` (the document " + "needs a rebuild) or `INTERNAL`. " + "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,
|
|
14485
|
+
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch_doc_read instead. Supply ref, issue, " + "or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). " + "An anchored comment or ask also says where its quote sits, as `Position:`: the block's path from the top, " + "and in a table the row (0 is the header), the cells before the anchored one, and the column's header; " + "`Position: unavailable (<code>)` when Dispatch could not read the document: `DOC_SERVICE_UNAVAILABLE` " + "(try again shortly), `DOC_SCHEMA` (the document needs repair), `DOCUMENT_UNLOADABLE` (the document " + "needs a rebuild) or `INTERNAL`. " + "An issue read carries a `Progress:` line, `tasks 3/7, children 2/5`: its spec's task-list items " + "(`- [ ]` / `- [x]`, nested lists included) and its direct children, each done of total and each only " + "when it has any (`Progress: none` otherwise). " + "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,
|
|
14486
14486
|
arguments: (z2) => ({
|
|
14487
14487
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14488
14488
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14516,7 +14516,7 @@ var dispatchToolSpecs = [
|
|
|
14516
14516
|
{
|
|
14517
14517
|
name: "dispatch_issues",
|
|
14518
14518
|
example: { project: "PROJ", limit: 250, offset: 250 },
|
|
14519
|
-
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch_search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never.",
|
|
14519
|
+
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, progress (its spec's task-list items and " + "its direct children, each done of total, as `tasks 3/7 \xB7 children 2/5`, each only when it has " + "any), and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch_search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never.",
|
|
14520
14520
|
arguments: (z2) => ({
|
|
14521
14521
|
project: z2.string().describe("Project key to list issues from."),
|
|
14522
14522
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -16140,6 +16140,17 @@ function routeText(issue2, titles) {
|
|
|
16140
16140
|
};
|
|
16141
16141
|
return issue2.route + (issue2.route_status == null ? "" : reach[issue2.route_status]);
|
|
16142
16142
|
}
|
|
16143
|
+
function progressText(progress, sep) {
|
|
16144
|
+
if (progress === undefined)
|
|
16145
|
+
return "";
|
|
16146
|
+
const parts = [];
|
|
16147
|
+
if (progress.tasks !== null)
|
|
16148
|
+
parts.push(`tasks ${progress.tasks.done}/${progress.tasks.total}`);
|
|
16149
|
+
if (progress.children !== null) {
|
|
16150
|
+
parts.push(`children ${progress.children.done}/${progress.children.total}`);
|
|
16151
|
+
}
|
|
16152
|
+
return parts.join(sep);
|
|
16153
|
+
}
|
|
16143
16154
|
function issueSummary(issue2, events, references, graph, titles) {
|
|
16144
16155
|
const asks = issue2.open_asks;
|
|
16145
16156
|
const spec = issue2.artifacts?.find((artifact) => artifact.primary);
|
|
@@ -16165,6 +16176,7 @@ function issueSummary(issue2, events, references, graph, titles) {
|
|
|
16165
16176
|
`Labels: ${issue2.labels.length === 0 ? "none" : issue2.labels.join(", ")}`,
|
|
16166
16177
|
componentsLine(issue2.components),
|
|
16167
16178
|
`Route: ${routeText(issue2, titles)}`,
|
|
16179
|
+
`Progress: ${progressText(issue2.progress, ", ") || "none"}`,
|
|
16168
16180
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
16169
16181
|
"External links:",
|
|
16170
16182
|
...issue2.external_links.length === 0 ? ["- none"] : issue2.external_links.map((link) => `- ${link.url}${link.kind === undefined ? "" : ` (${link.kind})`}`),
|
|
@@ -16852,7 +16864,8 @@ async function executeDispatchTool(input) {
|
|
|
16852
16864
|
route: row.route ?? null,
|
|
16853
16865
|
route_status: row.route_status ?? null,
|
|
16854
16866
|
route_holder: row.route_holder ?? null,
|
|
16855
|
-
updated_at: row.updated_at
|
|
16867
|
+
updated_at: row.updated_at,
|
|
16868
|
+
progress: row.progress
|
|
16856
16869
|
}));
|
|
16857
16870
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
16858
16871
|
const isPartial = offset !== 0 || rows.length !== total;
|
|
@@ -16860,7 +16873,10 @@ async function executeDispatchTool(input) {
|
|
|
16860
16873
|
return {
|
|
16861
16874
|
text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
|
|
16862
16875
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
|
|
16863
|
-
...rows.map((row) =>
|
|
16876
|
+
...rows.map((row) => {
|
|
16877
|
+
const progress = progressText(row.progress, " \xB7 ");
|
|
16878
|
+
return `${row.key} [${row.status}]${row.priority === null ? "" : ` P${row.priority}`} ${row.title}` + (row.open_asks === 0 ? "" : ` \xB7 ${row.open_asks} open ${row.open_asks === 1 ? "ask" : "asks"}`) + (row.claim === null ? "" : ` \xB7 claimed by ${claimText(row.claim, titles)}`) + (progress === "" ? "" : ` \xB7 ${progress}`) + (row.route === null || row.route_status === "live" || row.route_status === null ? "" : ` \xB7 route ${routeText(row)}`);
|
|
16879
|
+
})
|
|
16864
16880
|
].join(`
|
|
16865
16881
|
`),
|
|
16866
16882
|
details: { issues: rows, total, offset, limit }
|
|
@@ -17459,7 +17475,7 @@ function endInjectedUserTurns(sessionID) {
|
|
|
17459
17475
|
// package.json
|
|
17460
17476
|
var package_default = {
|
|
17461
17477
|
name: "@sjawhar/pi-legion-envoy",
|
|
17462
|
-
version: "7.
|
|
17478
|
+
version: "7.5.1",
|
|
17463
17479
|
license: "Apache-2.0",
|
|
17464
17480
|
type: "module",
|
|
17465
17481
|
omp: {
|
|
@@ -17827,6 +17843,7 @@ var LegionThreadOutcome = exports_external.union([
|
|
|
17827
17843
|
]);
|
|
17828
17844
|
var LegionThreadsResolveResponse = exports_external.strictObject({
|
|
17829
17845
|
threads: exports_external.array(LegionThreadOutcome),
|
|
17846
|
+
withheld: exports_external.number().int().nonnegative(),
|
|
17830
17847
|
refused: exports_external.strictObject({ url: nonEmptyString, error: nonEmptyString }).optional()
|
|
17831
17848
|
});
|
|
17832
17849
|
var LegionEmptyResponse = exports_external.strictObject({});
|
|
@@ -18851,9 +18868,10 @@ var LEGION_HANDOFF_COMPLETE = {
|
|
|
18851
18868
|
},
|
|
18852
18869
|
refusal: (attempt) => `refused \`${attempt}\`: a phase is completed with the \`legion\` tool's ` + "`handoff_complete`, never a shell command: the tool call is what records the phase " + "complete. A root architect or a `task` subagent has no handoff actions: it leaves the " + "completion to the worker. Text that only names the command (a commit message, a PR body) " + "reads as the command: pass it in a file."
|
|
18853
18870
|
};
|
|
18871
|
+
var TREE_PANE_RULES = [JJ_LOG_REWRITE, LEGION_HANDOFF_COMPLETE];
|
|
18854
18872
|
var PANE_RULES = {
|
|
18855
|
-
"phase-worker":
|
|
18856
|
-
"root-architect":
|
|
18873
|
+
"phase-worker": TREE_PANE_RULES,
|
|
18874
|
+
"root-architect": TREE_PANE_RULES
|
|
18857
18875
|
};
|
|
18858
18876
|
function refusedCommand(command, commands, rule) {
|
|
18859
18877
|
if (commands === undefined) {
|
|
@@ -18900,7 +18918,11 @@ function paneRuleRefusal(toolCall, rules) {
|
|
|
18900
18918
|
}
|
|
18901
18919
|
var LEGION_LOADED_MARKER = Symbol.for("legion.pi-envoy.legion-loaded");
|
|
18902
18920
|
var CODE_MUTATION_TOOLS = ["edit", "write", "apply_patch"];
|
|
18903
|
-
var
|
|
18921
|
+
var CODE_TOOL_REFUSAL = {
|
|
18922
|
+
architect: "the architect delegates all code work to phase workers",
|
|
18923
|
+
reviewer: "the reviewer edits nothing except the final .legion/ cleanup commit via bash",
|
|
18924
|
+
merger: "the merger only verifies and reports"
|
|
18925
|
+
};
|
|
18904
18926
|
var TOOL_DEVICE_URL = /^xd:\/\//iu;
|
|
18905
18927
|
function isToolDeviceInvocation(toolCall) {
|
|
18906
18928
|
return toolCall.toolName === "write" && typeof toolCall.input.path === "string" && TOOL_DEVICE_URL.test(toolCall.input.path);
|
|
@@ -18983,24 +19005,10 @@ function legionExtension(pi) {
|
|
|
18983
19005
|
return;
|
|
18984
19006
|
const sessionID = context.sessionManager.getSessionId();
|
|
18985
19007
|
const active = claimSession.capability(sessionID);
|
|
18986
|
-
const
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
if (active?.role === "architect" && toolCall.toolName === "task") {
|
|
18991
|
-
return {
|
|
18992
|
-
block: true,
|
|
18993
|
-
reason: "the architect delegates only through child issues and the daemon's phase workers; Legion runs one agent per process"
|
|
18994
|
-
};
|
|
18995
|
-
}
|
|
18996
|
-
if (active?.role === "reviewer" && !isToolDevice && CODE_MUTATION_TOOLS.includes(toolCall.toolName)) {
|
|
18997
|
-
return {
|
|
18998
|
-
block: true,
|
|
18999
|
-
reason: "the reviewer edits nothing except the final .legion/ cleanup commit via bash"
|
|
19000
|
-
};
|
|
19001
|
-
}
|
|
19002
|
-
if (active?.role === "merger" && !isToolDevice && MERGER_BLOCKED_TOOLS.includes(toolCall.toolName)) {
|
|
19003
|
-
return { block: true, reason: "the merger only verifies and reports" };
|
|
19008
|
+
const codeToolRefusal = active === undefined ? undefined : CODE_TOOL_REFUSAL[active.role];
|
|
19009
|
+
const mutatesCode = CODE_MUTATION_TOOLS.includes(toolCall.toolName) || active?.role === "architect" && toolCall.toolName === "bash" && !isSingleLegionCommand(toolCall.input.command);
|
|
19010
|
+
if (codeToolRefusal !== undefined && mutatesCode && !isToolDeviceInvocation(toolCall)) {
|
|
19011
|
+
return { block: true, reason: codeToolRefusal };
|
|
19004
19012
|
}
|
|
19005
19013
|
if (!needsGrant(toolCall))
|
|
19006
19014
|
return;
|
|
@@ -19092,5 +19100,6 @@ function legionExtension(pi) {
|
|
|
19092
19100
|
}
|
|
19093
19101
|
export {
|
|
19094
19102
|
setLegionBootstrapExitForTests,
|
|
19095
|
-
legionExtension as default
|
|
19103
|
+
legionExtension as default,
|
|
19104
|
+
CODE_TOOL_REFUSAL
|
|
19096
19105
|
};
|
|
@@ -74,7 +74,11 @@ which is written as text with a backslash escape (`1999\. was a year`) — omit
|
|
|
74
74
|
plus `delete` to change the block's kind, level or number. The one exception is a heading rename whose `find` carried a heading
|
|
75
75
|
marker: `replace(find="## Old", with="## New")` gives `## New`. A different level in `with` applies only when `find` named the
|
|
76
76
|
heading's actual level — `find="## Old"`, `with="### New"` retitles and makes it an h3 — because `# ` is the level-blind selector,
|
|
77
|
-
so `find="# Old"` renames the text and keeps whatever level it selected. `
|
|
77
|
+
so `find="# Old"` renames the text and keeps whatever level it selected. A task item (`- [ ]` / `- [x]`) is ticked the same way:
|
|
78
|
+
when `find` matches from the start of the item's text and `with` opens with `[x] ` or `[ ] `, the opening sets the item's
|
|
79
|
+
checkbox and the rest is its new text — `replace(find="Write it", with="[x] Write it")` ticks it, keeping its block id — since
|
|
80
|
+
the box is the item's attribute, not text, and written as text it would read `- [ ] [x] …`. The same opening over a plain list
|
|
81
|
+
item or a paragraph stays literal text, as any marker of another kind does. `with` that forms more than one
|
|
78
82
|
paragraph is rejected (`INVALID_OP` on `with`) — see the recipe for a multi-paragraph rewrite below; so is any non-empty `with` that
|
|
79
83
|
renders to no text, which a line indented four spaces or a tab does (markdown reads that as a code block), as does whitespace
|
|
80
84
|
alone. An empty `with` deletes the matched text on purpose; a list item, quote, typed block or footnote definition left
|
|
@@ -285,6 +285,13 @@ is open. That is the one review-thread step a human takes: the review App cannot
|
|
|
285
285
|
on a pull request the implementer opened, and the implementer's and merger's runs of the command
|
|
286
286
|
close every accepted one.
|
|
287
287
|
|
|
288
|
+
If a reviewer reports that `legion threads resolve` exited 1 counting threads that hold the
|
|
289
|
+
implement App's pending draft (the daemon counts them and never names them), tell the worker
|
|
290
|
+
holding the issue's phase (`envoy_publish` to its role topic) to move the issue back to
|
|
291
|
+
`implementing` with `request_backward_move`, naming the count, so the implementer submits or
|
|
292
|
+
discards its pending review; its own run of the command names those threads `left open … an
|
|
293
|
+
unsubmitted draft in a pending review`. Open no ask for it: the implementer clears it.
|
|
294
|
+
|
|
288
295
|
## 7. Close
|
|
289
296
|
|
|
290
297
|
After the merge result and the implementer's production report are recorded, post the sign-off and
|
|
@@ -20,7 +20,16 @@ Every path it cites is in sjawhar/legion.
|
|
|
20
20
|
the daemon instead (`POST /legion/v1/threads/resolve`), which resolves as the implementer's App
|
|
21
21
|
only the threads a bot outside Legion's role Apps opened whose newest submitted comment is the
|
|
22
22
|
reviewer's `Accepted:`, on the pull request of the reviewer's own issue, and prints the same
|
|
23
|
-
lines; the reviewer never holds the implementer's token.
|
|
23
|
+
lines; the reviewer never holds the implementer's token. A thread whose newest comment is a
|
|
24
|
+
draft in the implement App's pending review (the implementer's or the merger's: GitHub shows it
|
|
25
|
+
to that App alone, as which the daemon reads) is left open and never named: the command prints
|
|
26
|
+
only how many there are (`<n> unresolved threads hold the implement App's pending draft and
|
|
27
|
+
were left open`) and exits 1, as it does on a refusal. Report that count to the architect before
|
|
28
|
+
you spend your one re-run of the failed workflow; the architect sends the issue back so the
|
|
29
|
+
implementer submits or discards its pending review. Once the review is submitted, the
|
|
30
|
+
implementer's reply is the thread's newest comment, which leaves it open: answer the thread
|
|
31
|
+
again, then run the command again. Once the review is discarded, your `Accepted:` is the newest
|
|
32
|
+
comment again, and running the command again closes the thread.
|
|
24
33
|
When `LEGION_GRANT_FILE` or `LEGION_GRANT` is set, use `legion threads resolve --pr <number> --repo <owner>/<repo>`.
|
|
25
34
|
When neither is set, add `--gh` to that command, which applies the fallback's rule below through
|
|
26
35
|
your own `gh`; where no `legion` command is installed, use `gh api graphql` with the session's
|