@sjawhar/pi-legion-envoy 0.41.2 → 0.42.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
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({
|
|
@@ -29899,15 +29901,16 @@ var dispatchToolSpecs = [
|
|
|
29899
29901
|
},
|
|
29900
29902
|
{
|
|
29901
29903
|
name: "dispatch_message",
|
|
29902
|
-
description: "Post a note
|
|
29904
|
+
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
29905
|
arguments: (z) => ({
|
|
29904
29906
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29905
|
-
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
29907
|
+
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
29908
|
+
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
29909
|
})
|
|
29907
29910
|
},
|
|
29908
29911
|
{
|
|
29909
29912
|
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
|
|
29913
|
+
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
29914
|
arguments: (z) => ({
|
|
29912
29915
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29913
29916
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -31028,6 +31031,12 @@ function dispatchAskQuestion(event) {
|
|
|
31028
31031
|
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31029
31032
|
return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
|
|
31030
31033
|
}
|
|
31034
|
+
function dispatchMessageReplyPreview(event) {
|
|
31035
|
+
if (event.type !== "message.created")
|
|
31036
|
+
return;
|
|
31037
|
+
const parsed = MessageEventPayloadSchema.safeParse(event.payload);
|
|
31038
|
+
return parsed.success && parsed.data.reply_body !== undefined && parsed.data.reply_body !== "" ? parsed.data.reply_body : undefined;
|
|
31039
|
+
}
|
|
31031
31040
|
function parseDispatchFrame(rawPayload) {
|
|
31032
31041
|
let value;
|
|
31033
31042
|
try {
|
|
@@ -31078,6 +31087,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31078
31087
|
let dispatchEvent;
|
|
31079
31088
|
let dispatchIssue;
|
|
31080
31089
|
let askQuestion;
|
|
31090
|
+
let messageReplyPreview;
|
|
31081
31091
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31082
31092
|
if (envelope.source === "dispatch") {
|
|
31083
31093
|
if (envelope.payload === undefined) {
|
|
@@ -31092,6 +31102,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31092
31102
|
return { skip: true, content: "", envelope };
|
|
31093
31103
|
}
|
|
31094
31104
|
askQuestion = dispatchAskQuestion(frame.event);
|
|
31105
|
+
messageReplyPreview = dispatchMessageReplyPreview(frame.event);
|
|
31095
31106
|
dispatchEvent = {
|
|
31096
31107
|
owner: dispatchOwner(frame.event, subject ?? envelope.topic),
|
|
31097
31108
|
...frame.event.issue_key === null ? {
|
|
@@ -31145,7 +31156,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31145
31156
|
...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
|
|
31146
31157
|
...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
|
|
31147
31158
|
...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
|
|
31148
|
-
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
|
|
31159
|
+
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? messageReplyPreview ?? envelope.in_reply_to },
|
|
31149
31160
|
...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
|
|
31150
31161
|
...reply === undefined ? {} : { reply_with: reply },
|
|
31151
31162
|
...role === undefined ? {} : {
|
|
@@ -31456,6 +31467,16 @@ class DispatchClient {
|
|
|
31456
31467
|
async message(issue, input) {
|
|
31457
31468
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
31458
31469
|
}
|
|
31470
|
+
async getMessage(issue, id) {
|
|
31471
|
+
return this.#json("GET", [
|
|
31472
|
+
"api",
|
|
31473
|
+
"v1",
|
|
31474
|
+
"issues",
|
|
31475
|
+
await this.#resolveIssue(issue),
|
|
31476
|
+
"messages",
|
|
31477
|
+
id
|
|
31478
|
+
]);
|
|
31479
|
+
}
|
|
31459
31480
|
async artifact(issue, input) {
|
|
31460
31481
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
31461
31482
|
if ("content" in input)
|
|
@@ -31720,10 +31741,10 @@ function parseDispatchRef(ref) {
|
|
|
31720
31741
|
id: targetID
|
|
31721
31742
|
};
|
|
31722
31743
|
}
|
|
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\/([^/]+))?$/);
|
|
31744
|
+
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
31745
|
if (!issueReference)
|
|
31725
31746
|
return null;
|
|
31726
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
31747
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
31727
31748
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
31728
31749
|
return null;
|
|
31729
31750
|
const owner = { kind: "issue", issue };
|
|
@@ -31745,6 +31766,8 @@ function parseDispatchRef(ref) {
|
|
|
31745
31766
|
return { owner, kind: "ask", id: ask };
|
|
31746
31767
|
if (comment)
|
|
31747
31768
|
return { owner, kind: "comment", id: comment };
|
|
31769
|
+
if (message)
|
|
31770
|
+
return { owner, kind: "message", id: message };
|
|
31748
31771
|
return { owner, kind: "issue", id: issue };
|
|
31749
31772
|
}
|
|
31750
31773
|
function askId(args) {
|
|
@@ -31757,6 +31780,16 @@ function askId(args) {
|
|
|
31757
31780
|
}
|
|
31758
31781
|
return reference.id;
|
|
31759
31782
|
}
|
|
31783
|
+
function messageReplyTo(args) {
|
|
31784
|
+
const replyTo = optionalString(args, "reply_to");
|
|
31785
|
+
if (replyTo === undefined || !replyTo.startsWith("dispatch://"))
|
|
31786
|
+
return replyTo;
|
|
31787
|
+
const reference = parseDispatchRef(replyTo);
|
|
31788
|
+
if (reference?.kind !== "message") {
|
|
31789
|
+
throw new Error("reply_to must be a bare message id or a dispatch://.../message/<id> reference");
|
|
31790
|
+
}
|
|
31791
|
+
return reference.id;
|
|
31792
|
+
}
|
|
31760
31793
|
function toolSchema(tool) {
|
|
31761
31794
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
31762
31795
|
if (!spec)
|
|
@@ -31768,7 +31801,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
31768
31801
|
return { args, ref: null, owner: null };
|
|
31769
31802
|
const refArgument = args.ref;
|
|
31770
31803
|
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>");
|
|
31804
|
+
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
31805
|
})() : null;
|
|
31773
31806
|
const issueArgument = args.issue;
|
|
31774
31807
|
const projectArgument = args.project;
|
|
@@ -31959,6 +31992,18 @@ function commentSummary({ comment, replies }) {
|
|
|
31959
31992
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
31960
31993
|
`);
|
|
31961
31994
|
}
|
|
31995
|
+
function messageSummary({ message, replies }) {
|
|
31996
|
+
const root = [
|
|
31997
|
+
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
31998
|
+
`Body: ${message.body}`
|
|
31999
|
+
];
|
|
32000
|
+
const chain = replies.flatMap((reply) => [
|
|
32001
|
+
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
32002
|
+
`Body: ${reply.body}`
|
|
32003
|
+
]);
|
|
32004
|
+
return ["Message:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
32005
|
+
`);
|
|
32006
|
+
}
|
|
31962
32007
|
async function openArtifactMarks(client, resolved) {
|
|
31963
32008
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
31964
32009
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -32166,9 +32211,15 @@ async function executeDispatchTool(input) {
|
|
|
32166
32211
|
};
|
|
32167
32212
|
}
|
|
32168
32213
|
case "dispatch_message": {
|
|
32169
|
-
const
|
|
32214
|
+
const replyTo = messageReplyTo(args);
|
|
32215
|
+
const message = await client.message(issue(), {
|
|
32216
|
+
body: stringArg(args, "body"),
|
|
32217
|
+
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
32218
|
+
actor
|
|
32219
|
+
});
|
|
32220
|
+
const messageRef = `dispatch://${message.issue_key}/message/${message.id}`;
|
|
32170
32221
|
return {
|
|
32171
|
-
text: `Posted message ${message.id}`,
|
|
32222
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
32172
32223
|
details: {
|
|
32173
32224
|
issue: message.issue_key,
|
|
32174
32225
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -32255,6 +32306,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
32255
32306
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
32256
32307
|
};
|
|
32257
32308
|
}
|
|
32309
|
+
if (ownerArguments.ref?.kind === "message") {
|
|
32310
|
+
if (ownerArguments.ref.owner.kind !== "issue") {
|
|
32311
|
+
throw new Error("message references are issue-scoped");
|
|
32312
|
+
}
|
|
32313
|
+
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
32314
|
+
return {
|
|
32315
|
+
text: messageSummary(messageRead),
|
|
32316
|
+
details: { issue: messageRead.message.issue_key }
|
|
32317
|
+
};
|
|
32318
|
+
}
|
|
32258
32319
|
if (documentOwner().kind === "project") {
|
|
32259
32320
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
32260
32321
|
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({
|
|
@@ -29898,15 +29900,16 @@ var dispatchToolSpecs = [
|
|
|
29898
29900
|
},
|
|
29899
29901
|
{
|
|
29900
29902
|
name: "dispatch_message",
|
|
29901
|
-
description: "Post a note
|
|
29903
|
+
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
29904
|
arguments: (z) => ({
|
|
29903
29905
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29904
|
-
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
29906
|
+
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
29907
|
+
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
29908
|
})
|
|
29906
29909
|
},
|
|
29907
29910
|
{
|
|
29908
29911
|
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
|
|
29912
|
+
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
29913
|
arguments: (z) => ({
|
|
29911
29914
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29912
29915
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -31475,6 +31478,12 @@ function dispatchAskQuestion(event) {
|
|
|
31475
31478
|
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31476
31479
|
return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
|
|
31477
31480
|
}
|
|
31481
|
+
function dispatchMessageReplyPreview(event) {
|
|
31482
|
+
if (event.type !== "message.created")
|
|
31483
|
+
return;
|
|
31484
|
+
const parsed = MessageEventPayloadSchema.safeParse(event.payload);
|
|
31485
|
+
return parsed.success && parsed.data.reply_body !== undefined && parsed.data.reply_body !== "" ? parsed.data.reply_body : undefined;
|
|
31486
|
+
}
|
|
31478
31487
|
function parseDispatchFrame(rawPayload) {
|
|
31479
31488
|
let value;
|
|
31480
31489
|
try {
|
|
@@ -31525,6 +31534,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31525
31534
|
let dispatchEvent;
|
|
31526
31535
|
let dispatchIssue;
|
|
31527
31536
|
let askQuestion;
|
|
31537
|
+
let messageReplyPreview;
|
|
31528
31538
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31529
31539
|
if (envelope.source === "dispatch") {
|
|
31530
31540
|
if (envelope.payload === undefined) {
|
|
@@ -31539,6 +31549,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31539
31549
|
return { skip: true, content: "", envelope };
|
|
31540
31550
|
}
|
|
31541
31551
|
askQuestion = dispatchAskQuestion(frame.event);
|
|
31552
|
+
messageReplyPreview = dispatchMessageReplyPreview(frame.event);
|
|
31542
31553
|
dispatchEvent = {
|
|
31543
31554
|
owner: dispatchOwner(frame.event, subject ?? envelope.topic),
|
|
31544
31555
|
...frame.event.issue_key === null ? {
|
|
@@ -31592,7 +31603,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31592
31603
|
...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
|
|
31593
31604
|
...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
|
|
31594
31605
|
...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
|
|
31595
|
-
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
|
|
31606
|
+
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? messageReplyPreview ?? envelope.in_reply_to },
|
|
31596
31607
|
...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
|
|
31597
31608
|
...reply === undefined ? {} : { reply_with: reply },
|
|
31598
31609
|
...role === undefined ? {} : {
|
|
@@ -31896,6 +31907,16 @@ class DispatchClient {
|
|
|
31896
31907
|
async message(issue, input) {
|
|
31897
31908
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
31898
31909
|
}
|
|
31910
|
+
async getMessage(issue, id) {
|
|
31911
|
+
return this.#json("GET", [
|
|
31912
|
+
"api",
|
|
31913
|
+
"v1",
|
|
31914
|
+
"issues",
|
|
31915
|
+
await this.#resolveIssue(issue),
|
|
31916
|
+
"messages",
|
|
31917
|
+
id
|
|
31918
|
+
]);
|
|
31919
|
+
}
|
|
31899
31920
|
async artifact(issue, input) {
|
|
31900
31921
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
31901
31922
|
if ("content" in input)
|
|
@@ -32160,10 +32181,10 @@ function parseDispatchRef(ref) {
|
|
|
32160
32181
|
id: targetID
|
|
32161
32182
|
};
|
|
32162
32183
|
}
|
|
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\/([^/]+))?$/);
|
|
32184
|
+
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
32185
|
if (!issueReference)
|
|
32165
32186
|
return null;
|
|
32166
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
32187
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
32167
32188
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
32168
32189
|
return null;
|
|
32169
32190
|
const owner = { kind: "issue", issue };
|
|
@@ -32185,6 +32206,8 @@ function parseDispatchRef(ref) {
|
|
|
32185
32206
|
return { owner, kind: "ask", id: ask };
|
|
32186
32207
|
if (comment)
|
|
32187
32208
|
return { owner, kind: "comment", id: comment };
|
|
32209
|
+
if (message)
|
|
32210
|
+
return { owner, kind: "message", id: message };
|
|
32188
32211
|
return { owner, kind: "issue", id: issue };
|
|
32189
32212
|
}
|
|
32190
32213
|
function askId(args) {
|
|
@@ -32197,6 +32220,16 @@ function askId(args) {
|
|
|
32197
32220
|
}
|
|
32198
32221
|
return reference.id;
|
|
32199
32222
|
}
|
|
32223
|
+
function messageReplyTo(args) {
|
|
32224
|
+
const replyTo = optionalString(args, "reply_to");
|
|
32225
|
+
if (replyTo === undefined || !replyTo.startsWith("dispatch://"))
|
|
32226
|
+
return replyTo;
|
|
32227
|
+
const reference = parseDispatchRef(replyTo);
|
|
32228
|
+
if (reference?.kind !== "message") {
|
|
32229
|
+
throw new Error("reply_to must be a bare message id or a dispatch://.../message/<id> reference");
|
|
32230
|
+
}
|
|
32231
|
+
return reference.id;
|
|
32232
|
+
}
|
|
32200
32233
|
function toolSchema(tool) {
|
|
32201
32234
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
32202
32235
|
if (!spec)
|
|
@@ -32208,7 +32241,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
32208
32241
|
return { args, ref: null, owner: null };
|
|
32209
32242
|
const refArgument = args.ref;
|
|
32210
32243
|
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>");
|
|
32244
|
+
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
32245
|
})() : null;
|
|
32213
32246
|
const issueArgument = args.issue;
|
|
32214
32247
|
const projectArgument = args.project;
|
|
@@ -32399,6 +32432,18 @@ function commentSummary({ comment, replies }) {
|
|
|
32399
32432
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
32400
32433
|
`);
|
|
32401
32434
|
}
|
|
32435
|
+
function messageSummary({ message, replies }) {
|
|
32436
|
+
const root = [
|
|
32437
|
+
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
32438
|
+
`Body: ${message.body}`
|
|
32439
|
+
];
|
|
32440
|
+
const chain = replies.flatMap((reply) => [
|
|
32441
|
+
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
32442
|
+
`Body: ${reply.body}`
|
|
32443
|
+
]);
|
|
32444
|
+
return ["Message:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
32445
|
+
`);
|
|
32446
|
+
}
|
|
32402
32447
|
async function openArtifactMarks(client, resolved) {
|
|
32403
32448
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
32404
32449
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -32606,9 +32651,15 @@ async function executeDispatchTool(input) {
|
|
|
32606
32651
|
};
|
|
32607
32652
|
}
|
|
32608
32653
|
case "dispatch_message": {
|
|
32609
|
-
const
|
|
32654
|
+
const replyTo = messageReplyTo(args);
|
|
32655
|
+
const message = await client.message(issue(), {
|
|
32656
|
+
body: stringArg(args, "body"),
|
|
32657
|
+
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
32658
|
+
actor
|
|
32659
|
+
});
|
|
32660
|
+
const messageRef = `dispatch://${message.issue_key}/message/${message.id}`;
|
|
32610
32661
|
return {
|
|
32611
|
-
text: `Posted message ${message.id}`,
|
|
32662
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
32612
32663
|
details: {
|
|
32613
32664
|
issue: message.issue_key,
|
|
32614
32665
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -32695,6 +32746,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
32695
32746
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
32696
32747
|
};
|
|
32697
32748
|
}
|
|
32749
|
+
if (ownerArguments.ref?.kind === "message") {
|
|
32750
|
+
if (ownerArguments.ref.owner.kind !== "issue") {
|
|
32751
|
+
throw new Error("message references are issue-scoped");
|
|
32752
|
+
}
|
|
32753
|
+
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
32754
|
+
return {
|
|
32755
|
+
text: messageSummary(messageRead),
|
|
32756
|
+
details: { issue: messageRead.message.issue_key }
|
|
32757
|
+
};
|
|
32758
|
+
}
|
|
32698
32759
|
if (documentOwner().kind === "project") {
|
|
32699
32760
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
32700
32761
|
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,28 +164,6 @@ 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:
|
|
@@ -230,15 +209,17 @@ or by its filename; the slug also arrives on `artifact.created` events.
|
|
|
230
209
|
|
|
231
210
|
## Messages
|
|
232
211
|
|
|
233
|
-
|
|
234
|
-
|
|
212
|
+
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
|
|
213
|
+
change a human must know about now: a deliverable landed, a blocker only they can clear. Nothing else — no progress updates, no
|
|
214
|
+
"starting X", no "still working", no restating the spec, no status on a timer. Your transcript is where work is narrated; the
|
|
215
|
+
pull request is where it is summarised. One message that a human reads beats ten that train them to skip you.
|
|
235
216
|
|
|
236
217
|
```ts
|
|
237
218
|
dispatch_message({ issue, body })
|
|
238
219
|
```
|
|
239
220
|
|
|
240
|
-
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters.
|
|
241
|
-
|
|
221
|
+
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters. A message is not a decision
|
|
222
|
+
(`dispatch_ask`) or document feedback (`dispatch_comment`), and it does not wake anyone unless the issue is routed.
|
|
242
223
|
|
|
243
224
|
## What comes back
|
|
244
225
|
|
|
@@ -252,8 +233,8 @@ dispatch_read({ issue?, project?, artifact?, ref? })
|
|
|
252
233
|
|
|
253
234
|
With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
|
|
254
235
|
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.
|
|
236
|
+
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
|
|
237
|
+
message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
257
238
|
|
|
258
239
|
## References
|
|
259
240
|
|
|
@@ -265,6 +246,7 @@ dispatch://KEY/spec
|
|
|
265
246
|
dispatch://KEY/artifact/<slug>[@vN]
|
|
266
247
|
dispatch://KEY/ask/<id>
|
|
267
248
|
dispatch://KEY/comment/<id>
|
|
249
|
+
dispatch://KEY/message/<id>
|
|
268
250
|
dispatch://PROJECT/artifact/<slug>[@vN]
|
|
269
251
|
dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
270
252
|
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
@@ -299,27 +281,18 @@ dispatch_ask({
|
|
|
299
281
|
})
|
|
300
282
|
```
|
|
301
283
|
|
|
302
|
-
Before — progress
|
|
284
|
+
Before — a progress note that nobody needs, posted where humans look for decisions:
|
|
303
285
|
|
|
304
286
|
```ts
|
|
305
287
|
dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
|
|
306
288
|
```
|
|
307
289
|
|
|
308
|
-
After —
|
|
290
|
+
After — nothing. The merge is visible on the pull request; the docs work shows up as its own deliverable. Post a message only when
|
|
291
|
+
a human must act or a deliverable is theirs to use:
|
|
309
292
|
|
|
310
293
|
```ts
|
|
311
|
-
|
|
294
|
+
dispatch_message({
|
|
312
295
|
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
|
-
],
|
|
296
|
+
body: "Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.",
|
|
324
297
|
})
|
|
325
298
|
```
|
|
@@ -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
|
|