@sjawhar/pi-legion-envoy 0.41.1 → 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 +82 -15
- package/dist/legion.js +82 -15
- package/dist/skills/dispatch/SKILL.md +96 -139
- package/dist/skills/legion-architect/SKILL.md +3 -0
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29663,14 +29663,16 @@ var IssueEventPayloadSchema = object({
|
|
|
29663
29663
|
route: string2().nullish()
|
|
29664
29664
|
});
|
|
29665
29665
|
var ArtifactCreatedEventPayloadSchema = object({
|
|
29666
|
-
artifact: object({ name: string2().optional() }).optional()
|
|
29666
|
+
artifact: object({ id: string2().optional(), slug: string2().optional(), name: string2().optional() }).optional()
|
|
29667
29667
|
});
|
|
29668
29668
|
var ArtifactVersionEventPayloadSchema = object({
|
|
29669
|
+
artifact_id: string2().optional(),
|
|
29669
29670
|
name: string2().optional(),
|
|
29670
29671
|
version: object({ number: number2().optional(), summary: string2().nullish() }).optional(),
|
|
29671
29672
|
diff: string2().optional()
|
|
29672
29673
|
});
|
|
29673
29674
|
var askEventPayloadFields = {
|
|
29675
|
+
id: string2().optional(),
|
|
29674
29676
|
opened_event_id: number2().int().positive(),
|
|
29675
29677
|
question: string2().optional(),
|
|
29676
29678
|
options: array(object({ label: string2().optional() })).nullish(),
|
|
@@ -29708,13 +29710,15 @@ var CommentEventPayloadSchema = object({
|
|
|
29708
29710
|
ask_question: string2().optional(),
|
|
29709
29711
|
anchor: object({ quote: string2().optional() }).nullish(),
|
|
29710
29712
|
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
29711
|
-
author: object({ kind: string2(), id: string2()
|
|
29713
|
+
author: object({ kind: string2(), id: string2() }).optional(),
|
|
29712
29714
|
created_at: string2().optional()
|
|
29713
29715
|
});
|
|
29714
29716
|
var MessageEventPayloadSchema = object({
|
|
29715
29717
|
id: string2().optional(),
|
|
29716
29718
|
body: string2().optional(),
|
|
29717
|
-
|
|
29719
|
+
reply_to: string2().nullish(),
|
|
29720
|
+
reply_body: string2().optional(),
|
|
29721
|
+
author: object({ kind: string2(), id: string2() }).optional()
|
|
29718
29722
|
});
|
|
29719
29723
|
var ChildStatusEventPayloadSchema = object({
|
|
29720
29724
|
child_key: string2().optional(),
|
|
@@ -29816,7 +29820,7 @@ var dispatchToolSpecs = [
|
|
|
29816
29820
|
},
|
|
29817
29821
|
{
|
|
29818
29822
|
name: "dispatch_ask",
|
|
29819
|
-
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " +
|
|
29823
|
+
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. Anchor a document question, 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". Question is at most 800 ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
29820
29824
|
arguments: (z) => ({
|
|
29821
29825
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29822
29826
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -29897,15 +29901,16 @@ var dispatchToolSpecs = [
|
|
|
29897
29901
|
},
|
|
29898
29902
|
{
|
|
29899
29903
|
name: "dispatch_message",
|
|
29900
|
-
description: "Post a
|
|
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}`,
|
|
29901
29905
|
arguments: (z) => ({
|
|
29902
29906
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29903
|
-
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()
|
|
29904
29909
|
})
|
|
29905
29910
|
},
|
|
29906
29911
|
{
|
|
29907
29912
|
name: "dispatch_doc_edit",
|
|
29908
|
-
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " +
|
|
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}`,
|
|
29909
29914
|
arguments: (z) => ({
|
|
29910
29915
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29911
29916
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -31026,6 +31031,12 @@ function dispatchAskQuestion(event) {
|
|
|
31026
31031
|
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31027
31032
|
return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
|
|
31028
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
|
+
}
|
|
31029
31040
|
function parseDispatchFrame(rawPayload) {
|
|
31030
31041
|
let value;
|
|
31031
31042
|
try {
|
|
@@ -31076,6 +31087,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31076
31087
|
let dispatchEvent;
|
|
31077
31088
|
let dispatchIssue;
|
|
31078
31089
|
let askQuestion;
|
|
31090
|
+
let messageReplyPreview;
|
|
31079
31091
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31080
31092
|
if (envelope.source === "dispatch") {
|
|
31081
31093
|
if (envelope.payload === undefined) {
|
|
@@ -31090,6 +31102,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31090
31102
|
return { skip: true, content: "", envelope };
|
|
31091
31103
|
}
|
|
31092
31104
|
askQuestion = dispatchAskQuestion(frame.event);
|
|
31105
|
+
messageReplyPreview = dispatchMessageReplyPreview(frame.event);
|
|
31093
31106
|
dispatchEvent = {
|
|
31094
31107
|
owner: dispatchOwner(frame.event, subject ?? envelope.topic),
|
|
31095
31108
|
...frame.event.issue_key === null ? {
|
|
@@ -31143,7 +31156,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31143
31156
|
...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
|
|
31144
31157
|
...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
|
|
31145
31158
|
...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
|
|
31146
|
-
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
|
|
31159
|
+
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? messageReplyPreview ?? envelope.in_reply_to },
|
|
31147
31160
|
...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
|
|
31148
31161
|
...reply === undefined ? {} : { reply_with: reply },
|
|
31149
31162
|
...role === undefined ? {} : {
|
|
@@ -31454,6 +31467,16 @@ class DispatchClient {
|
|
|
31454
31467
|
async message(issue, input) {
|
|
31455
31468
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
31456
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
|
+
}
|
|
31457
31480
|
async artifact(issue, input) {
|
|
31458
31481
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
31459
31482
|
if ("content" in input)
|
|
@@ -31718,10 +31741,10 @@ function parseDispatchRef(ref) {
|
|
|
31718
31741
|
id: targetID
|
|
31719
31742
|
};
|
|
31720
31743
|
}
|
|
31721
|
-
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\/([^/]+))?$/);
|
|
31722
31745
|
if (!issueReference)
|
|
31723
31746
|
return null;
|
|
31724
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
31747
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
31725
31748
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
31726
31749
|
return null;
|
|
31727
31750
|
const owner = { kind: "issue", issue };
|
|
@@ -31743,6 +31766,8 @@ function parseDispatchRef(ref) {
|
|
|
31743
31766
|
return { owner, kind: "ask", id: ask };
|
|
31744
31767
|
if (comment)
|
|
31745
31768
|
return { owner, kind: "comment", id: comment };
|
|
31769
|
+
if (message)
|
|
31770
|
+
return { owner, kind: "message", id: message };
|
|
31746
31771
|
return { owner, kind: "issue", id: issue };
|
|
31747
31772
|
}
|
|
31748
31773
|
function askId(args) {
|
|
@@ -31755,6 +31780,16 @@ function askId(args) {
|
|
|
31755
31780
|
}
|
|
31756
31781
|
return reference.id;
|
|
31757
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
|
+
}
|
|
31758
31793
|
function toolSchema(tool) {
|
|
31759
31794
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
31760
31795
|
if (!spec)
|
|
@@ -31766,7 +31801,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
31766
31801
|
return { args, ref: null, owner: null };
|
|
31767
31802
|
const refArgument = args.ref;
|
|
31768
31803
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
31769
|
-
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>");
|
|
31770
31805
|
})() : null;
|
|
31771
31806
|
const issueArgument = args.issue;
|
|
31772
31807
|
const projectArgument = args.project;
|
|
@@ -31846,8 +31881,12 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
31846
31881
|
} catch (error) {
|
|
31847
31882
|
if (!(error instanceof DispatchServiceError) || error.status !== 404)
|
|
31848
31883
|
throw error;
|
|
31849
|
-
const artifacts = await client.listProjectArtifacts(owner.project);
|
|
31850
|
-
const
|
|
31884
|
+
const artifacts = await client.listProjectArtifacts(owner.project, true);
|
|
31885
|
+
const matches = artifacts.filter((candidate) => candidate.name === artifactReference);
|
|
31886
|
+
if (matches.length > 1) {
|
|
31887
|
+
throw new Error(`artifact name ${artifactReference} is ambiguous in project ${owner.project}; ` + `${matches.length} documents share it \u2014 use its slug instead`);
|
|
31888
|
+
}
|
|
31889
|
+
const artifact = matches[0];
|
|
31851
31890
|
if (!artifact)
|
|
31852
31891
|
throw error;
|
|
31853
31892
|
return { owner, artifact };
|
|
@@ -31953,6 +31992,18 @@ function commentSummary({ comment, replies }) {
|
|
|
31953
31992
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
31954
31993
|
`);
|
|
31955
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
|
+
}
|
|
31956
32007
|
async function openArtifactMarks(client, resolved) {
|
|
31957
32008
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
31958
32009
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -32160,9 +32211,15 @@ async function executeDispatchTool(input) {
|
|
|
32160
32211
|
};
|
|
32161
32212
|
}
|
|
32162
32213
|
case "dispatch_message": {
|
|
32163
|
-
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}`;
|
|
32164
32221
|
return {
|
|
32165
|
-
text: `Posted message ${message.id}`,
|
|
32222
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
32166
32223
|
details: {
|
|
32167
32224
|
issue: message.issue_key,
|
|
32168
32225
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -32249,6 +32306,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
32249
32306
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
32250
32307
|
};
|
|
32251
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
|
+
}
|
|
32252
32319
|
if (documentOwner().kind === "project") {
|
|
32253
32320
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
32254
32321
|
return {
|
package/dist/legion.js
CHANGED
|
@@ -29662,14 +29662,16 @@ var IssueEventPayloadSchema = object({
|
|
|
29662
29662
|
route: string2().nullish()
|
|
29663
29663
|
});
|
|
29664
29664
|
var ArtifactCreatedEventPayloadSchema = object({
|
|
29665
|
-
artifact: object({ name: string2().optional() }).optional()
|
|
29665
|
+
artifact: object({ id: string2().optional(), slug: string2().optional(), name: string2().optional() }).optional()
|
|
29666
29666
|
});
|
|
29667
29667
|
var ArtifactVersionEventPayloadSchema = object({
|
|
29668
|
+
artifact_id: string2().optional(),
|
|
29668
29669
|
name: string2().optional(),
|
|
29669
29670
|
version: object({ number: number2().optional(), summary: string2().nullish() }).optional(),
|
|
29670
29671
|
diff: string2().optional()
|
|
29671
29672
|
});
|
|
29672
29673
|
var askEventPayloadFields = {
|
|
29674
|
+
id: string2().optional(),
|
|
29673
29675
|
opened_event_id: number2().int().positive(),
|
|
29674
29676
|
question: string2().optional(),
|
|
29675
29677
|
options: array(object({ label: string2().optional() })).nullish(),
|
|
@@ -29707,13 +29709,15 @@ var CommentEventPayloadSchema = object({
|
|
|
29707
29709
|
ask_question: string2().optional(),
|
|
29708
29710
|
anchor: object({ quote: string2().optional() }).nullish(),
|
|
29709
29711
|
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
29710
|
-
author: object({ kind: string2(), id: string2()
|
|
29712
|
+
author: object({ kind: string2(), id: string2() }).optional(),
|
|
29711
29713
|
created_at: string2().optional()
|
|
29712
29714
|
});
|
|
29713
29715
|
var MessageEventPayloadSchema = object({
|
|
29714
29716
|
id: string2().optional(),
|
|
29715
29717
|
body: string2().optional(),
|
|
29716
|
-
|
|
29718
|
+
reply_to: string2().nullish(),
|
|
29719
|
+
reply_body: string2().optional(),
|
|
29720
|
+
author: object({ kind: string2(), id: string2() }).optional()
|
|
29717
29721
|
});
|
|
29718
29722
|
var ChildStatusEventPayloadSchema = object({
|
|
29719
29723
|
child_key: string2().optional(),
|
|
@@ -29815,7 +29819,7 @@ var dispatchToolSpecs = [
|
|
|
29815
29819
|
},
|
|
29816
29820
|
{
|
|
29817
29821
|
name: "dispatch_ask",
|
|
29818
|
-
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " +
|
|
29822
|
+
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. Anchor a document question, 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". Question is at most 800 ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
29819
29823
|
arguments: (z) => ({
|
|
29820
29824
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29821
29825
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -29896,15 +29900,16 @@ var dispatchToolSpecs = [
|
|
|
29896
29900
|
},
|
|
29897
29901
|
{
|
|
29898
29902
|
name: "dispatch_message",
|
|
29899
|
-
description: "Post a
|
|
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}`,
|
|
29900
29904
|
arguments: (z) => ({
|
|
29901
29905
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29902
|
-
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()
|
|
29903
29908
|
})
|
|
29904
29909
|
},
|
|
29905
29910
|
{
|
|
29906
29911
|
name: "dispatch_doc_edit",
|
|
29907
|
-
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " +
|
|
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}`,
|
|
29908
29913
|
arguments: (z) => ({
|
|
29909
29914
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29910
29915
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -31473,6 +31478,12 @@ function dispatchAskQuestion(event) {
|
|
|
31473
31478
|
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31474
31479
|
return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
|
|
31475
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
|
+
}
|
|
31476
31487
|
function parseDispatchFrame(rawPayload) {
|
|
31477
31488
|
let value;
|
|
31478
31489
|
try {
|
|
@@ -31523,6 +31534,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31523
31534
|
let dispatchEvent;
|
|
31524
31535
|
let dispatchIssue;
|
|
31525
31536
|
let askQuestion;
|
|
31537
|
+
let messageReplyPreview;
|
|
31526
31538
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31527
31539
|
if (envelope.source === "dispatch") {
|
|
31528
31540
|
if (envelope.payload === undefined) {
|
|
@@ -31537,6 +31549,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31537
31549
|
return { skip: true, content: "", envelope };
|
|
31538
31550
|
}
|
|
31539
31551
|
askQuestion = dispatchAskQuestion(frame.event);
|
|
31552
|
+
messageReplyPreview = dispatchMessageReplyPreview(frame.event);
|
|
31540
31553
|
dispatchEvent = {
|
|
31541
31554
|
owner: dispatchOwner(frame.event, subject ?? envelope.topic),
|
|
31542
31555
|
...frame.event.issue_key === null ? {
|
|
@@ -31590,7 +31603,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31590
31603
|
...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
|
|
31591
31604
|
...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
|
|
31592
31605
|
...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
|
|
31593
|
-
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? envelope.in_reply_to },
|
|
31606
|
+
...envelope.in_reply_to === undefined ? {} : { re: askQuestion ?? messageReplyPreview ?? envelope.in_reply_to },
|
|
31594
31607
|
...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
|
|
31595
31608
|
...reply === undefined ? {} : { reply_with: reply },
|
|
31596
31609
|
...role === undefined ? {} : {
|
|
@@ -31894,6 +31907,16 @@ class DispatchClient {
|
|
|
31894
31907
|
async message(issue, input) {
|
|
31895
31908
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
31896
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
|
+
}
|
|
31897
31920
|
async artifact(issue, input) {
|
|
31898
31921
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
|
|
31899
31922
|
if ("content" in input)
|
|
@@ -32158,10 +32181,10 @@ function parseDispatchRef(ref) {
|
|
|
32158
32181
|
id: targetID
|
|
32159
32182
|
};
|
|
32160
32183
|
}
|
|
32161
|
-
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\/([^/]+))?$/);
|
|
32162
32185
|
if (!issueReference)
|
|
32163
32186
|
return null;
|
|
32164
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
32187
|
+
const [, issue, spec, log, children, artifact, version, ask, comment, message] = issueReference;
|
|
32165
32188
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
32166
32189
|
return null;
|
|
32167
32190
|
const owner = { kind: "issue", issue };
|
|
@@ -32183,6 +32206,8 @@ function parseDispatchRef(ref) {
|
|
|
32183
32206
|
return { owner, kind: "ask", id: ask };
|
|
32184
32207
|
if (comment)
|
|
32185
32208
|
return { owner, kind: "comment", id: comment };
|
|
32209
|
+
if (message)
|
|
32210
|
+
return { owner, kind: "message", id: message };
|
|
32186
32211
|
return { owner, kind: "issue", id: issue };
|
|
32187
32212
|
}
|
|
32188
32213
|
function askId(args) {
|
|
@@ -32195,6 +32220,16 @@ function askId(args) {
|
|
|
32195
32220
|
}
|
|
32196
32221
|
return reference.id;
|
|
32197
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
|
+
}
|
|
32198
32233
|
function toolSchema(tool) {
|
|
32199
32234
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
32200
32235
|
if (!spec)
|
|
@@ -32206,7 +32241,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
32206
32241
|
return { args, ref: null, owner: null };
|
|
32207
32242
|
const refArgument = args.ref;
|
|
32208
32243
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
32209
|
-
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>");
|
|
32210
32245
|
})() : null;
|
|
32211
32246
|
const issueArgument = args.issue;
|
|
32212
32247
|
const projectArgument = args.project;
|
|
@@ -32286,8 +32321,12 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
32286
32321
|
} catch (error) {
|
|
32287
32322
|
if (!(error instanceof DispatchServiceError) || error.status !== 404)
|
|
32288
32323
|
throw error;
|
|
32289
|
-
const artifacts = await client.listProjectArtifacts(owner.project);
|
|
32290
|
-
const
|
|
32324
|
+
const artifacts = await client.listProjectArtifacts(owner.project, true);
|
|
32325
|
+
const matches = artifacts.filter((candidate) => candidate.name === artifactReference);
|
|
32326
|
+
if (matches.length > 1) {
|
|
32327
|
+
throw new Error(`artifact name ${artifactReference} is ambiguous in project ${owner.project}; ` + `${matches.length} documents share it \u2014 use its slug instead`);
|
|
32328
|
+
}
|
|
32329
|
+
const artifact = matches[0];
|
|
32291
32330
|
if (!artifact)
|
|
32292
32331
|
throw error;
|
|
32293
32332
|
return { owner, artifact };
|
|
@@ -32393,6 +32432,18 @@ function commentSummary({ comment, replies }) {
|
|
|
32393
32432
|
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
32394
32433
|
`);
|
|
32395
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
|
+
}
|
|
32396
32447
|
async function openArtifactMarks(client, resolved) {
|
|
32397
32448
|
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
32398
32449
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
@@ -32600,9 +32651,15 @@ async function executeDispatchTool(input) {
|
|
|
32600
32651
|
};
|
|
32601
32652
|
}
|
|
32602
32653
|
case "dispatch_message": {
|
|
32603
|
-
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}`;
|
|
32604
32661
|
return {
|
|
32605
|
-
text: `Posted message ${message.id}`,
|
|
32662
|
+
text: `Posted message ${message.id} (${messageRef})`,
|
|
32606
32663
|
details: {
|
|
32607
32664
|
issue: message.issue_key,
|
|
32608
32665
|
topic: dispatchIssueSubject(message.issue_key, ">"),
|
|
@@ -32689,6 +32746,16 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
32689
32746
|
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
32690
32747
|
};
|
|
32691
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
|
+
}
|
|
32692
32759
|
if (documentOwner().kind === "project") {
|
|
32693
32760
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
32694
32761
|
return {
|
|
@@ -5,17 +5,18 @@ 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
|
-
transcript is your scratch pad. Anything meant for a human
|
|
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
|
-
The server enforces high signal: an ask question is at most 800 characters with at most eight
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
14
|
+
longer exist.
|
|
14
15
|
|
|
15
16
|
## Writing a spec
|
|
16
17
|
|
|
17
|
-
A spec is a decision record for the human who decides and the implementer who builds, not a
|
|
18
|
-
|
|
18
|
+
A spec is a decision record for the human who decides and the implementer who builds, not a transcript of your thinking. Use exactly
|
|
19
|
+
these document headings in this order.
|
|
19
20
|
|
|
20
21
|
| Section | Required content | Form |
|
|
21
22
|
| --- | --- | --- |
|
|
@@ -35,32 +36,23 @@ transcript of your thinking. Use exactly these document headings in this order.
|
|
|
35
36
|
- Do not hedge with “might” or “could consider.”
|
|
36
37
|
- Do not use TBD, TODO, or placeholders; an open item is a Decision needed.
|
|
37
38
|
- Keep each section to one screen; work that exceeds one screen per section is two specs.
|
|
38
|
-
- Update the spec in place as decisions land
|
|
39
|
-
|
|
40
|
-
### Self-review
|
|
41
|
-
|
|
42
|
-
- [ ] No placeholders remain.
|
|
43
|
-
- [ ] No sections conflict.
|
|
44
|
-
- [ ] The spec covers one implementation plan's worth of work.
|
|
45
|
-
- [ ] Every requirement has exactly one reading.
|
|
39
|
+
- Update the spec in place as decisions land: the spec is the record, comments are the discussion.
|
|
40
|
+
- Before sending it: no sections conflict, and every requirement has exactly one reading.
|
|
46
41
|
|
|
47
42
|
## Your owner
|
|
48
43
|
|
|
49
|
-
Every session works on an issue or project document. Legion pre-fills `issue` from
|
|
50
|
-
`
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`design-notes` identifies `dispatch://CORE/artifact/design-notes`. On first use, an external
|
|
55
|
-
issue reference creates its native issue in the project configured for that repository in
|
|
56
|
-
Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
|
|
44
|
+
Every session works on an issue or project document. Legion pre-fills `issue` from `LEGION_ISSUE`: use a native issue key such as
|
|
45
|
+
`LEGION-3`, an external `owner/repo#n` reference, or a bare positive number (resolved against the cwd repository). Otherwise pass
|
|
46
|
+
exactly one owner to every owner-scoped tool: `issue` for an issue, or `project` and `artifact` for an unlinked project document (see
|
|
47
|
+
[References](#references) for the resulting ref shape). On first use, an external issue reference creates its native issue in the
|
|
48
|
+
project configured for that repository in Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
|
|
57
49
|
|
|
58
50
|
Architects create newly tracked child work with:
|
|
59
51
|
```ts
|
|
60
52
|
dispatch_issue({ project, title, parent?, external?, spec?, force? })
|
|
61
53
|
```
|
|
62
|
-
It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use
|
|
63
|
-
|
|
54
|
+
It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use it to park a question. When `spec` is
|
|
55
|
+
supplied, follow [Writing a spec](#writing-a-spec).
|
|
64
56
|
|
|
65
57
|
## Search first
|
|
66
58
|
|
|
@@ -68,13 +60,12 @@ Before you create an issue or start a design document, search:
|
|
|
68
60
|
```ts
|
|
69
61
|
dispatch_search({ query, project?, limit? })
|
|
70
62
|
```
|
|
71
|
-
It returns every issue, document, comment, ask, and message that contains the words, with the
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
It returns every issue, document, comment, ask, and message that contains the words, with the issue key and a link. Cite the hit you
|
|
64
|
+
build on (`dispatch://KEY` or the document reference), or state "no prior issue" in the spec. Websearch syntax applies: `"merge queue"`,
|
|
65
|
+
`-daemon`, `OR`.
|
|
74
66
|
|
|
75
|
-
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns
|
|
76
|
-
|
|
77
|
-
call with `force: true` when it is genuinely new work.
|
|
67
|
+
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
|
|
68
|
+
Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
|
|
78
69
|
|
|
79
70
|
## Asking
|
|
80
71
|
|
|
@@ -91,14 +82,14 @@ dispatch_ask({
|
|
|
91
82
|
anchor?: { artifact, quote, occurrence? },
|
|
92
83
|
})
|
|
93
84
|
```
|
|
94
|
-
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document, topic, ask }` for a project document.
|
|
86
|
+
Options are buttons: never enumerate choices in prose. Put the recommendation in `question`, and put each selectable choice in
|
|
87
|
+
`options`. Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occurrence` is zero-based and selects a repeated
|
|
88
|
+
quote, and an anchor whose quote later disappears becomes orphaned but stays readable against its original document version.
|
|
89
|
+
|
|
90
|
+
An ask must be answerable from its own text and its anchor alone. Anchor a question about a document passage with `anchor`; thread one
|
|
91
|
+
about a comment with `reply_to`; thread a follow-up on your own ask with `reply_to_ask`; cite anything else with a `dispatch://`
|
|
92
|
+
reference (see [References](#references)). Never write "see above", "the message above", or "as attached".
|
|
102
93
|
|
|
103
94
|
Correct or refine an open ask in place instead of opening a second question:
|
|
104
95
|
```ts
|
|
@@ -110,12 +101,11 @@ dispatch_edit_ask({
|
|
|
110
101
|
urgency?,
|
|
111
102
|
})
|
|
112
103
|
```
|
|
113
|
-
At least one field besides `ask` is required. Use this only while the same decision remains
|
|
114
|
-
|
|
115
|
-
If the decision is moot or superseded, retract the old ask and open a new one.
|
|
104
|
+
At least one field besides `ask` is required. Use this only while the same decision remains open: it keeps the prior text in the event
|
|
105
|
+
log. An answered or resolved ask cannot be edited. If the decision is moot or superseded, retract the old ask and open a new one.
|
|
116
106
|
|
|
117
|
-
An ask stays open until a human answers, unless its question no longer needs that answer. Retract a
|
|
118
|
-
|
|
107
|
+
An ask stays open until a human answers, unless its question no longer needs that answer. Retract a moot or superseded question, or
|
|
108
|
+
self-resolve one after finding the answer:
|
|
119
109
|
```ts
|
|
120
110
|
dispatch_resolve_ask({
|
|
121
111
|
ask,
|
|
@@ -123,32 +113,31 @@ dispatch_resolve_ask({
|
|
|
123
113
|
reason: "A newer ask supersedes this question.",
|
|
124
114
|
})
|
|
125
115
|
```
|
|
126
|
-
Use `retracted` when the question is obsolete and `resolved` when you found the answer. Include the
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
Use `retracted` when the question is obsolete and `resolved` when you found the answer. Include the reason because the question remains
|
|
117
|
+
in its Conversation card and reply thread. Resolution is not an answer: it never records a human decision, and an answered ask cannot be
|
|
118
|
+
resolved. A human may reply to an open or answered ask; so may you, e.g. after finding the answer — use `reply_to_ask` on
|
|
119
|
+
`dispatch_comment` (mutually exclusive with `reply_to`).
|
|
129
120
|
|
|
130
|
-
|
|
131
|
-
(the asker) can reply too — e.g. acknowledging a clarifying question, or following up after the
|
|
132
|
-
answer. Use `reply_to_ask` on `dispatch_comment` to reply under your own ask; it is mutually
|
|
133
|
-
exclusive with `reply_to`.
|
|
121
|
+
## The Spec
|
|
134
122
|
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
The spec holds requirements, design, acceptance, decisions, and rejected alternatives, structured per [Writing a spec](#writing-a-spec).
|
|
124
|
+
It changes only when a decision or requirement changes, and every version that records one is named with `summary`. Never write
|
|
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)).
|
|
137
127
|
|
|
138
128
|
Read the current document before changing it:
|
|
139
129
|
|
|
140
130
|
```ts
|
|
141
131
|
dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
142
132
|
```
|
|
143
|
-
It returns live or versioned markdown with open marks. `issue` with an omitted `artifact`
|
|
144
|
-
|
|
145
|
-
`dispatch://PROJECT/artifact/<slug>` ref supplies both. Then write narrative with:
|
|
133
|
+
It returns live or versioned markdown with open marks. `issue` with an omitted `artifact` reads the issue specification; a project needs
|
|
134
|
+
`artifact`; and a `dispatch://PROJECT/artifact/<slug>` ref supplies both. Then write with:
|
|
146
135
|
|
|
147
136
|
```ts
|
|
148
137
|
dispatch_doc_edit({ issue?, project?, artifact, ops, summary? })
|
|
149
138
|
```
|
|
150
|
-
It returns issue or project-document owner details plus `applied`, optional `version`, and its
|
|
151
|
-
|
|
139
|
+
It returns issue or project-document owner details plus `applied`, optional `version`, and its write `topic`. `ops` is an array of this
|
|
140
|
+
exact `EditOp` shape:
|
|
152
141
|
|
|
153
142
|
```ts
|
|
154
143
|
type EditOp = {
|
|
@@ -162,23 +151,18 @@ type EditOp = {
|
|
|
162
151
|
};
|
|
163
152
|
```
|
|
164
153
|
|
|
165
|
-
Target `replace` and `delete` by the document's plain text: inline-code and link text match
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
span separate blocks into separate operations.
|
|
154
|
+
Target `replace` and `delete` by the document's plain text: inline-code and link text match without Markdown syntax, and a table-cell
|
|
155
|
+
anchor is its cell text. Quote code-block contents without their Markdown fences. A quote must stay within one textblock; split changes
|
|
156
|
+
that span separate blocks into separate operations.
|
|
169
157
|
|
|
170
|
-
`replace` requires `find` and `with`; `delete` requires `find`; `insert` requires `markdown`
|
|
171
|
-
|
|
172
|
-
`"
|
|
173
|
-
|
|
158
|
+
`replace` requires `find` and `with`; `delete` requires `find`; `insert` requires `markdown` and exactly one of `after` or `before`. An
|
|
159
|
+
insert anchor is a quote, `"start"`, `"end"`, or `"heading:Title"`. Ordinary inserts create a sibling block before or after the quote or
|
|
160
|
+
heading's enclosing document block; `"start"` and `"end"` select the document edges. At a table-cell quote, a body-row fragment (no
|
|
161
|
+
header or delimiter rows) extends that table before or after the matched row instead; short rows are padded, wider rows are rejected,
|
|
162
|
+
and deleting a cell's quoted text removes only that text.
|
|
174
163
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
rows wider than the table are rejected.
|
|
178
|
-
Deleting a cell's quoted text removes that text, not the surrounding row or table.
|
|
179
|
-
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target;
|
|
180
|
-
re-read a missing or ambiguous target before retrying. Pass `summary` to name the version
|
|
181
|
-
when recording a decision. Never paste progress into a message.
|
|
164
|
+
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target; re-read a missing or ambiguous target before
|
|
165
|
+
retrying. Pass `summary` to name the version when recording a decision.
|
|
182
166
|
|
|
183
167
|
## Comments and suggestions
|
|
184
168
|
|
|
@@ -188,14 +172,11 @@ Add feedback with:
|
|
|
188
172
|
dispatch_comment({ issue?, project?, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
189
173
|
```
|
|
190
174
|
|
|
191
|
-
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`
|
|
196
|
-
mutually exclusive. Comments are edited only by their author from the dashboard. A delivered
|
|
197
|
-
`comment.created` event carries the comment `id`; reply to it with
|
|
198
|
-
`dispatch_comment({ reply_to: <id> })`.
|
|
175
|
+
It returns issue or project-document owner details plus `comment` and, for writes, `topic`. `quote` requires `artifact`; omit both for a
|
|
176
|
+
floating issue comment. A reply (`reply_to`/`reply_to_ask`) takes no `quote`; it belongs to its parent's anchor. Use `reply_to` to
|
|
177
|
+
continue a comment thread at its root; a reply to a resolved thread reopens it. Use `reply_to_ask` to reply directly under a question
|
|
178
|
+
asked with `dispatch_ask`. Comments are edited only by their author from the dashboard. A delivered `comment.created` event carries the
|
|
179
|
+
comment `id`; reply to it with `dispatch_comment({ reply_to: <id> })`.
|
|
199
180
|
|
|
200
181
|
Propose an exact replacement instead of describing it:
|
|
201
182
|
|
|
@@ -203,15 +184,9 @@ Propose an exact replacement instead of describing it:
|
|
|
203
184
|
dispatch_suggest({ issue?, project?, artifact, quote, replace_with, body?, occurrence? })
|
|
204
185
|
```
|
|
205
186
|
|
|
206
|
-
It returns issue or project-document owner details plus `comment` and its write `topic`. A
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
before retrying. `INVALID_ANCHOR` requires exactly one nonempty anchor `quote` or `mark_id`;
|
|
210
|
-
`ANCHOR_MISSING` means a browser mark was not observed in the live tree, and
|
|
211
|
-
`ANCHOR_ORPHANED` means its marked text no longer exists. `INVALID_MARKDOWN` and `DOC_SCHEMA`
|
|
212
|
-
reject Markdown or a live tree outside the Proof schema. `INVALID_OP` names a malformed edit;
|
|
213
|
-
`CAP_EXCEEDED` never truncates; `ISSUE_CLOSED` rejects a write. `ACTOR_KIND` and `ROUTE_INVALID`
|
|
214
|
-
reject an invalid actor or route.
|
|
187
|
+
It returns issue or project-document owner details plus `comment` and its write `topic`. A human accepts or rejects a suggestion.
|
|
188
|
+
Errors: `TARGET_AMBIGUOUS` (add `occurrence`), `TARGET_NOT_FOUND` (re-read first), `INVALID_ANCHOR`/`ANCHOR_MISSING`/`ANCHOR_ORPHANED`
|
|
189
|
+
(bad, unwritten, or stale quote), `INVALID_MARKDOWN`/`DOC_SCHEMA` (malformed content), `CAP_EXCEEDED`, `ISSUE_CLOSED`.
|
|
215
190
|
|
|
216
191
|
## Artifacts
|
|
217
192
|
|
|
@@ -227,40 +202,39 @@ Or, when the text is already in the call, post a Markdown document directly:
|
|
|
227
202
|
dispatch_artifact({ issue?, project?, name: "spec.md", content: "# Design\n..." })
|
|
228
203
|
```
|
|
229
204
|
|
|
230
|
-
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
same `name` creates its next version. Use `content` when the text is already in the call.
|
|
235
|
-
Address an existing artifact by the slug shown in the upload result or by its filename.
|
|
205
|
+
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project document; it must not include `artifact`.
|
|
206
|
+
Exactly one of `path` and `content` is required. It returns issue or project-document owner details plus `artifact`, `version`, and its
|
|
207
|
+
write `topic`. Uploading the same `name` creates its next version. Address an existing artifact by the slug shown in the upload result
|
|
208
|
+
or by its filename; the slug also arrives on `artifact.created` events.
|
|
236
209
|
|
|
237
210
|
## Messages
|
|
238
211
|
|
|
239
|
-
|
|
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.
|
|
240
216
|
|
|
241
217
|
```ts
|
|
242
218
|
dispatch_message({ issue, body })
|
|
243
219
|
```
|
|
244
220
|
|
|
245
|
-
It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters.
|
|
246
|
-
|
|
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.
|
|
247
223
|
|
|
248
224
|
## What comes back
|
|
249
225
|
|
|
250
|
-
A write result's `details.topic` subscribes the host to its owner. Issue writes use
|
|
251
|
-
`notifications.dispatch.
|
|
252
|
-
|
|
253
|
-
event; `notify` only controls agent wake and routed delivery. After a restart, catch up with:
|
|
226
|
+
A write result's `details.topic` subscribes the host to its owner. Issue writes use `notifications.dispatch.issue.<KEY>.>`;
|
|
227
|
+
project-document writes use `notifications.dispatch.document.<PROJECT>.<SLUG>.>`. The owner topic carries every Dispatch event; `notify`
|
|
228
|
+
only controls agent wake and routed delivery. After a restart, catch up with:
|
|
254
229
|
|
|
255
230
|
```ts
|
|
256
231
|
dispatch_read({ issue?, project?, artifact?, ref? })
|
|
257
232
|
```
|
|
258
233
|
|
|
259
|
-
With an issue ref, it returns the issue summary, open asks, references, and recent events with
|
|
260
|
-
`details` `{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
234
|
+
With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
|
|
235
|
+
document owner or ref, it returns a document summary with `details` `{ project, document }`. With an ask ref, it returns that ask's
|
|
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.
|
|
264
238
|
|
|
265
239
|
## References
|
|
266
240
|
|
|
@@ -272,11 +246,14 @@ dispatch://KEY/spec
|
|
|
272
246
|
dispatch://KEY/artifact/<slug>[@vN]
|
|
273
247
|
dispatch://KEY/ask/<id>
|
|
274
248
|
dispatch://KEY/comment/<id>
|
|
249
|
+
dispatch://KEY/message/<id>
|
|
275
250
|
dispatch://PROJECT/artifact/<slug>[@vN]
|
|
276
251
|
dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
277
252
|
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
278
253
|
```
|
|
279
254
|
|
|
255
|
+
A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records.
|
|
256
|
+
|
|
280
257
|
## Before / after
|
|
281
258
|
|
|
282
259
|
Before — a wall of text hides the decision and makes the choices unclickable:
|
|
@@ -294,48 +271,28 @@ After — anchor the decision and make each option a button:
|
|
|
294
271
|
dispatch_ask({
|
|
295
272
|
issue: "LEGION-815",
|
|
296
273
|
question:
|
|
297
|
-
"Choose the release gate. Recommendation: ship after release-note review
|
|
274
|
+
"Choose the release gate. Recommendation: ship after release-note review, since the tested deployment is otherwise ready.",
|
|
298
275
|
options: [
|
|
299
|
-
{
|
|
300
|
-
|
|
301
|
-
description: "Keeps the tested release intact and publishes reviewed instructions.",
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
label: "Ship now",
|
|
305
|
-
description: "Meets the demo deadline; release notes follow separately.",
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
label: "Remove dashboard changes",
|
|
309
|
-
description: "Narrows the release but requires another deployment test.",
|
|
310
|
-
},
|
|
276
|
+
{ label: "Review notes, then ship", description: "Keeps the release intact and reviewed." },
|
|
277
|
+
{ label: "Ship now", description: "Meets the demo deadline; release notes follow later." },
|
|
311
278
|
],
|
|
312
279
|
urgency: "high",
|
|
313
|
-
anchor: {
|
|
314
|
-
artifact: "spec",
|
|
315
|
-
quote: "Release requires reviewed operator instructions before deployment.",
|
|
316
|
-
},
|
|
280
|
+
anchor: { artifact: "spec", quote: "Release requires reviewed operator instructions before deployment." },
|
|
317
281
|
})
|
|
318
282
|
```
|
|
319
283
|
|
|
320
|
-
Before — a
|
|
284
|
+
Before — a progress note that nobody needs, posted where humans look for decisions:
|
|
321
285
|
|
|
322
|
-
```
|
|
323
|
-
|
|
286
|
+
```ts
|
|
287
|
+
dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
|
|
324
288
|
```
|
|
325
289
|
|
|
326
|
-
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:
|
|
327
292
|
|
|
328
293
|
```ts
|
|
329
|
-
|
|
294
|
+
dispatch_message({
|
|
330
295
|
issue: "LEGION-815",
|
|
331
|
-
|
|
332
|
-
ops: [
|
|
333
|
-
{
|
|
334
|
-
op: "replace",
|
|
335
|
-
find: "Release pending.",
|
|
336
|
-
with: "Release merged and ready for deployment.",
|
|
337
|
-
},
|
|
338
|
-
],
|
|
339
|
-
summary: "Recorded merged release",
|
|
296
|
+
body: "Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.",
|
|
340
297
|
})
|
|
341
298
|
```
|
|
@@ -71,6 +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
|
+
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`.
|
|
74
77
|
|
|
75
78
|
Write one root specification containing the accepted scope, adoption/decomposition,
|
|
76
79
|
waves, acceptance criteria, and integration test. When the config-armed root design gate
|