@sjawhar/pi-legion-envoy 0.41.2 → 0.43.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/envoy.js +83 -16
- package/dist/legion.js +83 -16
- package/dist/skills/dispatch/SKILL.md +35 -54
- package/dist/skills/legion-architect/SKILL.md +3 -3
- package/dist/skills/legion-worker/SKILL.md +1 -3
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29716,6 +29716,8 @@ var CommentEventPayloadSchema = object({
|
|
|
29716
29716
|
var MessageEventPayloadSchema = object({
|
|
29717
29717
|
id: string2().optional(),
|
|
29718
29718
|
body: string2().optional(),
|
|
29719
|
+
reply_to: string2().nullish(),
|
|
29720
|
+
reply_body: string2().optional(),
|
|
29719
29721
|
author: object({ kind: string2(), id: string2() }).optional()
|
|
29720
29722
|
});
|
|
29721
29723
|
var ChildStatusEventPayloadSchema = object({
|
|
@@ -29780,15 +29782,17 @@ function dispatchToolSchema(spec, z, opts) {
|
|
|
29780
29782
|
}
|
|
29781
29783
|
var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue in the repository's dashboard-configured project or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
|
|
29782
29784
|
var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
|
|
29783
|
-
function documentOwnerValidation(requireArtifact) {
|
|
29785
|
+
function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false) {
|
|
29784
29786
|
return {
|
|
29785
29787
|
check: (value) => {
|
|
29786
29788
|
const input = value;
|
|
29787
29789
|
const hasIssue = typeof input.issue === "string";
|
|
29788
29790
|
const hasProject = typeof input.project === "string";
|
|
29789
|
-
|
|
29791
|
+
const hasArtifact = typeof input.artifact === "string";
|
|
29792
|
+
const hasRef = typeof input.ref === "string";
|
|
29793
|
+
return (hasIssue !== hasProject || !hasIssue && !hasProject && hasRef) && (!hasProject || !requireArtifact || hasArtifact) && (!alwaysRequireArtifact || hasArtifact || hasRef);
|
|
29790
29794
|
},
|
|
29791
|
-
message: "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29795
|
+
message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29792
29796
|
};
|
|
29793
29797
|
}
|
|
29794
29798
|
var SPEC_SECTIONS = [
|
|
@@ -29823,6 +29827,7 @@ var dispatchToolSpecs = [
|
|
|
29823
29827
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29824
29828
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29825
29829
|
artifact: z.string().describe("Project document slug or id.").optional(),
|
|
29830
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29826
29831
|
question: z.string({ max: 800 }).describe("Decision question, at most 800 characters."),
|
|
29827
29832
|
options: z.array(z.object({
|
|
29828
29833
|
label: z.string().describe("Selectable option label."),
|
|
@@ -29875,6 +29880,7 @@ var dispatchToolSpecs = [
|
|
|
29875
29880
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29876
29881
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29877
29882
|
artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
|
|
29883
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29878
29884
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
29879
29885
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
29880
29886
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
@@ -29889,29 +29895,32 @@ var dispatchToolSpecs = [
|
|
|
29889
29895
|
arguments: (z) => ({
|
|
29890
29896
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29891
29897
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29892
|
-
artifact: z.string().describe("Artifact slug or id containing the quoted text."),
|
|
29898
|
+
artifact: z.string().describe("Artifact slug or id containing the quoted text; optional when ref names it.").optional(),
|
|
29899
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29893
29900
|
quote: z.string().describe("Exact document text to replace."),
|
|
29894
29901
|
replace_with: z.string().describe("Replacement text."),
|
|
29895
29902
|
body: z.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
29896
29903
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
29897
29904
|
}),
|
|
29898
|
-
validation: documentOwnerValidation(true)
|
|
29905
|
+
validation: documentOwnerValidation(true, true)
|
|
29899
29906
|
},
|
|
29900
29907
|
{
|
|
29901
29908
|
name: "dispatch_message",
|
|
29902
|
-
description: "Post a note
|
|
29909
|
+
description: "Post a note humans must read now: a reply to a human's message, a deliverable that landed, or a blocker only " + "they can clear. Never progress or status updates - Dispatch is a high-signal record, not a log. Not a decision " + `(dispatch_ask) or document feedback (dispatch_comment). Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
29903
29910
|
arguments: (z) => ({
|
|
29904
29911
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29905
|
-
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
29912
|
+
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
29913
|
+
reply_to: z.string().describe("Optional message id or dispatch://KEY/message/<id> reference to reply to, threading " + "this message under it so the reply stays with the original in the Conversation.").optional()
|
|
29906
29914
|
})
|
|
29907
29915
|
},
|
|
29908
29916
|
{
|
|
29909
29917
|
name: "dispatch_doc_edit",
|
|
29910
|
-
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " + "dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. The spec (or any document) holds requirements, " + `design, and decisions
|
|
29918
|
+
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " + "dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. The spec (or any document) holds requirements, " + `design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
29911
29919
|
arguments: (z) => ({
|
|
29912
29920
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29913
29921
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29914
|
-
artifact: z.string().describe("Artifact slug or id for the document."),
|
|
29922
|
+
artifact: z.string().describe("Artifact slug or id for the document; optional when ref names it.").optional(),
|
|
29923
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29915
29924
|
ops: z.array(z.object({
|
|
29916
29925
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
29917
29926
|
find: z.string().describe("Text to find for replace or delete.").optional(),
|
|
@@ -29923,7 +29932,7 @@ var dispatchToolSpecs = [
|
|
|
29923
29932
|
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
29924
29933
|
summary: z.string().describe("Optional named-version summary.").optional()
|
|
29925
29934
|
}),
|
|
29926
|
-
validation: documentOwnerValidation(true)
|
|
29935
|
+
validation: documentOwnerValidation(true, true)
|
|
29927
29936
|
},
|
|
29928
29937
|
{
|
|
29929
29938
|
name: "dispatch_doc_read",
|
|
@@ -31028,6 +31037,12 @@ function dispatchAskQuestion(event) {
|
|
|
31028
31037
|
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31029
31038
|
return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
|
|
31030
31039
|
}
|
|
31040
|
+
function dispatchMessageReplyPreview(event) {
|
|
31041
|
+
if (event.type !== "message.created")
|
|
31042
|
+
return;
|
|
31043
|
+
const parsed = MessageEventPayloadSchema.safeParse(event.payload);
|
|
31044
|
+
return parsed.success && parsed.data.reply_body !== undefined && parsed.data.reply_body !== "" ? parsed.data.reply_body : undefined;
|
|
31045
|
+
}
|
|
31031
31046
|
function parseDispatchFrame(rawPayload) {
|
|
31032
31047
|
let value;
|
|
31033
31048
|
try {
|
|
@@ -31078,6 +31093,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31078
31093
|
let dispatchEvent;
|
|
31079
31094
|
let dispatchIssue;
|
|
31080
31095
|
let askQuestion;
|
|
31096
|
+
let messageReplyPreview;
|
|
31081
31097
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31082
31098
|
if (envelope.source === "dispatch") {
|
|
31083
31099
|
if (envelope.payload === undefined) {
|
|
@@ -31092,6 +31108,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31092
31108
|
return { skip: true, content: "", envelope };
|
|
31093
31109
|
}
|
|
31094
31110
|
askQuestion = dispatchAskQuestion(frame.event);
|
|
31111
|
+
messageReplyPreview = dispatchMessageReplyPreview(frame.event);
|
|
31095
31112
|
dispatchEvent = {
|
|
31096
31113
|
owner: dispatchOwner(frame.event, subject ?? envelope.topic),
|
|
31097
31114
|
...frame.event.issue_key === null ? {
|
|
@@ -31145,7 +31162,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31145
31162
|
...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
|
|
31146
31163
|
...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
|
|
31147
31164
|
...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
|
|
31148
|
-
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
|
|
31165
|
+
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? messageReplyPreview ?? envelope.in_reply_to },
|
|
31149
31166
|
...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
|
|
31150
31167
|
...reply === undefined ? {} : { reply_with: reply },
|
|
31151
31168
|
...role === undefined ? {} : {
|
|
@@ -31456,6 +31473,16 @@ class DispatchClient {
|
|
|
31456
31473
|
async message(issue, input) {
|
|
31457
31474
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
31458
31475
|
}
|
|
31476
|
+
async getMessage(issue, id) {
|
|
31477
|
+
return this.#json("GET", [
|
|
31478
|
+
"api",
|
|
31479
|
+
"v1",
|
|
31480
|
+
"issues",
|
|
31481
|
+
await this.#resolveIssue(issue),
|
|
31482
|
+
"messages",
|
|
31483
|
+
id
|
|
31484
|
+
]);
|
|
31485
|
+
}
|
|
31459
31486
|
async artifact(issue, input) {
|
|
31460
31487
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
31461
31488
|
if ("content" in input)
|
|
@@ -31720,10 +31747,10 @@ function parseDispatchRef(ref) {
|
|
|
31720
31747
|
id: targetID
|
|
31721
31748
|
};
|
|
31722
31749
|
}
|
|
31723
|
-
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+))?$/);
|
|
31750
|
+
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+)|\/message\/([^/]+))?$/);
|
|
31724
31751
|
if (!issueReference)
|
|
31725
31752
|
return null;
|
|
31726
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
31753
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
31727
31754
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
31728
31755
|
return null;
|
|
31729
31756
|
const owner = { kind: "issue", issue };
|
|
@@ -31745,6 +31772,8 @@ function parseDispatchRef(ref) {
|
|
|
31745
31772
|
return { owner, kind: "ask", id: ask };
|
|
31746
31773
|
if (comment)
|
|
31747
31774
|
return { owner, kind: "comment", id: comment };
|
|
31775
|
+
if (message)
|
|
31776
|
+
return { owner, kind: "message", id: message };
|
|
31748
31777
|
return { owner, kind: "issue", id: issue };
|
|
31749
31778
|
}
|
|
31750
31779
|
function askId(args) {
|
|
@@ -31757,6 +31786,16 @@ function askId(args) {
|
|
|
31757
31786
|
}
|
|
31758
31787
|
return reference.id;
|
|
31759
31788
|
}
|
|
31789
|
+
function messageReplyTo(args) {
|
|
31790
|
+
const replyTo = optionalString(args, "reply_to");
|
|
31791
|
+
if (replyTo === undefined || !replyTo.startsWith("dispatch://"))
|
|
31792
|
+
return replyTo;
|
|
31793
|
+
const reference = parseDispatchRef(replyTo);
|
|
31794
|
+
if (reference?.kind !== "message") {
|
|
31795
|
+
throw new Error("reply_to must be a bare message id or a dispatch://.../message/<id> reference");
|
|
31796
|
+
}
|
|
31797
|
+
return reference.id;
|
|
31798
|
+
}
|
|
31760
31799
|
function toolSchema(tool) {
|
|
31761
31800
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
31762
31801
|
if (!spec)
|
|
@@ -31768,7 +31807,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
31768
31807
|
return { args, ref: null, owner: null };
|
|
31769
31808
|
const refArgument = args.ref;
|
|
31770
31809
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
31771
|
-
throw new Error("ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/artifact/<slug>, or dispatch://PROJECT/artifact/<slug>");
|
|
31810
|
+
throw new Error("ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/message/<uuid>, dispatch://KEY-1/artifact/<slug>, or " + "dispatch://PROJECT/artifact/<slug>");
|
|
31772
31811
|
})() : null;
|
|
31773
31812
|
const issueArgument = args.issue;
|
|
31774
31813
|
const projectArgument = args.project;
|
|
@@ -31959,6 +31998,18 @@ function commentSummary({ comment, replies }) {
|
|
|
31959
31998
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
31960
31999
|
`);
|
|
31961
32000
|
}
|
|
32001
|
+
function messageSummary({ message, replies }) {
|
|
32002
|
+
const root = [
|
|
32003
|
+
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
32004
|
+
`Body: ${message.body}`
|
|
32005
|
+
];
|
|
32006
|
+
const chain = replies.flatMap((reply) => [
|
|
32007
|
+
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
32008
|
+
`Body: ${reply.body}`
|
|
32009
|
+
]);
|
|
32010
|
+
return ["Message:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
32011
|
+
`);
|
|
32012
|
+
}
|
|
31962
32013
|
async function openArtifactMarks(client, resolved) {
|
|
31963
32014
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
31964
32015
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -32166,9 +32217,15 @@ async function executeDispatchTool(input) {
|
|
|
32166
32217
|
};
|
|
32167
32218
|
}
|
|
32168
32219
|
case "dispatch_message": {
|
|
32169
|
-
const
|
|
32220
|
+
const replyTo = messageReplyTo(args);
|
|
32221
|
+
const message = await client.message(issue(), {
|
|
32222
|
+
body: stringArg(args, "body"),
|
|
32223
|
+
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
32224
|
+
actor
|
|
32225
|
+
});
|
|
32226
|
+
const messageRef = `dispatch://${message.issue_key}/message/${message.id}`;
|
|
32170
32227
|
return {
|
|
32171
|
-
text: `Posted message ${message.id}`,
|
|
32228
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
32172
32229
|
details: {
|
|
32173
32230
|
issue: message.issue_key,
|
|
32174
32231
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -32255,6 +32312,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
32255
32312
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
32256
32313
|
};
|
|
32257
32314
|
}
|
|
32315
|
+
if (ownerArguments.ref?.kind === "message") {
|
|
32316
|
+
if (ownerArguments.ref.owner.kind !== "issue") {
|
|
32317
|
+
throw new Error("message references are issue-scoped");
|
|
32318
|
+
}
|
|
32319
|
+
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
32320
|
+
return {
|
|
32321
|
+
text: messageSummary(messageRead),
|
|
32322
|
+
details: { issue: messageRead.message.issue_key }
|
|
32323
|
+
};
|
|
32324
|
+
}
|
|
32258
32325
|
if (documentOwner().kind === "project") {
|
|
32259
32326
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
32260
32327
|
return {
|
package/dist/legion.js
CHANGED
|
@@ -29715,6 +29715,8 @@ var CommentEventPayloadSchema = object({
|
|
|
29715
29715
|
var MessageEventPayloadSchema = object({
|
|
29716
29716
|
id: string2().optional(),
|
|
29717
29717
|
body: string2().optional(),
|
|
29718
|
+
reply_to: string2().nullish(),
|
|
29719
|
+
reply_body: string2().optional(),
|
|
29718
29720
|
author: object({ kind: string2(), id: string2() }).optional()
|
|
29719
29721
|
});
|
|
29720
29722
|
var ChildStatusEventPayloadSchema = object({
|
|
@@ -29779,15 +29781,17 @@ function dispatchToolSchema(spec, z, opts) {
|
|
|
29779
29781
|
}
|
|
29780
29782
|
var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue in the repository's dashboard-configured project or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
|
|
29781
29783
|
var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
|
|
29782
|
-
function documentOwnerValidation(requireArtifact) {
|
|
29784
|
+
function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false) {
|
|
29783
29785
|
return {
|
|
29784
29786
|
check: (value) => {
|
|
29785
29787
|
const input = value;
|
|
29786
29788
|
const hasIssue = typeof input.issue === "string";
|
|
29787
29789
|
const hasProject = typeof input.project === "string";
|
|
29788
|
-
|
|
29790
|
+
const hasArtifact = typeof input.artifact === "string";
|
|
29791
|
+
const hasRef = typeof input.ref === "string";
|
|
29792
|
+
return (hasIssue !== hasProject || !hasIssue && !hasProject && hasRef) && (!hasProject || !requireArtifact || hasArtifact) && (!alwaysRequireArtifact || hasArtifact || hasRef);
|
|
29789
29793
|
},
|
|
29790
|
-
message: "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29794
|
+
message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29791
29795
|
};
|
|
29792
29796
|
}
|
|
29793
29797
|
var SPEC_SECTIONS = [
|
|
@@ -29822,6 +29826,7 @@ var dispatchToolSpecs = [
|
|
|
29822
29826
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29823
29827
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29824
29828
|
artifact: z.string().describe("Project document slug or id.").optional(),
|
|
29829
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29825
29830
|
question: z.string({ max: 800 }).describe("Decision question, at most 800 characters."),
|
|
29826
29831
|
options: z.array(z.object({
|
|
29827
29832
|
label: z.string().describe("Selectable option label."),
|
|
@@ -29874,6 +29879,7 @@ var dispatchToolSpecs = [
|
|
|
29874
29879
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29875
29880
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29876
29881
|
artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
|
|
29882
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29877
29883
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
29878
29884
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
29879
29885
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
@@ -29888,29 +29894,32 @@ var dispatchToolSpecs = [
|
|
|
29888
29894
|
arguments: (z) => ({
|
|
29889
29895
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29890
29896
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29891
|
-
artifact: z.string().describe("Artifact slug or id containing the quoted text."),
|
|
29897
|
+
artifact: z.string().describe("Artifact slug or id containing the quoted text; optional when ref names it.").optional(),
|
|
29898
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29892
29899
|
quote: z.string().describe("Exact document text to replace."),
|
|
29893
29900
|
replace_with: z.string().describe("Replacement text."),
|
|
29894
29901
|
body: z.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
29895
29902
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
29896
29903
|
}),
|
|
29897
|
-
validation: documentOwnerValidation(true)
|
|
29904
|
+
validation: documentOwnerValidation(true, true)
|
|
29898
29905
|
},
|
|
29899
29906
|
{
|
|
29900
29907
|
name: "dispatch_message",
|
|
29901
|
-
description: "Post a note
|
|
29908
|
+
description: "Post a note humans must read now: a reply to a human's message, a deliverable that landed, or a blocker only " + "they can clear. Never progress or status updates - Dispatch is a high-signal record, not a log. Not a decision " + `(dispatch_ask) or document feedback (dispatch_comment). Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
29902
29909
|
arguments: (z) => ({
|
|
29903
29910
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29904
|
-
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
29911
|
+
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
29912
|
+
reply_to: z.string().describe("Optional message id or dispatch://KEY/message/<id> reference to reply to, threading " + "this message under it so the reply stays with the original in the Conversation.").optional()
|
|
29905
29913
|
})
|
|
29906
29914
|
},
|
|
29907
29915
|
{
|
|
29908
29916
|
name: "dispatch_doc_edit",
|
|
29909
|
-
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " + "dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. The spec (or any document) holds requirements, " + `design, and decisions
|
|
29917
|
+
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " + "dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. The spec (or any document) holds requirements, " + `design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
29910
29918
|
arguments: (z) => ({
|
|
29911
29919
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29912
29920
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29913
|
-
artifact: z.string().describe("Artifact slug or id for the document."),
|
|
29921
|
+
artifact: z.string().describe("Artifact slug or id for the document; optional when ref names it.").optional(),
|
|
29922
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29914
29923
|
ops: z.array(z.object({
|
|
29915
29924
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
29916
29925
|
find: z.string().describe("Text to find for replace or delete.").optional(),
|
|
@@ -29922,7 +29931,7 @@ var dispatchToolSpecs = [
|
|
|
29922
29931
|
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
29923
29932
|
summary: z.string().describe("Optional named-version summary.").optional()
|
|
29924
29933
|
}),
|
|
29925
|
-
validation: documentOwnerValidation(true)
|
|
29934
|
+
validation: documentOwnerValidation(true, true)
|
|
29926
29935
|
},
|
|
29927
29936
|
{
|
|
29928
29937
|
name: "dispatch_doc_read",
|
|
@@ -31475,6 +31484,12 @@ function dispatchAskQuestion(event) {
|
|
|
31475
31484
|
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31476
31485
|
return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
|
|
31477
31486
|
}
|
|
31487
|
+
function dispatchMessageReplyPreview(event) {
|
|
31488
|
+
if (event.type !== "message.created")
|
|
31489
|
+
return;
|
|
31490
|
+
const parsed = MessageEventPayloadSchema.safeParse(event.payload);
|
|
31491
|
+
return parsed.success && parsed.data.reply_body !== undefined && parsed.data.reply_body !== "" ? parsed.data.reply_body : undefined;
|
|
31492
|
+
}
|
|
31478
31493
|
function parseDispatchFrame(rawPayload) {
|
|
31479
31494
|
let value;
|
|
31480
31495
|
try {
|
|
@@ -31525,6 +31540,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31525
31540
|
let dispatchEvent;
|
|
31526
31541
|
let dispatchIssue;
|
|
31527
31542
|
let askQuestion;
|
|
31543
|
+
let messageReplyPreview;
|
|
31528
31544
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31529
31545
|
if (envelope.source === "dispatch") {
|
|
31530
31546
|
if (envelope.payload === undefined) {
|
|
@@ -31539,6 +31555,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31539
31555
|
return { skip: true, content: "", envelope };
|
|
31540
31556
|
}
|
|
31541
31557
|
askQuestion = dispatchAskQuestion(frame.event);
|
|
31558
|
+
messageReplyPreview = dispatchMessageReplyPreview(frame.event);
|
|
31542
31559
|
dispatchEvent = {
|
|
31543
31560
|
owner: dispatchOwner(frame.event, subject ?? envelope.topic),
|
|
31544
31561
|
...frame.event.issue_key === null ? {
|
|
@@ -31592,7 +31609,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31592
31609
|
...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
|
|
31593
31610
|
...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
|
|
31594
31611
|
...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
|
|
31595
|
-
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
|
|
31612
|
+
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? messageReplyPreview ?? envelope.in_reply_to },
|
|
31596
31613
|
...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
|
|
31597
31614
|
...reply === undefined ? {} : { reply_with: reply },
|
|
31598
31615
|
...role === undefined ? {} : {
|
|
@@ -31896,6 +31913,16 @@ class DispatchClient {
|
|
|
31896
31913
|
async message(issue, input) {
|
|
31897
31914
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
31898
31915
|
}
|
|
31916
|
+
async getMessage(issue, id) {
|
|
31917
|
+
return this.#json("GET", [
|
|
31918
|
+
"api",
|
|
31919
|
+
"v1",
|
|
31920
|
+
"issues",
|
|
31921
|
+
await this.#resolveIssue(issue),
|
|
31922
|
+
"messages",
|
|
31923
|
+
id
|
|
31924
|
+
]);
|
|
31925
|
+
}
|
|
31899
31926
|
async artifact(issue, input) {
|
|
31900
31927
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
31901
31928
|
if ("content" in input)
|
|
@@ -32160,10 +32187,10 @@ function parseDispatchRef(ref) {
|
|
|
32160
32187
|
id: targetID
|
|
32161
32188
|
};
|
|
32162
32189
|
}
|
|
32163
|
-
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+))?$/);
|
|
32190
|
+
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+)|\/message\/([^/]+))?$/);
|
|
32164
32191
|
if (!issueReference)
|
|
32165
32192
|
return null;
|
|
32166
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
32193
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
32167
32194
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
32168
32195
|
return null;
|
|
32169
32196
|
const owner = { kind: "issue", issue };
|
|
@@ -32185,6 +32212,8 @@ function parseDispatchRef(ref) {
|
|
|
32185
32212
|
return { owner, kind: "ask", id: ask };
|
|
32186
32213
|
if (comment)
|
|
32187
32214
|
return { owner, kind: "comment", id: comment };
|
|
32215
|
+
if (message)
|
|
32216
|
+
return { owner, kind: "message", id: message };
|
|
32188
32217
|
return { owner, kind: "issue", id: issue };
|
|
32189
32218
|
}
|
|
32190
32219
|
function askId(args) {
|
|
@@ -32197,6 +32226,16 @@ function askId(args) {
|
|
|
32197
32226
|
}
|
|
32198
32227
|
return reference.id;
|
|
32199
32228
|
}
|
|
32229
|
+
function messageReplyTo(args) {
|
|
32230
|
+
const replyTo = optionalString(args, "reply_to");
|
|
32231
|
+
if (replyTo === undefined || !replyTo.startsWith("dispatch://"))
|
|
32232
|
+
return replyTo;
|
|
32233
|
+
const reference = parseDispatchRef(replyTo);
|
|
32234
|
+
if (reference?.kind !== "message") {
|
|
32235
|
+
throw new Error("reply_to must be a bare message id or a dispatch://.../message/<id> reference");
|
|
32236
|
+
}
|
|
32237
|
+
return reference.id;
|
|
32238
|
+
}
|
|
32200
32239
|
function toolSchema(tool) {
|
|
32201
32240
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
32202
32241
|
if (!spec)
|
|
@@ -32208,7 +32247,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
32208
32247
|
return { args, ref: null, owner: null };
|
|
32209
32248
|
const refArgument = args.ref;
|
|
32210
32249
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
32211
|
-
throw new Error("ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/artifact/<slug>, or dispatch://PROJECT/artifact/<slug>");
|
|
32250
|
+
throw new Error("ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/message/<uuid>, dispatch://KEY-1/artifact/<slug>, or " + "dispatch://PROJECT/artifact/<slug>");
|
|
32212
32251
|
})() : null;
|
|
32213
32252
|
const issueArgument = args.issue;
|
|
32214
32253
|
const projectArgument = args.project;
|
|
@@ -32399,6 +32438,18 @@ function commentSummary({ comment, replies }) {
|
|
|
32399
32438
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
32400
32439
|
`);
|
|
32401
32440
|
}
|
|
32441
|
+
function messageSummary({ message, replies }) {
|
|
32442
|
+
const root = [
|
|
32443
|
+
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
32444
|
+
`Body: ${message.body}`
|
|
32445
|
+
];
|
|
32446
|
+
const chain = replies.flatMap((reply) => [
|
|
32447
|
+
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
32448
|
+
`Body: ${reply.body}`
|
|
32449
|
+
]);
|
|
32450
|
+
return ["Message:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
32451
|
+
`);
|
|
32452
|
+
}
|
|
32402
32453
|
async function openArtifactMarks(client, resolved) {
|
|
32403
32454
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
32404
32455
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -32606,9 +32657,15 @@ async function executeDispatchTool(input) {
|
|
|
32606
32657
|
};
|
|
32607
32658
|
}
|
|
32608
32659
|
case "dispatch_message": {
|
|
32609
|
-
const
|
|
32660
|
+
const replyTo = messageReplyTo(args);
|
|
32661
|
+
const message = await client.message(issue(), {
|
|
32662
|
+
body: stringArg(args, "body"),
|
|
32663
|
+
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
32664
|
+
actor
|
|
32665
|
+
});
|
|
32666
|
+
const messageRef = `dispatch://${message.issue_key}/message/${message.id}`;
|
|
32610
32667
|
return {
|
|
32611
|
-
text: `Posted message ${message.id}`,
|
|
32668
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
32612
32669
|
details: {
|
|
32613
32670
|
issue: message.issue_key,
|
|
32614
32671
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -32695,6 +32752,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
32695
32752
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
32696
32753
|
};
|
|
32697
32754
|
}
|
|
32755
|
+
if (ownerArguments.ref?.kind === "message") {
|
|
32756
|
+
if (ownerArguments.ref.owner.kind !== "issue") {
|
|
32757
|
+
throw new Error("message references are issue-scoped");
|
|
32758
|
+
}
|
|
32759
|
+
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
32760
|
+
return {
|
|
32761
|
+
text: messageSummary(messageRead),
|
|
32762
|
+
details: { issue: messageRead.message.issue_key }
|
|
32763
|
+
};
|
|
32764
|
+
}
|
|
32698
32765
|
if (documentOwner().kind === "project") {
|
|
32699
32766
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
32700
32767
|
return {
|
|
@@ -5,8 +5,9 @@ description: "Use when asking Sami a question, updating the spec, commenting on
|
|
|
5
5
|
|
|
6
6
|
# Dispatch
|
|
7
7
|
|
|
8
|
-
Dispatch is your issue's or project document's living spec, asks, comments, and artifacts
|
|
9
|
-
|
|
8
|
+
Dispatch is your issue's or project document's living spec, asks, comments, and artifacts — a high-signal record for the humans who
|
|
9
|
+
decide, never a log of your work. The transcript is your scratch pad; progress and status stay there. Anything meant for a human
|
|
10
|
+
goes through a `dispatch_*` tool.
|
|
10
11
|
|
|
11
12
|
The server enforces high signal: an ask question is at most 800 characters with at most eight options; comment and message bodies are at
|
|
12
13
|
most 2,000 characters; an artifact is at most 25 MiB. It refuses over-limit input; it never truncates it. GitHub threads and markers no
|
|
@@ -121,8 +122,8 @@ resolved. A human may reply to an open or answered ask; so may you, e.g. after f
|
|
|
121
122
|
|
|
122
123
|
The spec holds requirements, design, acceptance, decisions, and rejected alternatives, structured per [Writing a spec](#writing-a-spec).
|
|
123
124
|
It changes only when a decision or requirement changes, and every version that records one is named with `summary`. Never write
|
|
124
|
-
progress, status, timestamps, an "Update HH:MMZ" section, a PR list, or handoff notes into the spec
|
|
125
|
-
[
|
|
125
|
+
progress, status, timestamps, an "Update HH:MMZ" section, a PR list, or handoff notes into the spec. Progress is not a
|
|
126
|
+
Dispatch object at all: it lives in your transcript and your pull request (see [Messages](#messages)).
|
|
126
127
|
|
|
127
128
|
Read the current document before changing it:
|
|
128
129
|
|
|
@@ -163,46 +164,27 @@ and deleting a cell's quoted text removes only that text.
|
|
|
163
164
|
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target; re-read a missing or ambiguous target before
|
|
164
165
|
retrying. Pass `summary` to name the version when recording a decision.
|
|
165
166
|
|
|
166
|
-
## Progress
|
|
167
|
-
|
|
168
|
-
Every issue you work has one progress artifact, `progress.md` — for humans reading later and for your own successor after compaction. It
|
|
169
|
-
is never a wake signal.
|
|
170
|
-
|
|
171
|
-
Create it once, on first use:
|
|
172
|
-
```ts
|
|
173
|
-
dispatch_artifact({ issue, name: "progress.md", content: "### 2026-09-11 15:00Z - Started\n..." })
|
|
174
|
-
```
|
|
175
|
-
The server slugs `progress.md` to `progress-md`; address every later edit with that slug. Append — never edit or remove an earlier
|
|
176
|
-
entry:
|
|
177
|
-
```ts
|
|
178
|
-
dispatch_doc_edit({
|
|
179
|
-
issue,
|
|
180
|
-
artifact: "progress-md",
|
|
181
|
-
ops: [{ op: "insert", after: "end", markdown: "### 2026-09-11 16:10Z - Blocked\n..." }],
|
|
182
|
-
})
|
|
183
|
-
```
|
|
184
|
-
Newest entry last. Each entry is `### <UTC time> - <headline>` followed by 1-5 lines: what changed (cite `dispatch://` refs or PR
|
|
185
|
-
links), what is blocked and on whom, and what is next. `.legion/<phase>.json` is the durable machine handoff between phases;
|
|
186
|
-
`progress.md` is the human-readable narrative for the same work — keep both, never conflate one for the other.
|
|
187
|
-
|
|
188
167
|
## Comments and suggestions
|
|
189
168
|
|
|
190
169
|
Add feedback with:
|
|
191
170
|
|
|
192
171
|
```ts
|
|
193
|
-
dispatch_comment({ issue?, project?, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
172
|
+
dispatch_comment({ issue?, project?, artifact?, ref?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
194
173
|
```
|
|
195
174
|
|
|
196
|
-
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
175
|
+
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
176
|
+
`ref` names the owner (an issue or project-document reference) in place of `issue`/`project`.
|
|
177
|
+
`quote` requires `artifact`; omit both for a floating issue comment. A reply (`reply_to`/`reply_to_ask`)
|
|
178
|
+
takes no `quote`; it belongs to its parent's anchor. Use `reply_to` to continue a comment thread at
|
|
179
|
+
its root; a reply to a resolved thread reopens it. Use `reply_to_ask` to reply directly under a
|
|
180
|
+
question asked with `dispatch_ask`. Comments are edited only by their author from the dashboard. A
|
|
181
|
+
delivered `comment.created` event carries the comment `id`; reply to it with
|
|
182
|
+
`dispatch_comment({ reply_to: <id> })`.
|
|
201
183
|
|
|
202
184
|
Propose an exact replacement instead of describing it:
|
|
203
185
|
|
|
204
186
|
```ts
|
|
205
|
-
dispatch_suggest({ issue?, project?, artifact, quote, replace_with, body?, occurrence? })
|
|
187
|
+
dispatch_suggest({ issue?, project?, artifact, ref?, quote, replace_with, body?, occurrence? })
|
|
206
188
|
```
|
|
207
189
|
|
|
208
190
|
It returns issue or project-document owner details plus `comment` and its write `topic`. A human accepts or rejects a suggestion.
|
|
@@ -230,15 +212,17 @@ or by its filename; the slug also arrives on `artifact.created` events.
|
|
|
230
212
|
|
|
231
213
|
## Messages
|
|
232
214
|
|
|
233
|
-
|
|
234
|
-
|
|
215
|
+
Dispatch is a high-signal record for humans, not a log of what you are doing. A message is a reply to a human's message, or a
|
|
216
|
+
change a human must know about now: a deliverable landed, a blocker only they can clear. Nothing else — no progress updates, no
|
|
217
|
+
"starting X", no "still working", no restating the spec, no status on a timer. Your transcript is where work is narrated; the
|
|
218
|
+
pull request is where it is summarised. One message that a human reads beats ten that train them to skip you.
|
|
235
219
|
|
|
236
220
|
```ts
|
|
237
221
|
dispatch_message({ issue, body })
|
|
238
222
|
```
|
|
239
223
|
|
|
240
|
-
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters.
|
|
241
|
-
|
|
224
|
+
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters. A message is not a decision
|
|
225
|
+
(`dispatch_ask`) or document feedback (`dispatch_comment`), and it does not wake anyone unless the issue is routed.
|
|
242
226
|
|
|
243
227
|
## What comes back
|
|
244
228
|
|
|
@@ -252,12 +236,17 @@ dispatch_read({ issue?, project?, artifact?, ref? })
|
|
|
252
236
|
|
|
253
237
|
With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
|
|
254
238
|
document owner or ref, it returns a document summary with `details` `{ project, document }`. With an ask ref, it returns that ask's
|
|
255
|
-
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain.
|
|
256
|
-
not subscribe; use `dispatch_doc_read` for document contents.
|
|
239
|
+
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
|
|
240
|
+
message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
257
241
|
|
|
258
242
|
## References
|
|
259
243
|
|
|
260
|
-
Use these in document, ask, comment, and message bodies
|
|
244
|
+
Use these in document, ask, comment, and message bodies. In the dashboard, a reference renders
|
|
245
|
+
as an inline link whose text is the target's title (an issue's title, an ask's question, a
|
|
246
|
+
comment's first line, a document's name) once it resolves; a body that is only a bare reference
|
|
247
|
+
still gets an unfurl card instead. Every `ref` argument below (and `issue`/`project`) accepts
|
|
248
|
+
either form — an issue key or a project key is never ambiguous, since a project key never
|
|
249
|
+
contains a dash:
|
|
261
250
|
|
|
262
251
|
```text
|
|
263
252
|
dispatch://KEY
|
|
@@ -265,6 +254,7 @@ dispatch://KEY/spec
|
|
|
265
254
|
dispatch://KEY/artifact/<slug>[@vN]
|
|
266
255
|
dispatch://KEY/ask/<id>
|
|
267
256
|
dispatch://KEY/comment/<id>
|
|
257
|
+
dispatch://KEY/message/<id>
|
|
268
258
|
dispatch://PROJECT/artifact/<slug>[@vN]
|
|
269
259
|
dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
270
260
|
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
@@ -299,27 +289,18 @@ dispatch_ask({
|
|
|
299
289
|
})
|
|
300
290
|
```
|
|
301
291
|
|
|
302
|
-
Before — progress
|
|
292
|
+
Before — a progress note that nobody needs, posted where humans look for decisions:
|
|
303
293
|
|
|
304
294
|
```ts
|
|
305
295
|
dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
|
|
306
296
|
```
|
|
307
297
|
|
|
308
|
-
After —
|
|
298
|
+
After — nothing. The merge is visible on the pull request; the docs work shows up as its own deliverable. Post a message only when
|
|
299
|
+
a human must act or a deliverable is theirs to use:
|
|
309
300
|
|
|
310
301
|
```ts
|
|
311
|
-
|
|
302
|
+
dispatch_message({
|
|
312
303
|
issue: "LEGION-815",
|
|
313
|
-
|
|
314
|
-
ops: [
|
|
315
|
-
{
|
|
316
|
-
op: "insert",
|
|
317
|
-
after: "end",
|
|
318
|
-
markdown:
|
|
319
|
-
"### 2026-09-11 15:40Z - Release PR merged\n" +
|
|
320
|
-
"- dispatch://LEGION-815/artifact/spec stays unchanged; this is progress, not a decision.\n" +
|
|
321
|
-
"- Next: docs review.",
|
|
322
|
-
},
|
|
323
|
-
],
|
|
304
|
+
body: "Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.",
|
|
324
305
|
})
|
|
325
306
|
```
|
|
@@ -71,9 +71,9 @@ exercise a criterion end to end, building that path is a child issue of this tre
|
|
|
71
71
|
inert until released.
|
|
72
72
|
|
|
73
73
|
Specifications written into Dispatch follow [`skills/dispatch`'s Writing a spec](../dispatch/SKILL.md#writing-a-spec).
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
Wave releases, child closures, and your own status are visible from the issue tree and the
|
|
75
|
+
handoffs; do not narrate them into the spec or a `dispatch_message`. A blocker only Sami can
|
|
76
|
+
clear is a `dispatch_ask`.
|
|
77
77
|
|
|
78
78
|
Write one root specification containing the accepted scope, adoption/decomposition,
|
|
79
79
|
waves, acceptance criteria, and integration test. When the config-armed root design gate
|
|
@@ -97,9 +97,7 @@ committed predecessor handoffs in lifecycle order from `$LEGION_WORKSPACE/.legio
|
|
|
97
97
|
Read only files that precede the assigned phase. There is no handoff schema (rejected
|
|
98
98
|
design — no schema validation runs anywhere in this pipeline): write the phase-specific
|
|
99
99
|
fields the next phase and the architect need, consistent with what predecessor phases
|
|
100
|
-
already wrote. The durable copy lives in `$LEGION_WORKSPACE/.legion/<phase>.json
|
|
101
|
-
handoff between phases, not a human-readable status; post that to the issue's `progress.md`
|
|
102
|
-
artifact instead (see [`skills/dispatch`'s Progress](../dispatch/SKILL.md#progress)). If a
|
|
100
|
+
already wrote. The durable copy lives in `$LEGION_WORKSPACE/.legion/<phase>.json`. If a
|
|
103
101
|
committed handoff conflicts with memory or a prior transcript, the committed file wins: it
|
|
104
102
|
is the copy that survived.
|
|
105
103
|
|