@sjawhar/opencode-legion-envoy 9.0.0 → 9.1.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.
- package/dist/dispatch.js +35 -27
- package/dist/src/server.js +16 -15
- package/package.json +1 -1
- package/skills/AGENTS.md +7 -6
- package/skills/dispatch/SKILL.md +54 -169
- package/skills/dispatch-first/SKILL.md +23 -17
- package/skills/dispatch-writing-specs/SKILL.md +76 -0
- package/skills/leading-a-work-stream/SKILL.md +96 -0
- package/skills/leading-a-work-stream/references/daily-plan.md +8 -0
- package/skills/leading-a-work-stream/references/decision-rights.md +6 -0
- package/skills/leading-a-work-stream/references/direction-and-coaching.md +13 -0
- package/skills/leading-a-work-stream/references/dispatch-upkeep.md +7 -0
- package/skills/leading-a-work-stream/references/sources.md +9 -0
- package/skills/leading-a-work-stream/references/speed-tick.md +44 -0
- package/skills/leading-a-work-stream/references/user-quotes.md +7 -0
- package/skills/legion-architect/SKILL.md +4 -2
- package/skills/legion-architect/references/acceptance.md +11 -3
- package/skills/legion-architect/references/design-gate.md +32 -17
- package/skills/legion-architect/references/wake-routing.md +2 -2
- package/skills/legion-controller/SKILL.md +27 -16
- package/skills/legion-controller/references/backlog-stewardship.md +17 -2
- package/skills/legion-controller/references/daily-report.md +4 -4
- package/skills/legion-controller/references/finding-asks.md +111 -14
- package/skills/legion-controller/references/slack-mentions.md +2 -2
- package/skills/legion-controller/references/triage.md +9 -5
- package/skills/legion-worker/SKILL.md +5 -4
- package/skills/legion-worker/references/conflicts-and-rewrites.md +23 -16
- package/skills/legion-worker/references/findings.md +126 -31
- package/skills/legion-worker/references/merge-gate.md +48 -17
- package/skills/legion-worker/references/pr-body.md +9 -5
- package/skills/dispatch/references/api.md +0 -21
- package/skills/dispatch/references/asks.md +0 -43
- package/skills/dispatch/references/document-edits.md +0 -59
- package/skills/dispatch/references/documents.md +0 -79
- package/skills/dispatch/references/examples.md +0 -85
- package/skills/dispatch/references/issues.md +0 -66
- package/skills/dispatch/references/messages.md +0 -31
- package/skills/dispatch/references/reading.md +0 -32
- package/skills/dispatch-brainstorming/SKILL.md +0 -56
package/dist/dispatch.js
CHANGED
|
@@ -13623,6 +13623,7 @@ var askEventPayloadFields = {
|
|
|
13623
13623
|
text: exports_external.string().nullish(),
|
|
13624
13624
|
at: exports_external.string().optional()
|
|
13625
13625
|
}).nullish(),
|
|
13626
|
+
restored: exports_external.boolean().optional(),
|
|
13626
13627
|
anchor: exports_external.object({
|
|
13627
13628
|
quote: exports_external.string().optional(),
|
|
13628
13629
|
mark_id: exports_external.string().optional(),
|
|
@@ -13793,19 +13794,17 @@ function actorName(actor, titles) {
|
|
|
13793
13794
|
const live = titles?.get(actor.id)?.trim();
|
|
13794
13795
|
return sessionLabel(actor.id, live === undefined || live === "" ? actor.origin?.session_title : live);
|
|
13795
13796
|
}
|
|
13796
|
-
function
|
|
13797
|
-
const name = actorName(actor, titles);
|
|
13797
|
+
function actorAttribution(actor) {
|
|
13798
13798
|
if (actor.kind !== "session") {
|
|
13799
|
-
return
|
|
13799
|
+
return "";
|
|
13800
13800
|
}
|
|
13801
13801
|
if (actor.owner !== undefined) {
|
|
13802
|
-
return
|
|
13802
|
+
return ` (for ${actor.owner})`;
|
|
13803
13803
|
}
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
}
|
|
13808
|
-
return `${name} (as ${serviceSubjectLabel(service)})`;
|
|
13804
|
+
return actor.service === undefined ? "" : ` (as ${serviceSubjectLabel(actor.service)})`;
|
|
13805
|
+
}
|
|
13806
|
+
function actorLabel(actor, titles) {
|
|
13807
|
+
return `${actorName(actor, titles)}${actorAttribution(actor)}`;
|
|
13809
13808
|
}
|
|
13810
13809
|
// ../contracts/src/agent-stream.ts
|
|
13811
13810
|
var AGENT_STREAM_LIMITS = {
|
|
@@ -14014,7 +14013,7 @@ function componentsArgument(z2) {
|
|
|
14014
14013
|
reason: z2.string({ min: 1 }).describe("For mode none: why this issue is not architectural (process, hiring, ops).").optional()
|
|
14015
14014
|
}).describe("Attach the issue to architecture components. Attach the root before decomposing it; children inherit unless they choose.");
|
|
14016
14015
|
}
|
|
14017
|
-
var SPEC_WRITING_POINTER =
|
|
14016
|
+
var SPEC_WRITING_POINTER = "Write a spec as skill://dispatch-writing-specs says.";
|
|
14018
14017
|
function imagesArgument(z2, owner, cap) {
|
|
14019
14018
|
return z2.array(z2.string({ min: 1 }), { min: 1 }).describe(`Optional local picture files to show inline: PNG, JPEG, GIF or WebP (by their bytes, not their name), each at most 25 MiB. Each is uploaded to ${owner} and appended after a blank line as one  line, in this order; those lines count toward ${cap}.`).optional();
|
|
14020
14019
|
}
|
|
@@ -14052,7 +14051,7 @@ var dispatchToolSpecs = [
|
|
|
14052
14051
|
{
|
|
14053
14052
|
name: "dispatch_issue",
|
|
14054
14053
|
example: { project: "DSP", title: "Native workspace" },
|
|
14055
|
-
description: "Create a native Dispatch issue for newly tracked work. Search first with dispatch search; if potentially duplicate issues exist, this returns 409 POSSIBLE_DUPLICATE unless --force is set after reading them. " + "When passing a spec, before calling, have a fresh read-only reader check it against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. " + "A spec holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK. " + `Do not use it when an existing issue already covers the work; read or update that issue instead. ${ISSUE_REFERENCE}`,
|
|
14054
|
+
description: "Create a native Dispatch issue for newly tracked work. Search first with dispatch search; if potentially duplicate issues exist, this returns 409 POSSIBLE_DUPLICATE unless --force is set after reading them. " + "When passing a spec, before calling, have a fresh read-only reader check it against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. " + "A spec holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK. " + "Create a project only when a person asks for one; work that spans projects goes in the project that owns most of it. " + `Do not use it when an existing issue already covers the work; read or update that issue instead. ${ISSUE_REFERENCE}`,
|
|
14056
14055
|
arguments: (z2) => ({
|
|
14057
14056
|
project: z2.string().describe("Project key for the new issue."),
|
|
14058
14057
|
title: z2.string({ max: ISSUE_TITLE_MAX }).describe("Concise issue title, at most 1,000 characters."),
|
|
@@ -14063,7 +14062,7 @@ var dispatchToolSpecs = [
|
|
|
14063
14062
|
spec: z2.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_POINTER}`).optional(),
|
|
14064
14063
|
labels: z2.array(z2.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
|
|
14065
14064
|
priority: z2.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority, a number from 0 to 3: 0 is highest and 3 is lowest.").optional(),
|
|
14066
|
-
assignee: z2.string().describe("Email of the person who answers this issue's asks (someone who has signed in to Dispatch); defaults to your owner when you act for a person, else the parent's assignee, else the project's default assignee when it still names a person who has signed in, else unassigned.").optional(),
|
|
14065
|
+
assignee: z2.string().describe("Email of the person who answers this issue's asks (someone who has signed in to Dispatch); defaults to your owner when you act for a person, else the parent's assignee, else the project's default assignee when it still names a person who has signed in, else unassigned. Set it only when a person said who owns the issue.").optional(),
|
|
14067
14066
|
components: componentsArgument(z2).optional(),
|
|
14068
14067
|
repo: z2.string().describe("The repository the issue targets, owner/name, mapped to the issue's project in Settings; a child inherits its parent's when unset, and an issue with none on its parent chain takes the project's default.").optional()
|
|
14069
14068
|
})
|
|
@@ -14075,7 +14074,7 @@ var dispatchToolSpecs = [
|
|
|
14075
14074
|
priority: 1,
|
|
14076
14075
|
before: "DSP-2"
|
|
14077
14076
|
},
|
|
14078
|
-
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, because a closed issue refuses messages, comments, and " + "artifacts; reason goes only with status done. A closed issue takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
14077
|
+
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, because a closed issue refuses messages, comments, and " + "artifacts; reason goes only with status done. A closed issue takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
14079
14078
|
arguments: (z2) => ({
|
|
14080
14079
|
issue: z2.string().describe(ISSUE_REFERENCE),
|
|
14081
14080
|
status: z2.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
@@ -14125,7 +14124,7 @@ var dispatchToolSpecs = [
|
|
|
14125
14124
|
}
|
|
14126
14125
|
]
|
|
14127
14126
|
},
|
|
14128
|
-
description: "Open a to-do or permission only a human can give, or a decision that has no document to " + "live in. A question about the design an issue's document records is not this tool: write " + "it into that document as a decision block (dispatch doc-edit inserting an ask block at the " + "end of the section it concerns), at every phase, approved spec or not; the block reaches " + "the Inbox and its answer lands next to its context. Never give an ask an Approve option: a " + "document is approved through dispatch request-approval. Do not use this tool for a status " + "update or discussion; use dispatch message instead. " + ASK_QUESTION_CONTRACT + " " + ASK_OPTIONS_CONTRACT + " For an action only a human can perform, state what it changes and risks as constraints. " + "Anything that requires a human to do, including a credential or grant renewal, is an ask, " + "never a message. " + "Anchor a to-do about a document passage, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". ` + `A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} characters, ` + `the lines images appends included, and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
14127
|
+
description: "Open a to-do or permission only a human can give, or a decision that has no document to " + "live in. A question about the design an issue's document records is not this tool: write " + "it into that document as a decision block (dispatch doc-edit inserting an ask block at the " + "end of the section it concerns), at every phase, approved spec or not; the block reaches " + "the Inbox and its answer lands next to its context. Never give an ask an Approve option: a " + "document is approved through dispatch request-approval. Do not use this tool for a status " + "update or discussion; use dispatch message instead. Never create an issue to park a " + "question: the question is an ask on the issue it concerns. " + ASK_QUESTION_CONTRACT + " " + ASK_OPTIONS_CONTRACT + " For an action only a human can perform, state what it changes and risks as constraints. " + "Anything that requires a human to do, including a credential or grant renewal, is an ask, " + "never a message. Measure what you can first, and put the measurement and the size of the " + "population it covers in the question; drop an option that cannot fix most of that " + "population, never offer it cut down, and keep an option not to act (Hold). Set urgency high " + "when your work halts on the answer, and keep working on everything else. " + "Anchor a to-do about a document passage, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". ` + `A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} characters, ` + `the lines images appends included, and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
14129
14128
|
arguments: (z2) => ({
|
|
14130
14129
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14131
14130
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14189,7 +14188,7 @@ var dispatchToolSpecs = [
|
|
|
14189
14188
|
kind: "retracted",
|
|
14190
14189
|
reason: "A newer question supersedes this one."
|
|
14191
14190
|
},
|
|
14192
|
-
description: "Retract an open question that is moot or resolve one after finding the answer. This closes " + "the question without answering it. An ask that lives as an `ask` block in a document is " + 'closed in the document too. A reason beginning "removed from the document in version" is ' + "refused: that marks a retraction the document's own settlement wrote.",
|
|
14191
|
+
description: "Retract an open question that is moot or resolve one after finding the answer. This closes " + "the question without answering it. An ask that lives as an `ask` block in a document is " + 'closed in the document too. A reason beginning "removed from the document in version" is ' + "refused: that marks a retraction the document's own settlement wrote. Use retracted for an ask " + "a newer one replaces or the design made moot, resolved for one whose answer you found; an " + "answered ask cannot be resolved, and resolving records no decision by a person.",
|
|
14193
14192
|
arguments: (z2) => ({
|
|
14194
14193
|
ask: z2.string().describe("Ask id (uuid) to close; an 8+ hex prefix unique among this session's own open asks " + "works too."),
|
|
14195
14194
|
kind: z2.enum(["retracted", "resolved"]).describe("Whether the ask is retracted or self-resolved."),
|
|
@@ -14218,7 +14217,7 @@ var dispatchToolSpecs = [
|
|
|
14218
14217
|
{
|
|
14219
14218
|
name: "dispatch_comment",
|
|
14220
14219
|
example: { issue: "DSP-1", body: "Looks good." },
|
|
14221
|
-
description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${OWNER_REFERENCE}`,
|
|
14220
|
+
description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. A reply under an open ask asks for clarification, not an answer: answer it with reply_to_ask, or reword the ask with dispatch edit-ask, rather than opening a second ask. Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${OWNER_REFERENCE}`,
|
|
14222
14221
|
arguments: (z2) => ({
|
|
14223
14222
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14224
14223
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14258,7 +14257,7 @@ var dispatchToolSpecs = [
|
|
|
14258
14257
|
{
|
|
14259
14258
|
name: "dispatch_message",
|
|
14260
14259
|
example: { issue: "DSP-1", body: "Implementation started." },
|
|
14261
|
-
description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. Every other message names its issue. " + `Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${ISSUE_REFERENCE}`,
|
|
14260
|
+
description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. " + "Every other message names its issue; keep --issue whenever the frame you answer gave one. A BTW, Aside or Steer " + "frame carries a reply_with command: answer with it, on that issue. On Oh My Pi an Agents-page Send or Aside " + "arrives as a user message: answer in the conversation, with no dispatch message. " + `Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${ISSUE_REFERENCE}`,
|
|
14262
14261
|
arguments: (z2) => ({
|
|
14263
14262
|
issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
|
|
14264
14263
|
body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Update text, at most ${DISPATCH_BODY_MAX} characters.`),
|
|
@@ -14275,7 +14274,7 @@ var dispatchToolSpecs = [
|
|
|
14275
14274
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
14276
14275
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
14277
14276
|
},
|
|
14278
|
-
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>"; dispatch doc-read --blocks lists every block's id, type, opening words and token, and each table's rows and cells with the indexes delete_row and delete_column take (GET /api/v1/artifacts/{artifact UUID}/blocks serves the same list). ` + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch doc-read, or block {id, token} values from dispatch doc-read --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
|
|
14277
|
+
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>"; dispatch doc-read --blocks lists every block's id, type, opening words and token, and each table's rows and cells with the indexes delete_row and delete_column take (GET /api/v1/artifacts/{artifact UUID}/blocks serves the same list). ` + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch doc-read, or block {id, token} values from dispatch doc-read --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. " + "One replace per paragraph: more new paragraphs go in one insert after the last one you rewrote, since two inserts at one anchor come out reversed; read the passage back with dispatch doc-read afterwards. " + `The spec holds requirements, design, and decisions - never progress, status, or timestamps; a record of open items or progress is a document of its own. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
|
|
14279
14278
|
arguments: (z2) => ({
|
|
14280
14279
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14281
14280
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14322,12 +14321,12 @@ var dispatchToolSpecs = [
|
|
|
14322
14321
|
{
|
|
14323
14322
|
name: "dispatch_request_approval",
|
|
14324
14323
|
example: { issue: "DSP-1", summary: "A live sync replaces the nightly export." },
|
|
14325
|
-
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect
|
|
14324
|
+
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect runs `plan-gap-analyst` and `plan-reviewer`. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. An open request follows the document: a " + "later version moves it to that version and leaves it waiting on you, as a human's reply in " + "its thread does. Only the first move since the request was opened or handed back sends an " + "event, and never to the session whose version made it; dispatch doc-read shows whom it " + "waits on. Once the revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", call this again to hand that same Inbox row back. The request carries nothing new. A " + "call while it already waits on the human hands nothing back: the same summary changes " + "nothing, and a different one is refused, since it would rewrite the card the human is " + "reading. Approve and Request changes each close the request, so the next call opens a new " + "one. Call it once per revision, when the revision is complete, never after each edit. An " + "approval goes stale when the document changes outside its decision blocks after it: " + "request approval again once that revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", with a one-sentence summary naming what changed since the earlier approval: the card " + "names that approval (version, date, reviewer) and its Show diff control carries the detail. " + "A human's own answer to a decision block carries the approval to the version it writes when " + "that answer is the whole change since the approved version, so nothing is requested for " + "it. A new version of a Legion root spec closes its armed design gate until a human " + "approves it; the carried approval reopens it too. " + "Refused, with nothing sent, while the document holds an open decision block, even when a " + "human asked for approval: the refusal names each block; ask the human to answer or waive " + "it first. " + OWNER_REFERENCE,
|
|
14326
14325
|
arguments: (z2) => ({
|
|
14327
14326
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14328
14327
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14329
14328
|
artifact: z2.string().describe("Project document artifact id, slug, or filename; primary document by default for an issue.").optional(),
|
|
14330
|
-
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question; after an earlier approval, one sentence naming what changed since it, since the card shows that approval and a diff. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14329
|
+
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question; after an earlier approval, one sentence naming what changed since it, since the card shows that approval and a diff. A Legion architect adds one last sentence with its two spec reviews' results. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14331
14330
|
}),
|
|
14332
14331
|
validation: documentOwnerValidation(true)
|
|
14333
14332
|
},
|
|
@@ -14335,7 +14334,7 @@ var dispatchToolSpecs = [
|
|
|
14335
14334
|
name: "dispatch_artifact",
|
|
14336
14335
|
example: { issue: "DSP-1", name: "design.md", content: `# Design
|
|
14337
14336
|
` },
|
|
14338
|
-
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; a markdown document is at most 1 MiB and any other file at most 25 MiB. " + "Markdown is also refused with 413 CAP_EXCEEDED when it makes more than 65,536 elements (a block weighs 3, and an empty list item, quote, footnote definition or typed block 3 more for the empty paragraph it holds; a table cell 4, a hard line break 3, an image 3, a piece of inline HTML 2, an autolink 2, a footnote reference 2; inline syntax, marks, lines of text and backslash escapes or character references 1 each), or when a new version would leave the document's stored markdown (what its text reads back as) longer than 1 MiB or making more than 65,536 elements, and longer or heavier than before; shorten it or split it across documents. " + "Markdown holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK; a new version of a document is held to it only for the asks it writes or changes. " + `${OWNER_REFERENCE}`,
|
|
14337
|
+
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. Never upload a document's own name to change it (spec.md replaces the spec whole); edit it with dispatch doc-edit. Uploading the same name again makes its next version. Exactly one of path or content is required; a markdown document is at most 1 MiB and any other file at most 25 MiB. " + "Markdown is also refused with 413 CAP_EXCEEDED when it makes more than 65,536 elements (a block weighs 3, and an empty list item, quote, footnote definition or typed block 3 more for the empty paragraph it holds; a table cell 4, a hard line break 3, an image 3, a piece of inline HTML 2, an autolink 2, a footnote reference 2; inline syntax, marks, lines of text and backslash escapes or character references 1 each), or when a new version would leave the document's stored markdown (what its text reads back as) longer than 1 MiB or making more than 65,536 elements, and longer or heavier than before; shorten it or split it across documents. " + "Markdown holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK; a new version of a document is held to it only for the asks it writes or changes. " + `${OWNER_REFERENCE}`,
|
|
14339
14338
|
arguments: (z2) => ({
|
|
14340
14339
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14341
14340
|
project: z2.string().describe("Project key for an unlinked document.").optional(),
|
|
@@ -14360,7 +14359,7 @@ var dispatchToolSpecs = [
|
|
|
14360
14359
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14361
14360
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14362
14361
|
artifact: z2.string().describe("Document artifact id, slug, or filename: one of the issue's, or a project document.").optional(),
|
|
14363
|
-
ref: z2.string().describe("Optional dispatch://
|
|
14362
|
+
ref: z2.string().describe("Optional dispatch:// reference: dispatch://KEY, dispatch://KEY/spec, " + "dispatch://KEY/artifact/<slug>[@vN], dispatch://KEY/ask/<id>, dispatch://KEY/comment/<id>, " + "dispatch://KEY/message/<id>, dispatch://PROJECT/artifact/<slug>[@vN], and its " + "/ask/<id> and /comment/<id>; or the dashboard URL of one.").optional(),
|
|
14364
14363
|
message: z2.string().describe("A message id (uuid): reads the conversation it belongs to, the root message and every " + "reply. Name nothing else with it.").optional(),
|
|
14365
14364
|
events: z2.number({ int: true, min: 1 }).describe(`How many of the issue's newest events an issue read or its dispatch://KEY/log lists, oldest first; default ${DEFAULT_ISSUE_READ_EVENTS}. Name no --all-events with it.`).optional(),
|
|
14366
14365
|
all_events: z2.boolean().describe(`List every one of the issue's events, oldest first, on an issue read or its dispatch://KEY/log, in place of the last ${DEFAULT_ISSUE_READ_EVENTS}. Name no --events with it.`).optional()
|
|
@@ -14392,7 +14391,7 @@ var dispatchToolSpecs = [
|
|
|
14392
14391
|
{
|
|
14393
14392
|
name: "dispatch_issues",
|
|
14394
14393
|
example: { project: "PROJ", limit: 250, offset: 250 },
|
|
14395
|
-
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
|
|
14394
|
+
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, as of the server's copy of the listener's session list, at " + "most 5 s old, 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. " + "all takes the whole filtered list in one read instead, for a caller that must not miss or " + "repeat a row while other writers change the list, and especially for one whose own writes " + "move issues out of what it filtered on.",
|
|
14396
14395
|
arguments: (z2) => ({
|
|
14397
14396
|
project: z2.string().describe("Project key to list issues from."),
|
|
14398
14397
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -14426,7 +14425,7 @@ var dispatchToolSpecs = [
|
|
|
14426
14425
|
{
|
|
14427
14426
|
name: "dispatch_architecture_sync",
|
|
14428
14427
|
example: { project: "CORE", repo: "acme/infra" },
|
|
14429
|
-
description: "Import a project's architecture model from its source repositories now, instead of waiting " + "for the server's five-minute schedule. A project may have several sources, one per " + "repository, and its model is every source's components together. Without repo every " + "source imports and each answers on its own line; with repo only that source does. Each " + "line names the imported commit, or the recorded error when that source's model was " + "rejected (its previous components stay up, and every other source is untouched). Sources " + "are saved in Settings or with PUT /api/v1/projects/{key}/architecture-sources/{owner}/{repo}; " + "404 SOURCE_NOT_FOUND when the project has none, or none for that repository.",
|
|
14428
|
+
description: "Import a project's architecture model from its source repositories now, instead of waiting " + "for the server's five-minute schedule. A project may have several sources, one per " + "repository, and its model is every source's components together. Without repo every " + "source imports and each answers on its own line; with repo only that source does. Each " + "line names the imported commit, or the recorded error when that source's model was " + "rejected (its previous components stay up, and every other source is untouched). A failed " + "line also says what of that source still shows (its commit, its component count when it " + "was saved again on another branch and has not imported from it yet, or that it never " + "imported), and a credential or branch problem is that source's failed line, or with repo " + "the error 409 SOURCE_ACCESS. Sources " + "are saved in Settings or with PUT /api/v1/projects/{key}/architecture-sources/{owner}/{repo}; " + "404 SOURCE_NOT_FOUND when the project has none, or none for that repository.",
|
|
14430
14429
|
arguments: (z2) => ({
|
|
14431
14430
|
project: z2.string().describe("Project key whose architecture sources to sync, such as CORE."),
|
|
14432
14431
|
repo: z2.string().describe("One source repository of the project to sync alone, owner/name; omit it to sync every source.").optional()
|
|
@@ -16371,10 +16370,10 @@ function renderAdvice(tool, key, advice, opts) {
|
|
|
16371
16370
|
if (unparsed !== undefined && unparsed.count > 0 && (tool === "dispatch_issue" || tool === "dispatch_artifact")) {
|
|
16372
16371
|
const quoted = unparsed.examples.map((example) => JSON.stringify(example)).join(", ");
|
|
16373
16372
|
const subject2 = unparsed.count === 1 ? "1 typed-block opening in this document is text, not a block" : `${unparsed.count} typed-block openings in this document are text, not blocks`;
|
|
16374
|
-
lines.push(`${subject2}: ${quoted}. An opening like \`:::ask{\u2026}\` makes a block only as a line of its own, so as text it asks nobody. Mentioning the syntax on purpose? Put it in code. See the \`dispatch\` skill, "
|
|
16373
|
+
lines.push(`${subject2}: ${quoted}. An opening like \`:::ask{\u2026}\` makes a block only as a line of its own, so as text it asks nobody. Mentioning the syntax on purpose? Put it in code. See the \`dispatch-writing-specs\` skill, "Questions".`);
|
|
16375
16374
|
}
|
|
16376
16375
|
if (advice.decision_blocks === 0 && opts.isPrimarySpec === true && (tool === "dispatch_issue" || tool === "dispatch_artifact")) {
|
|
16377
|
-
lines.push(
|
|
16376
|
+
lines.push("This spec holds no ask blocks. A person does not watch your chat. They see that you need them only through an open ask or a spec that waits for their approval. If the design needs a person's choice, add an ask block. If it does not, request approval with `dispatch request-approval`.");
|
|
16378
16377
|
}
|
|
16379
16378
|
if (advice.issue_status === "triage" && advice.legion_managed_status !== true && tool !== "dispatch_issue" && !(tool === "dispatch_issue_update" && opts.setsStatus === true) && !triageAdviceShown.has(key)) {
|
|
16380
16379
|
triageAdviceShown.add(key);
|
|
@@ -17424,9 +17423,18 @@ function actorText(actor) {
|
|
|
17424
17423
|
}
|
|
17425
17424
|
return `${actor.kind} ${actor.id} (as ${serviceSubjectLabel(service)})`;
|
|
17426
17425
|
}
|
|
17426
|
+
function eventRef(event) {
|
|
17427
|
+
const payload = event.payload;
|
|
17428
|
+
const id = typeof payload === "object" && payload !== null && "id" in payload ? payload.id : undefined;
|
|
17429
|
+
if (typeof id !== "string" || id === "" || event.issue_key === null)
|
|
17430
|
+
return;
|
|
17431
|
+
const kind = event.type.startsWith("message.") ? "message" : event.type.startsWith("comment.") || event.type.startsWith("suggestion.") ? "comment" : event.type.startsWith("ask.") ? "ask" : undefined;
|
|
17432
|
+
return kind === undefined ? undefined : `dispatch://${event.issue_key}/${kind}/${id}`;
|
|
17433
|
+
}
|
|
17427
17434
|
function eventLine(event) {
|
|
17428
17435
|
const head = eventHead(event);
|
|
17429
|
-
|
|
17436
|
+
const ref = eventRef(event);
|
|
17437
|
+
return `- #${event.seq} ${event.type}${ref === undefined ? "" : ` ${ref}`} \xB7 ${actorText(event.actor)} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
|
|
17430
17438
|
}
|
|
17431
17439
|
function childrenSummary(issue2) {
|
|
17432
17440
|
return [
|
package/dist/src/server.js
CHANGED
|
@@ -13617,6 +13617,7 @@ var askEventPayloadFields = {
|
|
|
13617
13617
|
text: exports_external.string().nullish(),
|
|
13618
13618
|
at: exports_external.string().optional()
|
|
13619
13619
|
}).nullish(),
|
|
13620
|
+
restored: exports_external.boolean().optional(),
|
|
13620
13621
|
anchor: exports_external.object({
|
|
13621
13622
|
quote: exports_external.string().optional(),
|
|
13622
13623
|
mark_id: exports_external.string().optional(),
|
|
@@ -13871,7 +13872,7 @@ function componentsArgument(z2) {
|
|
|
13871
13872
|
reason: z2.string({ min: 1 }).describe("For mode none: why this issue is not architectural (process, hiring, ops).").optional()
|
|
13872
13873
|
}).describe("Attach the issue to architecture components. Attach the root before decomposing it; children inherit unless they choose.");
|
|
13873
13874
|
}
|
|
13874
|
-
var SPEC_WRITING_POINTER =
|
|
13875
|
+
var SPEC_WRITING_POINTER = "Write a spec as skill://dispatch-writing-specs says.";
|
|
13875
13876
|
function imagesArgument(z2, owner, cap) {
|
|
13876
13877
|
return z2.array(z2.string({ min: 1 }), { min: 1 }).describe(`Optional local picture files to show inline: PNG, JPEG, GIF or WebP (by their bytes, not their name), each at most 25 MiB. Each is uploaded to ${owner} and appended after a blank line as one  line, in this order; those lines count toward ${cap}.`).optional();
|
|
13877
13878
|
}
|
|
@@ -13909,7 +13910,7 @@ var dispatchToolSpecs = [
|
|
|
13909
13910
|
{
|
|
13910
13911
|
name: "dispatch_issue",
|
|
13911
13912
|
example: { project: "DSP", title: "Native workspace" },
|
|
13912
|
-
description: "Create a native Dispatch issue for newly tracked work. Search first with dispatch search; if potentially duplicate issues exist, this returns 409 POSSIBLE_DUPLICATE unless --force is set after reading them. " + "When passing a spec, before calling, have a fresh read-only reader check it against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. " + "A spec holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK. " + `Do not use it when an existing issue already covers the work; read or update that issue instead. ${ISSUE_REFERENCE}`,
|
|
13913
|
+
description: "Create a native Dispatch issue for newly tracked work. Search first with dispatch search; if potentially duplicate issues exist, this returns 409 POSSIBLE_DUPLICATE unless --force is set after reading them. " + "When passing a spec, before calling, have a fresh read-only reader check it against the code and the human's words, then resolve every finding; a Legion architect uses `plan-gap-analyst`. " + "A spec holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK. " + "Create a project only when a person asks for one; work that spans projects goes in the project that owns most of it. " + `Do not use it when an existing issue already covers the work; read or update that issue instead. ${ISSUE_REFERENCE}`,
|
|
13913
13914
|
arguments: (z2) => ({
|
|
13914
13915
|
project: z2.string().describe("Project key for the new issue."),
|
|
13915
13916
|
title: z2.string({ max: ISSUE_TITLE_MAX }).describe("Concise issue title, at most 1,000 characters."),
|
|
@@ -13920,7 +13921,7 @@ var dispatchToolSpecs = [
|
|
|
13920
13921
|
spec: z2.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_POINTER}`).optional(),
|
|
13921
13922
|
labels: z2.array(z2.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
|
|
13922
13923
|
priority: z2.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority, a number from 0 to 3: 0 is highest and 3 is lowest.").optional(),
|
|
13923
|
-
assignee: z2.string().describe("Email of the person who answers this issue's asks (someone who has signed in to Dispatch); defaults to your owner when you act for a person, else the parent's assignee, else the project's default assignee when it still names a person who has signed in, else unassigned.").optional(),
|
|
13924
|
+
assignee: z2.string().describe("Email of the person who answers this issue's asks (someone who has signed in to Dispatch); defaults to your owner when you act for a person, else the parent's assignee, else the project's default assignee when it still names a person who has signed in, else unassigned. Set it only when a person said who owns the issue.").optional(),
|
|
13924
13925
|
components: componentsArgument(z2).optional(),
|
|
13925
13926
|
repo: z2.string().describe("The repository the issue targets, owner/name, mapped to the issue's project in Settings; a child inherits its parent's when unset, and an issue with none on its parent chain takes the project's default.").optional()
|
|
13926
13927
|
})
|
|
@@ -13932,7 +13933,7 @@ var dispatchToolSpecs = [
|
|
|
13932
13933
|
priority: 1,
|
|
13933
13934
|
before: "DSP-2"
|
|
13934
13935
|
},
|
|
13935
|
-
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, because a closed issue refuses messages, comments, and " + "artifacts; reason goes only with status done. A closed issue takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
13936
|
+
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, because a closed issue refuses messages, comments, and " + "artifacts; reason goes only with status done. A closed issue takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
13936
13937
|
arguments: (z2) => ({
|
|
13937
13938
|
issue: z2.string().describe(ISSUE_REFERENCE),
|
|
13938
13939
|
status: z2.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
@@ -13982,7 +13983,7 @@ var dispatchToolSpecs = [
|
|
|
13982
13983
|
}
|
|
13983
13984
|
]
|
|
13984
13985
|
},
|
|
13985
|
-
description: "Open a to-do or permission only a human can give, or a decision that has no document to " + "live in. A question about the design an issue's document records is not this tool: write " + "it into that document as a decision block (dispatch doc-edit inserting an ask block at the " + "end of the section it concerns), at every phase, approved spec or not; the block reaches " + "the Inbox and its answer lands next to its context. Never give an ask an Approve option: a " + "document is approved through dispatch request-approval. Do not use this tool for a status " + "update or discussion; use dispatch message instead. " + ASK_QUESTION_CONTRACT + " " + ASK_OPTIONS_CONTRACT + " For an action only a human can perform, state what it changes and risks as constraints. " + "Anything that requires a human to do, including a credential or grant renewal, is an ask, " + "never a message. " + "Anchor a to-do about a document passage, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". ` + `A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} characters, ` + `the lines images appends included, and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
13986
|
+
description: "Open a to-do or permission only a human can give, or a decision that has no document to " + "live in. A question about the design an issue's document records is not this tool: write " + "it into that document as a decision block (dispatch doc-edit inserting an ask block at the " + "end of the section it concerns), at every phase, approved spec or not; the block reaches " + "the Inbox and its answer lands next to its context. Never give an ask an Approve option: a " + "document is approved through dispatch request-approval. Do not use this tool for a status " + "update or discussion; use dispatch message instead. Never create an issue to park a " + "question: the question is an ask on the issue it concerns. " + ASK_QUESTION_CONTRACT + " " + ASK_OPTIONS_CONTRACT + " For an action only a human can perform, state what it changes and risks as constraints. " + "Anything that requires a human to do, including a credential or grant renewal, is an ask, " + "never a message. Measure what you can first, and put the measurement and the size of the " + "population it covers in the question; drop an option that cannot fix most of that " + "population, never offer it cut down, and keep an option not to act (Hold). Set urgency high " + "when your work halts on the answer, and keep working on everything else. " + "Anchor a to-do about a document passage, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". ` + `A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} characters, ` + `the lines images appends included, and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
13986
13987
|
arguments: (z2) => ({
|
|
13987
13988
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
13988
13989
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14046,7 +14047,7 @@ var dispatchToolSpecs = [
|
|
|
14046
14047
|
kind: "retracted",
|
|
14047
14048
|
reason: "A newer question supersedes this one."
|
|
14048
14049
|
},
|
|
14049
|
-
description: "Retract an open question that is moot or resolve one after finding the answer. This closes " + "the question without answering it. An ask that lives as an `ask` block in a document is " + 'closed in the document too. A reason beginning "removed from the document in version" is ' + "refused: that marks a retraction the document's own settlement wrote.",
|
|
14050
|
+
description: "Retract an open question that is moot or resolve one after finding the answer. This closes " + "the question without answering it. An ask that lives as an `ask` block in a document is " + 'closed in the document too. A reason beginning "removed from the document in version" is ' + "refused: that marks a retraction the document's own settlement wrote. Use retracted for an ask " + "a newer one replaces or the design made moot, resolved for one whose answer you found; an " + "answered ask cannot be resolved, and resolving records no decision by a person.",
|
|
14050
14051
|
arguments: (z2) => ({
|
|
14051
14052
|
ask: z2.string().describe("Ask id (uuid) to close; an 8+ hex prefix unique among this session's own open asks " + "works too."),
|
|
14052
14053
|
kind: z2.enum(["retracted", "resolved"]).describe("Whether the ask is retracted or self-resolved."),
|
|
@@ -14075,7 +14076,7 @@ var dispatchToolSpecs = [
|
|
|
14075
14076
|
{
|
|
14076
14077
|
name: "dispatch_comment",
|
|
14077
14078
|
example: { issue: "DSP-1", body: "Looks good." },
|
|
14078
|
-
description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${OWNER_REFERENCE}`,
|
|
14079
|
+
description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. A reply under an open ask asks for clarification, not an answer: answer it with reply_to_ask, or reword the ask with dispatch edit-ask, rather than opening a second ask. Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${OWNER_REFERENCE}`,
|
|
14079
14080
|
arguments: (z2) => ({
|
|
14080
14081
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14081
14082
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14115,7 +14116,7 @@ var dispatchToolSpecs = [
|
|
|
14115
14116
|
{
|
|
14116
14117
|
name: "dispatch_message",
|
|
14117
14118
|
example: { issue: "DSP-1", body: "Implementation started." },
|
|
14118
|
-
description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. Every other message names its issue. " + `Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${ISSUE_REFERENCE}`,
|
|
14119
|
+
description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. " + "Every other message names its issue; keep --issue whenever the frame you answer gave one. A BTW, Aside or Steer " + "frame carries a reply_with command: answer with it, on that issue. On Oh My Pi an Agents-page Send or Aside " + "arrives as a user message: answer in the conversation, with no dispatch message. " + `Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${ISSUE_REFERENCE}`,
|
|
14119
14120
|
arguments: (z2) => ({
|
|
14120
14121
|
issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
|
|
14121
14122
|
body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Update text, at most ${DISPATCH_BODY_MAX} characters.`),
|
|
@@ -14132,7 +14133,7 @@ var dispatchToolSpecs = [
|
|
|
14132
14133
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
14133
14134
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
14134
14135
|
},
|
|
14135
|
-
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>"; dispatch doc-read --blocks lists every block's id, type, opening words and token, and each table's rows and cells with the indexes delete_row and delete_column take (GET /api/v1/artifacts/{artifact UUID}/blocks serves the same list). ` + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch doc-read, or block {id, token} values from dispatch doc-read --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
|
|
14136
|
+
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>"; dispatch doc-read --blocks lists every block's id, type, opening words and token, and each table's rows and cells with the indexes delete_row and delete_column take (GET /api/v1/artifacts/{artifact UUID}/blocks serves the same list). ` + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch doc-read, or block {id, token} values from dispatch doc-read --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. " + "One replace per paragraph: more new paragraphs go in one insert after the last one you rewrote, since two inserts at one anchor come out reversed; read the passage back with dispatch doc-read afterwards. " + `The spec holds requirements, design, and decisions - never progress, status, or timestamps; a record of open items or progress is a document of its own. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
|
|
14136
14137
|
arguments: (z2) => ({
|
|
14137
14138
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14138
14139
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14179,12 +14180,12 @@ var dispatchToolSpecs = [
|
|
|
14179
14180
|
{
|
|
14180
14181
|
name: "dispatch_request_approval",
|
|
14181
14182
|
example: { issue: "DSP-1", summary: "A live sync replaces the nightly export." },
|
|
14182
|
-
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect
|
|
14183
|
+
description: "Ask a human to approve a document at its current version. Before calling, have a fresh read-only reader check the spec against the code and the human's words, then resolve every finding; a Legion architect runs `plan-gap-analyst` and `plan-reviewer`. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. An open request follows the document: a " + "later version moves it to that version and leaves it waiting on you, as a human's reply in " + "its thread does. Only the first move since the request was opened or handed back sends an " + "event, and never to the session whose version made it; dispatch doc-read shows whom it " + "waits on. Once the revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", call this again to hand that same Inbox row back. The request carries nothing new. A " + "call while it already waits on the human hands nothing back: the same summary changes " + "nothing, and a different one is refused, since it would rewrite the card the human is " + "reading. Approve and Request changes each close the request, so the next call opens a new " + "one. Call it once per revision, when the revision is complete, never after each edit. An " + "approval goes stale when the document changes outside its decision blocks after it: " + "request approval again once that revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", with a one-sentence summary naming what changed since the earlier approval: the card " + "names that approval (version, date, reviewer) and its Show diff control carries the detail. " + "A human's own answer to a decision block carries the approval to the version it writes when " + "that answer is the whole change since the approved version, so nothing is requested for " + "it. A new version of a Legion root spec closes its armed design gate until a human " + "approves it; the carried approval reopens it too. " + "Refused, with nothing sent, while the document holds an open decision block, even when a " + "human asked for approval: the refusal names each block; ask the human to answer or waive " + "it first. " + OWNER_REFERENCE,
|
|
14183
14184
|
arguments: (z2) => ({
|
|
14184
14185
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14185
14186
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14186
14187
|
artifact: z2.string().describe("Project document artifact id, slug, or filename; primary document by default for an issue.").optional(),
|
|
14187
|
-
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question; after an earlier approval, one sentence naming what changed since it, since the card shows that approval and a diff. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14188
|
+
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question; after an earlier approval, one sentence naming what changed since it, since the card shows that approval and a diff. A Legion architect adds one last sentence with its two spec reviews' results. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14188
14189
|
}),
|
|
14189
14190
|
validation: documentOwnerValidation(true)
|
|
14190
14191
|
},
|
|
@@ -14192,7 +14193,7 @@ var dispatchToolSpecs = [
|
|
|
14192
14193
|
name: "dispatch_artifact",
|
|
14193
14194
|
example: { issue: "DSP-1", name: "design.md", content: `# Design
|
|
14194
14195
|
` },
|
|
14195
|
-
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; a markdown document is at most 1 MiB and any other file at most 25 MiB. " + "Markdown is also refused with 413 CAP_EXCEEDED when it makes more than 65,536 elements (a block weighs 3, and an empty list item, quote, footnote definition or typed block 3 more for the empty paragraph it holds; a table cell 4, a hard line break 3, an image 3, a piece of inline HTML 2, an autolink 2, a footnote reference 2; inline syntax, marks, lines of text and backslash escapes or character references 1 each), or when a new version would leave the document's stored markdown (what its text reads back as) longer than 1 MiB or making more than 65,536 elements, and longer or heavier than before; shorten it or split it across documents. " + "Markdown holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK; a new version of a document is held to it only for the asks it writes or changes. " + `${OWNER_REFERENCE}`,
|
|
14196
|
+
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. Never upload a document's own name to change it (spec.md replaces the spec whole); edit it with dispatch doc-edit. Uploading the same name again makes its next version. Exactly one of path or content is required; a markdown document is at most 1 MiB and any other file at most 25 MiB. " + "Markdown is also refused with 413 CAP_EXCEEDED when it makes more than 65,536 elements (a block weighs 3, and an empty list item, quote, footnote definition or typed block 3 more for the empty paragraph it holds; a table cell 4, a hard line break 3, an image 3, a piece of inline HTML 2, an autolink 2, a footnote reference 2; inline syntax, marks, lines of text and backslash escapes or character references 1 each), or when a new version would leave the document's stored markdown (what its text reads back as) longer than 1 MiB or making more than 65,536 elements, and longer or heavier than before; shorten it or split it across documents. " + "Markdown holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK; a new version of a document is held to it only for the asks it writes or changes. " + `${OWNER_REFERENCE}`,
|
|
14196
14197
|
arguments: (z2) => ({
|
|
14197
14198
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14198
14199
|
project: z2.string().describe("Project key for an unlinked document.").optional(),
|
|
@@ -14217,7 +14218,7 @@ var dispatchToolSpecs = [
|
|
|
14217
14218
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14218
14219
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14219
14220
|
artifact: z2.string().describe("Document artifact id, slug, or filename: one of the issue's, or a project document.").optional(),
|
|
14220
|
-
ref: z2.string().describe("Optional dispatch://
|
|
14221
|
+
ref: z2.string().describe("Optional dispatch:// reference: dispatch://KEY, dispatch://KEY/spec, " + "dispatch://KEY/artifact/<slug>[@vN], dispatch://KEY/ask/<id>, dispatch://KEY/comment/<id>, " + "dispatch://KEY/message/<id>, dispatch://PROJECT/artifact/<slug>[@vN], and its " + "/ask/<id> and /comment/<id>; or the dashboard URL of one.").optional(),
|
|
14221
14222
|
message: z2.string().describe("A message id (uuid): reads the conversation it belongs to, the root message and every " + "reply. Name nothing else with it.").optional(),
|
|
14222
14223
|
events: z2.number({ int: true, min: 1 }).describe(`How many of the issue's newest events an issue read or its dispatch://KEY/log lists, oldest first; default ${DEFAULT_ISSUE_READ_EVENTS}. Name no --all-events with it.`).optional(),
|
|
14223
14224
|
all_events: z2.boolean().describe(`List every one of the issue's events, oldest first, on an issue read or its dispatch://KEY/log, in place of the last ${DEFAULT_ISSUE_READ_EVENTS}. Name no --events with it.`).optional()
|
|
@@ -14249,7 +14250,7 @@ var dispatchToolSpecs = [
|
|
|
14249
14250
|
{
|
|
14250
14251
|
name: "dispatch_issues",
|
|
14251
14252
|
example: { project: "PROJ", limit: 250, offset: 250 },
|
|
14252
|
-
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
|
|
14253
|
+
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, as of the server's copy of the listener's session list, at " + "most 5 s old, 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. " + "all takes the whole filtered list in one read instead, for a caller that must not miss or " + "repeat a row while other writers change the list, and especially for one whose own writes " + "move issues out of what it filtered on.",
|
|
14253
14254
|
arguments: (z2) => ({
|
|
14254
14255
|
project: z2.string().describe("Project key to list issues from."),
|
|
14255
14256
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -14283,7 +14284,7 @@ var dispatchToolSpecs = [
|
|
|
14283
14284
|
{
|
|
14284
14285
|
name: "dispatch_architecture_sync",
|
|
14285
14286
|
example: { project: "CORE", repo: "acme/infra" },
|
|
14286
|
-
description: "Import a project's architecture model from its source repositories now, instead of waiting " + "for the server's five-minute schedule. A project may have several sources, one per " + "repository, and its model is every source's components together. Without repo every " + "source imports and each answers on its own line; with repo only that source does. Each " + "line names the imported commit, or the recorded error when that source's model was " + "rejected (its previous components stay up, and every other source is untouched). Sources " + "are saved in Settings or with PUT /api/v1/projects/{key}/architecture-sources/{owner}/{repo}; " + "404 SOURCE_NOT_FOUND when the project has none, or none for that repository.",
|
|
14287
|
+
description: "Import a project's architecture model from its source repositories now, instead of waiting " + "for the server's five-minute schedule. A project may have several sources, one per " + "repository, and its model is every source's components together. Without repo every " + "source imports and each answers on its own line; with repo only that source does. Each " + "line names the imported commit, or the recorded error when that source's model was " + "rejected (its previous components stay up, and every other source is untouched). A failed " + "line also says what of that source still shows (its commit, its component count when it " + "was saved again on another branch and has not imported from it yet, or that it never " + "imported), and a credential or branch problem is that source's failed line, or with repo " + "the error 409 SOURCE_ACCESS. Sources " + "are saved in Settings or with PUT /api/v1/projects/{key}/architecture-sources/{owner}/{repo}; " + "404 SOURCE_NOT_FOUND when the project has none, or none for that repository.",
|
|
14287
14288
|
arguments: (z2) => ({
|
|
14288
14289
|
project: z2.string().describe("Project key whose architecture sources to sync, such as CORE."),
|
|
14289
14290
|
repo: z2.string().describe("One source repository of the project to sync alone, owner/name; omit it to sync every source.").optional()
|
package/package.json
CHANGED
package/skills/AGENTS.md
CHANGED
|
@@ -8,12 +8,13 @@ They are Markdown instructions loaded by Oh My Pi sessions; the daemon and the t
|
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| `asd-ste100/` | every session with Dispatch, before it writes text a person reads | the ASD-STE100 rules for text a reader must not misread (Legion's copy of the MIT-licensed skill; `LICENSE` beside it) |
|
|
10
10
|
| `ce-simplify-code/` | the implementer, once per pull request | the behaviour-preserving simplify pass, in the implementer's first implementing round (Legion's copy of the MIT-licensed Compound Engineering skill; `LICENSE` beside it) |
|
|
11
|
-
| `dispatch/` | every role, and any session writing to Dispatch |
|
|
12
|
-
| `dispatch-
|
|
13
|
-
| `dispatch-
|
|
11
|
+
| `dispatch/` | every role, and any session writing to Dispatch, before its first `dispatch` command | what `dispatch <command> --help` does not say: asks and ask blocks, choosing work, issue status, closing what you opened, structure, components, references, and the HTTP API |
|
|
12
|
+
| `dispatch-first/` | every session with Dispatch configured, injected by each host plugin on every request (Oh My Pi), on every `SessionStart` (startup, resume, clear, compact, fork) and `SubagentStart` (Claude Code), or as an instruction file (OpenCode) | searching Dispatch before acting, extending the existing issue, citing decisions, closing duplicates, reaching a person only through Dispatch, and which Dispatch skill to read when; kept under 60 lines and 6,000 characters |
|
|
13
|
+
| `dispatch-writing-specs/` | any session with Dispatch, before a spec or text a person reads | the aim, the outline, the text rules, questions, each version's check, decisions, old specs, approval, and the plan as `plan.md` |
|
|
14
14
|
| `envoy/` | every role | subscriptions, agent-to-agent messages, and topic formats |
|
|
15
|
+
| `leading-a-work-stream/` | a Legion architect, and any session that leads a team of agents | what a lead does and does not do, decision rights, check-ins, and facts proven with a command |
|
|
15
16
|
| `legion-architect/` | root and sub-architects | tree ownership, decomposition, waves, gates, integration, the acceptance before the merge |
|
|
16
|
-
| `legion-controller/` | the controller root process | wake routing, admitting
|
|
17
|
+
| `legion-controller/` | the controller root process | wake routing, admitting the work a person hands over, giving each clear defect a role reports to the tree that owns its code and filing one no tree owns, asking a person about every other finding, the daily report, escalation |
|
|
17
18
|
| `legion-oracle/` | any role doing research | repository-grounded research |
|
|
18
19
|
| `legion-retro/` | the implementer, at retro | the pre-merge retrospective, its Dispatch message, its last commit removing the issue's `.legion/<issue>/` from the head that merges, and the PR body content the pull request's changed paths require at that head |
|
|
19
20
|
| `legion-worker/` | planner, implementer, tester, reviewer, merger | the phase contracts: handoffs, GitHub identity, PR body and READY discipline, the merge-gate order |
|
|
@@ -21,9 +22,9 @@ They are Markdown instructions loaded by Oh My Pi sessions; the daemon and the t
|
|
|
21
22
|
| `thermonuclear-deep-review/` | the `thermonuclear-deep-review` agent, and the reviewer (the Security Guidelines) | the correctness rubric of the reviewer's pair, with its tagged, diff-triggered Security Guidelines and the attack on the PR body's claims (adapted from the MIT-licensed Thermos plugin in `cursor/plugins`; `LICENSE` beside it) |
|
|
22
23
|
|
|
23
24
|
The owning skill above is where each contract is defined; a role prompt that needs a contract from its own seat points there or restates only its own step. This file lists and does not restate.
|
|
24
|
-
The skills here ship in two Oh My Pi plugins, and the partition is kept in one place, `scripts/pi-plugin-prepack.sh` at the repository root, keyed by package name: `@sjawhar/pi-envoy` (`packages/pi-envoy`) ships `asd-ste100`, `dispatch`, `dispatch-first`, `dispatch-
|
|
25
|
+
The skills here ship in two Oh My Pi plugins, and the partition is kept in one place, `scripts/pi-plugin-prepack.sh` at the repository root, keyed by package name: `@sjawhar/pi-envoy` (`packages/pi-envoy`) ships `asd-ste100`, `dispatch`, `dispatch-first`, `dispatch-writing-specs` and `envoy`, the skills every session with Dispatch reads; `@sjawhar/pi-legion` (`packages/pi-legion`) ships `legion-architect`, `legion-controller`, `legion-oracle`, `legion-retro`, `legion-worker`, `leading-a-work-stream`, `ce-simplify-code`, `thermonuclear-code-quality` and `thermonuclear-deep-review`, the skills a Legion pane's prompts load. Each plugin's prepack stages its list into its `dist/skills`, and `packages/pi-shared/test/skills-partition.test.ts` holds the two lists to this directory and to the plugin's skill lists in `.github/workflows/release.yaml` (its `changes` filter and its version step): together they are every skill directory here, and none is in both. A link from a skill in one partition into a skill in the other (a Legion skill's link into `dispatch`) is written `skill://<name>/<path>`, never as a relative path: a staged partition is installed on its own, so a relative link out of it dangles, while a `skill://` link resolves by name once both plugins are installed.
|
|
25
26
|
A Legion prompt (a skill here, a role prompt, or an agent definition in `packages/pi-legion/agents/`) names a task agent only as `task(agent="<name>")` and a skill it tells the model to load only as `skill://<name>`. Those are the two forms the Go daemon's boot gate and `legion probe-image` resolve through Oh My Pi, refusing by name one it cannot find; a dispatch or a load written any other way goes unchecked. An agent or skill Legion's prompts name is shipped here or in `packages/pi-legion/agents/`, unless Oh My Pi bundles it.
|
|
26
|
-
A skill file an agent loads stays under 51,200 bytes, Oh My Pi's spill threshold (a longer one arrives with its middle cut out), and a skill's detail lives in `references/` files linked as `skill://<name>/references/<file>.md`, never by relative path, which a read by path cuts at 300 lines. The rules are `packages/pi-shared/test/skills-guard.ts`, run over each plugin's staged partition: `packages/pi-envoy/src/skills-guard.test.ts` over the Envoy plugin's five skills, with the `dispatch-first` budget check, and `packages/pi-legion/src/skills-guard.test.ts` over the Legion plugin's
|
|
27
|
+
A skill file an agent loads stays under 51,200 bytes, Oh My Pi's spill threshold (a longer one arrives with its middle cut out), and a skill's detail lives in `references/` files linked as `skill://<name>/references/<file>.md`, never by relative path, which a read by path cuts at 300 lines. The rules are `packages/pi-shared/test/skills-guard.ts`, run over each plugin's staged partition: `packages/pi-envoy/src/skills-guard.test.ts` over the Envoy plugin's five skills, with the `dispatch-first` budget check and the word budget of the three Dispatch skills (`dispatch`, `dispatch-first` and `dispatch-writing-specs` together under 2,000 words, every file and frontmatter counted), and `packages/pi-legion/src/skills-guard.test.ts` over the Legion plugin's nine, with the daemon's prompts as linking roots and every `legion-worker` reference linked from somewhere. Each fails on a file at the threshold, on any `SKILL.md` body of 500 lines or more, on a skill whose frontmatter `name` is not its directory's name, on a `skill://<name>/<path>` link that names a missing file or a missing `#heading` (resolved against this directory, where both partitions live), and on a relative link that leaves the staged partition or names no file. The Envoy guard also fails on a `task(agent="<name>")` in the Envoy plugin's skills that names an agent neither that plugin nor Oh My Pi bundles (the bundled list read off the pinned binary's `omp agents unpack` under `LEGION_TEST_OMP`, never written down), since those skills reach a session with no other plugin installed; a `skill://legion-*` mention there is a sentence conditioned on a Legion role, inert for a person and right for a pane with both plugins, which the guard leaves alone.
|
|
27
28
|
The text a worker boots with (its role prompt) lives in `packages/daemon/internal/prompts/roles/`,
|
|
28
29
|
embedded in the `legion` binary and composed per role by `packages/daemon/internal/prompts/prompts.go`;
|
|
29
30
|
`packages/daemon/internal/prompts/roles_test.go` holds the structural rules for those parts.
|