@sjawhar/pi-legion-envoy 1.35.1 → 1.36.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 +464 -110
- package/dist/legion.js +15 -8
- package/dist/skills/dispatch/SKILL.md +11 -4
- package/dist/skills/envoy/SKILL.md +15 -3
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29903,7 +29903,7 @@ var dispatchToolSpecs = [
|
|
|
29903
29903
|
options: z.array(z.object({
|
|
29904
29904
|
label: z.string().describe("Selectable option label."),
|
|
29905
29905
|
description: z.string().describe("Optional option context.").optional()
|
|
29906
|
-
}), { max: 8 }).describe("
|
|
29906
|
+
}), { max: 8 }).describe("Up to 8 choices, each an object { label, description? } (never a bare string).").optional(),
|
|
29907
29907
|
multiple: z.boolean().describe("Whether multiple choices may be selected.").optional(),
|
|
29908
29908
|
urgency: z.enum(ASK_URGENCIES).describe("Optional decision urgency.").optional(),
|
|
29909
29909
|
anchor: z.object({
|
|
@@ -29955,7 +29955,7 @@ var dispatchToolSpecs = [
|
|
|
29955
29955
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
29956
29956
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
29957
29957
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
29958
|
-
reply_to: z.string().describe("
|
|
29958
|
+
reply_to: z.string().describe("A comment id (uuid); replying to any comment in a thread continues that thread (an " + "ask's clarification thread included). To reply to an ask, use reply_to_ask with the " + "ask id instead.").optional(),
|
|
29959
29959
|
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional(),
|
|
29960
29960
|
turn: z.enum(["agent", "human"]).describe("Only with reply_to_ask: who holds the turn after this reply. agent: a progress note - " + "you keep the turn and the ask stays 'Waiting on agents' for the human; human (default): " + "you need the human to act - the ask returns to 'Waiting on you'.").optional()
|
|
29961
29961
|
}),
|
|
@@ -29987,7 +29987,7 @@ var dispatchToolSpecs = [
|
|
|
29987
29987
|
},
|
|
29988
29988
|
{
|
|
29989
29989
|
name: "dispatch_doc_edit",
|
|
29990
|
-
description: "Apply deterministic document edits, including retyping an identified paragraph into a schema-declared typed block. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " +
|
|
29990
|
+
description: "Apply deterministic document edits, including retyping an identified paragraph into a schema-declared typed block. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + 'For replace, delete, and quote insert anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading \'# \' matches a heading. Insert anchors also accept "start", "end", and "heading:<exact heading text>". ' + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
29991
29991
|
arguments: (z) => ({
|
|
29992
29992
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29993
29993
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -29995,12 +29995,12 @@ var dispatchToolSpecs = [
|
|
|
29995
29995
|
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29996
29996
|
ops: z.array(z.object({
|
|
29997
29997
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
29998
|
-
find: z.string().describe("Text
|
|
29998
|
+
find: z.string().describe("Text of the target block as rendered, for replace or delete; inline markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading.").optional(),
|
|
29999
29999
|
with: z.string().describe("Replacement text for replace.").optional(),
|
|
30000
30000
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
30001
30001
|
markdown: z.string().describe("Markdown to insert.").optional(),
|
|
30002
|
-
after: z.string().describe(
|
|
30003
|
-
before: z.string().describe(
|
|
30002
|
+
after: z.string().describe(`Insert after this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>".`).optional(),
|
|
30003
|
+
before: z.string().describe(`Insert before this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>".`).optional(),
|
|
30004
30004
|
block: z.string().describe("Block id to retype.").optional(),
|
|
30005
30005
|
type: z.string().describe("Typed block name for retype.").optional(),
|
|
30006
30006
|
attributes: z.unknown().describe("Typed block attributes for retype.").optional()
|
|
@@ -30504,6 +30504,9 @@ function canonicalRepo(owner, repo) {
|
|
|
30504
30504
|
return `${owner.trim().toLowerCase()}/${repo.trim().toLowerCase().replace(/\.git$/, "")}`;
|
|
30505
30505
|
}
|
|
30506
30506
|
// ../contracts/src/tool-schema.ts
|
|
30507
|
+
function overCapMessage(length, max) {
|
|
30508
|
+
return `is ${length - max} characters over the ${max}-character limit (${length}/${max})`;
|
|
30509
|
+
}
|
|
30507
30510
|
function zodSchemaApi(zod) {
|
|
30508
30511
|
const api = zod;
|
|
30509
30512
|
return {
|
|
@@ -30511,8 +30514,12 @@ function zodSchemaApi(zod) {
|
|
|
30511
30514
|
let schema = api.string();
|
|
30512
30515
|
if (opts.min !== undefined)
|
|
30513
30516
|
schema = schema.min(opts.min);
|
|
30514
|
-
if (opts.max !== undefined)
|
|
30515
|
-
|
|
30517
|
+
if (opts.max !== undefined) {
|
|
30518
|
+
const max = opts.max;
|
|
30519
|
+
schema = schema.max(max, {
|
|
30520
|
+
error: (issue) => overCapMessage(typeof issue.input === "string" ? issue.input.length : max + 1, max)
|
|
30521
|
+
});
|
|
30522
|
+
}
|
|
30516
30523
|
return schema;
|
|
30517
30524
|
},
|
|
30518
30525
|
number: (opts = {}) => {
|
|
@@ -31091,6 +31098,24 @@ function resolveOptions(options) {
|
|
|
31091
31098
|
};
|
|
31092
31099
|
}
|
|
31093
31100
|
|
|
31101
|
+
// ../envoy-client/src/ask-answer.ts
|
|
31102
|
+
var HEAD_LENGTH = 120;
|
|
31103
|
+
function textHead(text, limit = HEAD_LENGTH) {
|
|
31104
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
31105
|
+
return flat.length > limit ? `${flat.slice(0, limit)}\u2026` : flat;
|
|
31106
|
+
}
|
|
31107
|
+
function askAnswerText(answer) {
|
|
31108
|
+
if (answer === null || answer === undefined)
|
|
31109
|
+
return "";
|
|
31110
|
+
const selected = (answer.selected ?? []).join(", ");
|
|
31111
|
+
const text = answer.text ?? "";
|
|
31112
|
+
if (text === "")
|
|
31113
|
+
return selected;
|
|
31114
|
+
if (selected === "")
|
|
31115
|
+
return text;
|
|
31116
|
+
return `${selected} - ${text}`;
|
|
31117
|
+
}
|
|
31118
|
+
|
|
31094
31119
|
// ../envoy-client/src/delivery.ts
|
|
31095
31120
|
var KNOWN_SOURCES = EnvelopeSchema.shape.source.enum;
|
|
31096
31121
|
var FOREIGN_SESSION_ID = /\b01a0[0-9a-f]{4}-[0-9a-f]{4}-7[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
|
|
@@ -31147,7 +31172,14 @@ function senderLabel(envelope) {
|
|
|
31147
31172
|
function replyWith(envelope) {
|
|
31148
31173
|
if (envelope.source !== "agent" || envelope.source_session === undefined)
|
|
31149
31174
|
return;
|
|
31150
|
-
return
|
|
31175
|
+
return {
|
|
31176
|
+
tool: "envoy_send",
|
|
31177
|
+
args: {
|
|
31178
|
+
session_id: envelope.source_session,
|
|
31179
|
+
...envelope.event_id === undefined ? {} : { in_reply_to: envelope.event_id },
|
|
31180
|
+
message: "..."
|
|
31181
|
+
}
|
|
31182
|
+
};
|
|
31151
31183
|
}
|
|
31152
31184
|
var DISPATCH_PAYLOAD_SCHEMAS = {
|
|
31153
31185
|
"issue.created": IssueEventPayloadSchema,
|
|
@@ -31170,15 +31202,18 @@ var DISPATCH_PAYLOAD_SCHEMAS = {
|
|
|
31170
31202
|
"message.answered": MessageEventPayloadSchema,
|
|
31171
31203
|
"child.status": ChildStatusEventPayloadSchema
|
|
31172
31204
|
};
|
|
31205
|
+
function topicDocument(topic) {
|
|
31206
|
+
if (topic?.startsWith(DISPATCH_DOCUMENT_TOPIC_PREFIX) !== true)
|
|
31207
|
+
return;
|
|
31208
|
+
const [project, slug] = topic.slice(DISPATCH_DOCUMENT_TOPIC_PREFIX.length).split(".", 3);
|
|
31209
|
+
return project !== undefined && project !== "" && slug !== undefined && slug !== "" ? { project, slug } : undefined;
|
|
31210
|
+
}
|
|
31173
31211
|
function dispatchOwner(event, topic) {
|
|
31174
31212
|
if (event.issue_key !== null)
|
|
31175
31213
|
return event.issue_key;
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
return `${project} / ${slug}`;
|
|
31180
|
-
}
|
|
31181
|
-
}
|
|
31214
|
+
const document = topicDocument(topic);
|
|
31215
|
+
if (document !== undefined)
|
|
31216
|
+
return `${document.project} / ${document.slug}`;
|
|
31182
31217
|
return event.artifact_id ?? "unknown";
|
|
31183
31218
|
}
|
|
31184
31219
|
function dispatchCommentReplyWith(event, topic) {
|
|
@@ -31189,9 +31224,12 @@ function dispatchCommentReplyWith(event, topic) {
|
|
|
31189
31224
|
return;
|
|
31190
31225
|
const reply = comment.data.ask_id;
|
|
31191
31226
|
const thread = reply === undefined || reply === null || reply === "" ? "reply_to" : "reply_to_ask";
|
|
31192
|
-
const threadID = thread === "reply_to" ? comment.data.id : reply;
|
|
31227
|
+
const threadID = thread === "reply_to" ? comment.data.id : String(reply);
|
|
31193
31228
|
if (event.issue_key !== null) {
|
|
31194
|
-
return
|
|
31229
|
+
return {
|
|
31230
|
+
tool: "dispatch_comment",
|
|
31231
|
+
args: { issue: event.issue_key, [thread]: threadID, body: "..." }
|
|
31232
|
+
};
|
|
31195
31233
|
}
|
|
31196
31234
|
let project = comment.data.project_key;
|
|
31197
31235
|
let artifact = comment.data.artifact_slug;
|
|
@@ -31200,7 +31238,21 @@ function dispatchCommentReplyWith(event, topic) {
|
|
|
31200
31238
|
}
|
|
31201
31239
|
if (project === undefined || project === "" || artifact === undefined || artifact === "")
|
|
31202
31240
|
return;
|
|
31203
|
-
return
|
|
31241
|
+
return {
|
|
31242
|
+
tool: "dispatch_comment",
|
|
31243
|
+
args: { project, artifact, [thread]: threadID, body: "..." }
|
|
31244
|
+
};
|
|
31245
|
+
}
|
|
31246
|
+
function dispatchAskAnswer(event) {
|
|
31247
|
+
if (event.type !== "ask.answered")
|
|
31248
|
+
return;
|
|
31249
|
+
const ask = AskEventPayloadSchema.safeParse(event.payload);
|
|
31250
|
+
if (!ask.success)
|
|
31251
|
+
return;
|
|
31252
|
+
return {
|
|
31253
|
+
question: textHead(ask.data.question ?? ""),
|
|
31254
|
+
answer: askAnswerText(ask.data.answer)
|
|
31255
|
+
};
|
|
31204
31256
|
}
|
|
31205
31257
|
function dispatchPayload(event) {
|
|
31206
31258
|
const schema = DISPATCH_PAYLOAD_SCHEMAS[event.type];
|
|
@@ -31213,13 +31265,33 @@ function dispatchAskQuestion(event) {
|
|
|
31213
31265
|
if (event.type !== "comment.created")
|
|
31214
31266
|
return;
|
|
31215
31267
|
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31216
|
-
return parsed.success && parsed.data.ask_question !== "" ? parsed.data.ask_question : undefined;
|
|
31217
|
-
}
|
|
31218
|
-
function
|
|
31219
|
-
|
|
31220
|
-
|
|
31221
|
-
|
|
31222
|
-
|
|
31268
|
+
return parsed.success && parsed.data.ask_question !== undefined && parsed.data.ask_question !== "" ? textHead(parsed.data.ask_question) : undefined;
|
|
31269
|
+
}
|
|
31270
|
+
function dispatchReplyRef(event, topic, inReplyTo) {
|
|
31271
|
+
let kind;
|
|
31272
|
+
let document = topicDocument(topic);
|
|
31273
|
+
if (event.type.startsWith("ask.")) {
|
|
31274
|
+
kind = "ask";
|
|
31275
|
+
} else if (event.type.startsWith("message.")) {
|
|
31276
|
+
kind = "message";
|
|
31277
|
+
} else if (event.type === "comment.created") {
|
|
31278
|
+
const comment = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31279
|
+
if (!comment.success || comment.data.ask_id === undefined || comment.data.ask_id === null) {
|
|
31280
|
+
return inReplyTo;
|
|
31281
|
+
}
|
|
31282
|
+
kind = "ask";
|
|
31283
|
+
const { project_key: project, artifact_slug: slug } = comment.data;
|
|
31284
|
+
if (project !== undefined && project !== "" && slug !== undefined && slug !== "") {
|
|
31285
|
+
document = { project, slug };
|
|
31286
|
+
}
|
|
31287
|
+
} else {
|
|
31288
|
+
return inReplyTo;
|
|
31289
|
+
}
|
|
31290
|
+
if (event.issue_key !== null)
|
|
31291
|
+
return `dispatch://${event.issue_key}/${kind}/${inReplyTo}`;
|
|
31292
|
+
if (document === undefined)
|
|
31293
|
+
return inReplyTo;
|
|
31294
|
+
return `dispatch://${document.project}/artifact/${document.slug}/${kind}/${inReplyTo}`;
|
|
31223
31295
|
}
|
|
31224
31296
|
function isObject2(value) {
|
|
31225
31297
|
return typeof value === "object" && value !== null;
|
|
@@ -31308,8 +31380,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31308
31380
|
let delivery;
|
|
31309
31381
|
let rejectedDelivery;
|
|
31310
31382
|
let malformedDelivery = false;
|
|
31311
|
-
let
|
|
31312
|
-
let messageReplyPreview;
|
|
31383
|
+
let inReplyTo = envelope.in_reply_to;
|
|
31313
31384
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31314
31385
|
if (envelope.source === "dispatch") {
|
|
31315
31386
|
if (envelope.payload === undefined) {
|
|
@@ -31335,8 +31406,11 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31335
31406
|
envelope
|
|
31336
31407
|
};
|
|
31337
31408
|
}
|
|
31338
|
-
|
|
31339
|
-
|
|
31409
|
+
const answered = dispatchAskAnswer(frame.event);
|
|
31410
|
+
const question = answered?.question ?? dispatchAskQuestion(frame.event);
|
|
31411
|
+
if (inReplyTo !== undefined) {
|
|
31412
|
+
inReplyTo = dispatchReplyRef(frame.event, subject ?? envelope.topic, inReplyTo);
|
|
31413
|
+
}
|
|
31340
31414
|
dispatchReply = dispatchCommentReplyWith(frame.event, subject ?? envelope.topic);
|
|
31341
31415
|
if (frame.event.type === "message.created") {
|
|
31342
31416
|
const message = MessageEventPayloadSchema.safeParse(frame.event.payload);
|
|
@@ -31350,7 +31424,10 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31350
31424
|
body: message.data.body ?? envelope.payload_summary ?? ""
|
|
31351
31425
|
};
|
|
31352
31426
|
if (frame.event.issue_key !== null) {
|
|
31353
|
-
dispatchReply =
|
|
31427
|
+
dispatchReply = {
|
|
31428
|
+
tool: "dispatch_message",
|
|
31429
|
+
args: { issue: frame.event.issue_key, in_reply_to: message.data.id, body: "..." }
|
|
31430
|
+
};
|
|
31354
31431
|
}
|
|
31355
31432
|
}
|
|
31356
31433
|
}
|
|
@@ -31364,6 +31441,8 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31364
31441
|
} : { issue_key: frame.event.issue_key },
|
|
31365
31442
|
type: frame.event.type,
|
|
31366
31443
|
actor: frame.event.actor,
|
|
31444
|
+
...question === undefined ? {} : { question },
|
|
31445
|
+
...answered === undefined ? {} : { answer: answered.answer },
|
|
31367
31446
|
payload: dispatchPayload(frame.event)
|
|
31368
31447
|
};
|
|
31369
31448
|
dispatchActor = frame.event.actor;
|
|
@@ -31410,11 +31489,14 @@ ${envelope.payload ?? ""}`;
|
|
|
31410
31489
|
...envelope.expires_at === undefined ? {} : { by: inboundTimestamp(envelope.expires_at) },
|
|
31411
31490
|
...envelope.urgency === undefined ? {} : { urgency: envelope.urgency },
|
|
31412
31491
|
...envelope.expects_reply === undefined ? {} : { expects_reply: envelope.expects_reply },
|
|
31413
|
-
...
|
|
31492
|
+
...inReplyTo === undefined ? {} : { re: inReplyTo },
|
|
31414
31493
|
...envelope.supersedes === undefined ? {} : { supersedes: envelope.supersedes },
|
|
31415
31494
|
...reply === undefined ? {} : { reply_with: reply },
|
|
31416
31495
|
...role === undefined ? {} : {
|
|
31417
|
-
reply_role:
|
|
31496
|
+
reply_role: {
|
|
31497
|
+
tool: "envoy_publish",
|
|
31498
|
+
args: { topic: `notifications.role.${role}`, message: "..." }
|
|
31499
|
+
}
|
|
31418
31500
|
},
|
|
31419
31501
|
...dispatchRendered ? { dispatch: dispatchEvent } : {
|
|
31420
31502
|
...summaryIsHead ? {} : { summary: payloadSummary },
|
|
@@ -31760,6 +31842,9 @@ class DispatchClient {
|
|
|
31760
31842
|
async comment(issue, input) {
|
|
31761
31843
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], input);
|
|
31762
31844
|
}
|
|
31845
|
+
async listIssueAsks(issue, state) {
|
|
31846
|
+
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "asks"], undefined, state === undefined ? undefined : { state });
|
|
31847
|
+
}
|
|
31763
31848
|
async getArtifactAsks(id, state) {
|
|
31764
31849
|
return this.#json("GET", ["api", "v1", "artifacts", id, "asks"], undefined, state === undefined ? undefined : { state });
|
|
31765
31850
|
}
|
|
@@ -31958,6 +32043,116 @@ class DispatchClient {
|
|
|
31958
32043
|
}
|
|
31959
32044
|
}
|
|
31960
32045
|
|
|
32046
|
+
// ../envoy-client/src/tool-input-errors.ts
|
|
32047
|
+
class ToolInputError extends Error {
|
|
32048
|
+
tool;
|
|
32049
|
+
problems;
|
|
32050
|
+
constructor(tool, problems) {
|
|
32051
|
+
const count = problems.length;
|
|
32052
|
+
super([
|
|
32053
|
+
`${tool} was not called: ${count} problem${count === 1 ? "" : "s"}`,
|
|
32054
|
+
...problems.map((problem) => `- ${problem}`)
|
|
32055
|
+
].join(`
|
|
32056
|
+
`));
|
|
32057
|
+
this.name = "ToolInputError";
|
|
32058
|
+
this.tool = tool;
|
|
32059
|
+
this.problems = problems;
|
|
32060
|
+
}
|
|
32061
|
+
}
|
|
32062
|
+
function unwrap(schema) {
|
|
32063
|
+
let current = schema;
|
|
32064
|
+
while (current !== undefined) {
|
|
32065
|
+
const def = current.def;
|
|
32066
|
+
if (!("innerType" in def) || !(def.type === "optional" || def.type === "nullable" || def.type === "default"))
|
|
32067
|
+
break;
|
|
32068
|
+
current = def.innerType;
|
|
32069
|
+
}
|
|
32070
|
+
return current;
|
|
32071
|
+
}
|
|
32072
|
+
function shapeOf(schema) {
|
|
32073
|
+
const unwrapped = unwrap(schema);
|
|
32074
|
+
if (unwrapped === undefined)
|
|
32075
|
+
return;
|
|
32076
|
+
const def = unwrapped.def;
|
|
32077
|
+
return def.type === "object" && "shape" in def ? def.shape : undefined;
|
|
32078
|
+
}
|
|
32079
|
+
function schemaAt(schema, path) {
|
|
32080
|
+
let current = schema;
|
|
32081
|
+
for (const key of path) {
|
|
32082
|
+
const unwrapped = unwrap(current);
|
|
32083
|
+
if (unwrapped === undefined)
|
|
32084
|
+
return;
|
|
32085
|
+
const def = unwrapped.def;
|
|
32086
|
+
if (def.type === "object" && "shape" in def) {
|
|
32087
|
+
current = shapeOf(unwrapped)?.[String(key)];
|
|
32088
|
+
} else if (def.type === "array" && "element" in def) {
|
|
32089
|
+
current = def.element;
|
|
32090
|
+
} else {
|
|
32091
|
+
return;
|
|
32092
|
+
}
|
|
32093
|
+
}
|
|
32094
|
+
return unwrap(current);
|
|
32095
|
+
}
|
|
32096
|
+
function describeInput(value) {
|
|
32097
|
+
if (value === null)
|
|
32098
|
+
return "null";
|
|
32099
|
+
if (Array.isArray(value))
|
|
32100
|
+
return "an array";
|
|
32101
|
+
switch (typeof value) {
|
|
32102
|
+
case "number":
|
|
32103
|
+
case "boolean":
|
|
32104
|
+
return String(value);
|
|
32105
|
+
case "string":
|
|
32106
|
+
return "a string";
|
|
32107
|
+
case "object":
|
|
32108
|
+
return "an object";
|
|
32109
|
+
default:
|
|
32110
|
+
return typeof value;
|
|
32111
|
+
}
|
|
32112
|
+
}
|
|
32113
|
+
function describeExpected(expected, schema) {
|
|
32114
|
+
switch (expected) {
|
|
32115
|
+
case "int":
|
|
32116
|
+
return "an integer";
|
|
32117
|
+
case "object": {
|
|
32118
|
+
const shape = shapeOf(schema);
|
|
32119
|
+
const keys = shape === undefined ? [] : Object.entries(shape).map(([key, field]) => `${key}${field.def.type === "optional" ? "?" : ""}`);
|
|
32120
|
+
return keys.length === 0 ? "an object" : `an object {${keys.join(", ")}}`;
|
|
32121
|
+
}
|
|
32122
|
+
case "array":
|
|
32123
|
+
return "an array";
|
|
32124
|
+
default:
|
|
32125
|
+
return `a ${expected}`;
|
|
32126
|
+
}
|
|
32127
|
+
}
|
|
32128
|
+
function formatZodIssues(issues, schema) {
|
|
32129
|
+
const allowed = Object.keys(shapeOf(schema) ?? {}).join(", ");
|
|
32130
|
+
return issues.flatMap((issue) => {
|
|
32131
|
+
const path = issue.path.map(String).join(".");
|
|
32132
|
+
switch (issue.code) {
|
|
32133
|
+
case "invalid_type":
|
|
32134
|
+
return issue.input === undefined ? [`${path} is required (${issue.expected})`] : [
|
|
32135
|
+
`${path} must be ${describeExpected(issue.expected, schemaAt(schema, issue.path))}, not ${describeInput(issue.input)}`
|
|
32136
|
+
];
|
|
32137
|
+
case "unrecognized_keys":
|
|
32138
|
+
return issue.keys.map((key) => `unknown field "${key}"; allowed: ${allowed}`);
|
|
32139
|
+
case "invalid_value":
|
|
32140
|
+
return [
|
|
32141
|
+
`${path} must be one of ${issue.values.map(String).join("|")}; got ${JSON.stringify(issue.input)}`
|
|
32142
|
+
];
|
|
32143
|
+
case "too_big":
|
|
32144
|
+
if (issue.origin === "array" && Array.isArray(issue.input)) {
|
|
32145
|
+
return [`${path} has ${issue.input.length} items; the limit is ${issue.maximum}`];
|
|
32146
|
+
}
|
|
32147
|
+
return [`${path} ${issue.message}`];
|
|
32148
|
+
case "custom":
|
|
32149
|
+
return [issue.message];
|
|
32150
|
+
default:
|
|
32151
|
+
return [path === "" ? issue.message : `${path}: ${issue.message}`];
|
|
32152
|
+
}
|
|
32153
|
+
});
|
|
32154
|
+
}
|
|
32155
|
+
|
|
31961
32156
|
// ../envoy-client/src/dispatch-execute.ts
|
|
31962
32157
|
function documentResultDetails(artifact) {
|
|
31963
32158
|
return {
|
|
@@ -32052,23 +32247,19 @@ function askUrgency(args) {
|
|
|
32052
32247
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
32053
32248
|
}
|
|
32054
32249
|
function askKind(args) {
|
|
32055
|
-
|
|
32056
|
-
if (value === undefined || value === "action")
|
|
32057
|
-
return value;
|
|
32058
|
-
throw new Error("kind must be action");
|
|
32250
|
+
return optionalString(args, "kind") === "action" ? "action" : undefined;
|
|
32059
32251
|
}
|
|
32060
|
-
|
|
32061
|
-
|
|
32062
|
-
|
|
32063
|
-
if (ref === undefined || question.includes(ref))
|
|
32064
|
-
return question;
|
|
32065
|
-
const withRef = `${question}
|
|
32252
|
+
var maxAskQuestion16 = 800;
|
|
32253
|
+
function questionWithRef(question, ref) {
|
|
32254
|
+
return ref === undefined || question.includes(ref) ? question : `${question}
|
|
32066
32255
|
|
|
32067
32256
|
Ref: ${ref}`;
|
|
32068
|
-
|
|
32069
|
-
|
|
32070
|
-
|
|
32071
|
-
|
|
32257
|
+
}
|
|
32258
|
+
function askQuestionWithRef(args) {
|
|
32259
|
+
return questionWithRef(stringArg(args, "question"), optionalString(args, "ref"));
|
|
32260
|
+
}
|
|
32261
|
+
function askQuestionProblem(withRef) {
|
|
32262
|
+
return withRef.length > maxAskQuestion16 ? `question plus ref ${overCapMessage(withRef.length, maxAskQuestion16)}; shorten the question or drop the ref` : undefined;
|
|
32072
32263
|
}
|
|
32073
32264
|
function parseDispatchRef(ref) {
|
|
32074
32265
|
const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
|
|
@@ -32090,7 +32281,8 @@ function parseDispatchRef(ref) {
|
|
|
32090
32281
|
return {
|
|
32091
32282
|
owner: { kind: "project", project },
|
|
32092
32283
|
kind: targetKind,
|
|
32093
|
-
id: targetID
|
|
32284
|
+
id: targetID,
|
|
32285
|
+
artifact
|
|
32094
32286
|
};
|
|
32095
32287
|
}
|
|
32096
32288
|
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\/([^/]+))?$/);
|
|
@@ -32122,60 +32314,171 @@ function parseDispatchRef(ref) {
|
|
|
32122
32314
|
return { owner, kind: "message", id: message };
|
|
32123
32315
|
return { owner, kind: "issue", id: issue };
|
|
32124
32316
|
}
|
|
32317
|
+
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
32318
|
+
var askIdProblem = "ask must be a bare ask id or a dispatch://.../ask/<id> reference";
|
|
32319
|
+
var messageIdProblem = "in_reply_to must be a full message id (uuid) or a dispatch://KEY/message/<id> reference";
|
|
32125
32320
|
function askId(args) {
|
|
32126
32321
|
const ask = stringArg(args, "ask");
|
|
32127
|
-
|
|
32128
|
-
|
|
32129
|
-
|
|
32130
|
-
|
|
32131
|
-
|
|
32322
|
+
return ask.startsWith("dispatch://") ? parseDispatchRef(ask)?.id ?? ask : ask;
|
|
32323
|
+
}
|
|
32324
|
+
function messageIdOf(value) {
|
|
32325
|
+
const id = value.startsWith("dispatch://") ? (() => {
|
|
32326
|
+
const reference = parseDispatchRef(value);
|
|
32327
|
+
return reference?.kind === "message" ? reference.id : undefined;
|
|
32328
|
+
})() : value;
|
|
32329
|
+
return id !== undefined && uuidPattern.test(id) ? id : undefined;
|
|
32330
|
+
}
|
|
32331
|
+
var idPrefixPattern = /^[0-9a-f][0-9a-f-]{7,}$/i;
|
|
32332
|
+
async function resolveIdPrefix(tool, kind, ref, list) {
|
|
32333
|
+
if (uuidPattern.test(ref.id))
|
|
32334
|
+
return ref.id;
|
|
32335
|
+
const ownerName = ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
|
|
32336
|
+
if (!idPrefixPattern.test(ref.id)) {
|
|
32337
|
+
throw new ToolInputError(tool, [
|
|
32338
|
+
`${kind} id ${ref.id} must be a full uuid or a prefix of at least 8 hex characters`
|
|
32339
|
+
]);
|
|
32132
32340
|
}
|
|
32133
|
-
|
|
32341
|
+
const prefix = ref.id.toLowerCase();
|
|
32342
|
+
const matches = (await list()).filter((item) => item.id.toLowerCase().startsWith(prefix));
|
|
32343
|
+
if (matches.length === 1 && matches[0] !== undefined)
|
|
32344
|
+
return matches[0].id;
|
|
32345
|
+
throw new ToolInputError(tool, [
|
|
32346
|
+
matches.length === 0 ? `${kind} id ${ref.id} matches none of the ${kind}s on ${ownerName}; use the full id` : `${kind} id ${ref.id} matches ${matches.length} ${kind}s on ${ownerName}; use the full id`
|
|
32347
|
+
]);
|
|
32134
32348
|
}
|
|
32135
32349
|
function messageInReplyTo(args) {
|
|
32136
32350
|
const inReplyTo = optionalString(args, "in_reply_to");
|
|
32137
|
-
|
|
32138
|
-
|
|
32139
|
-
|
|
32140
|
-
|
|
32141
|
-
|
|
32351
|
+
return inReplyTo === undefined ? undefined : messageIdOf(inReplyTo);
|
|
32352
|
+
}
|
|
32353
|
+
function argumentProblems(tool, args) {
|
|
32354
|
+
const problems = [];
|
|
32355
|
+
switch (tool) {
|
|
32356
|
+
case "dispatch_ask": {
|
|
32357
|
+
const question = optionalString(args, "question");
|
|
32358
|
+
const ref = optionalString(args, "ref");
|
|
32359
|
+
if (question !== undefined && ref !== undefined && question.length <= maxAskQuestion16) {
|
|
32360
|
+
const problem = askQuestionProblem(questionWithRef(question, ref));
|
|
32361
|
+
if (problem !== undefined)
|
|
32362
|
+
problems.push(problem);
|
|
32363
|
+
}
|
|
32364
|
+
break;
|
|
32365
|
+
}
|
|
32366
|
+
case "dispatch_edit_ask":
|
|
32367
|
+
case "dispatch_resolve_ask": {
|
|
32368
|
+
const ask = optionalString(args, "ask");
|
|
32369
|
+
if (ask?.startsWith("dispatch://") && parseDispatchRef(ask)?.kind !== "ask") {
|
|
32370
|
+
problems.push(askIdProblem);
|
|
32371
|
+
}
|
|
32372
|
+
break;
|
|
32373
|
+
}
|
|
32374
|
+
case "dispatch_comment": {
|
|
32375
|
+
if (optionalString(args, "quote") !== undefined && optionalString(args, "artifact") === undefined) {
|
|
32376
|
+
problems.push("artifact is required when quote is supplied");
|
|
32377
|
+
}
|
|
32378
|
+
if (optionalString(args, "reply_to") !== undefined && optionalString(args, "reply_to_ask") !== undefined) {
|
|
32379
|
+
problems.push("reply_to and reply_to_ask cannot both be set");
|
|
32380
|
+
}
|
|
32381
|
+
break;
|
|
32382
|
+
}
|
|
32383
|
+
case "dispatch_message": {
|
|
32384
|
+
const inReplyTo = optionalString(args, "in_reply_to");
|
|
32385
|
+
if (inReplyTo !== undefined && messageIdOf(inReplyTo) === undefined) {
|
|
32386
|
+
problems.push(messageIdProblem);
|
|
32387
|
+
}
|
|
32388
|
+
break;
|
|
32389
|
+
}
|
|
32142
32390
|
}
|
|
32143
|
-
return
|
|
32391
|
+
return problems;
|
|
32144
32392
|
}
|
|
32393
|
+
function dispatchRefFromUrl(value, serverUrl) {
|
|
32394
|
+
let url;
|
|
32395
|
+
let origin;
|
|
32396
|
+
try {
|
|
32397
|
+
url = new URL(value);
|
|
32398
|
+
origin = new URL(serverUrl).origin;
|
|
32399
|
+
} catch {
|
|
32400
|
+
return;
|
|
32401
|
+
}
|
|
32402
|
+
if (url.origin !== origin)
|
|
32403
|
+
return;
|
|
32404
|
+
const versionOf = (name) => {
|
|
32405
|
+
const value = url.searchParams.get(name);
|
|
32406
|
+
return value !== null && /^[1-9][0-9]*$/.test(value) ? `@v${value}` : "";
|
|
32407
|
+
};
|
|
32408
|
+
const issuePage = url.pathname.match(/^\/issues\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec|log|conversation|children)|\/artifacts\/([^/]+)|\/asks\/([^/]+)|\/comments\/([^/]+)|\/messages\/([^/]+))?\/?$/);
|
|
32409
|
+
if (issuePage) {
|
|
32410
|
+
const [, key, page, artifact, ask, comment, message] = issuePage;
|
|
32411
|
+
const version = versionOf("v");
|
|
32412
|
+
if (page === "spec" && version !== "")
|
|
32413
|
+
return `dispatch://${key}/artifact/spec${version}`;
|
|
32414
|
+
if (page !== undefined)
|
|
32415
|
+
return `dispatch://${key}/${page === "conversation" ? "log" : page}`;
|
|
32416
|
+
if (artifact !== undefined) {
|
|
32417
|
+
return `dispatch://${key}/artifact/${decodeURIComponent(artifact)}${version}`;
|
|
32418
|
+
}
|
|
32419
|
+
if (ask !== undefined)
|
|
32420
|
+
return `dispatch://${key}/ask/${ask}`;
|
|
32421
|
+
if (comment !== undefined)
|
|
32422
|
+
return `dispatch://${key}/comment/${comment}`;
|
|
32423
|
+
if (message !== undefined)
|
|
32424
|
+
return `dispatch://${key}/message/${message}`;
|
|
32425
|
+
return `dispatch://${key}`;
|
|
32426
|
+
}
|
|
32427
|
+
const documentPage = url.pathname.match(/^\/projects\/([A-Z][A-Z0-9]{1,9})\/documents\/([^/]+)\/?$/);
|
|
32428
|
+
if (!documentPage)
|
|
32429
|
+
return;
|
|
32430
|
+
const [, project, slug] = documentPage;
|
|
32431
|
+
const document = `dispatch://${project}/artifact/${decodeURIComponent(slug ?? "")}${versionOf("version")}`;
|
|
32432
|
+
const ask = url.searchParams.get("ask");
|
|
32433
|
+
if (ask !== null)
|
|
32434
|
+
return `${document}/ask/${ask}`;
|
|
32435
|
+
const comment = url.searchParams.get("comment");
|
|
32436
|
+
if (comment !== null)
|
|
32437
|
+
return `${document}/comment/${comment}`;
|
|
32438
|
+
return document;
|
|
32439
|
+
}
|
|
32440
|
+
var refGrammarProblem = "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/<document-ref> (an artifact id, slug, or filename); " + "a dashboard URL on this Dispatch server is accepted too";
|
|
32441
|
+
var ownerRequiredProblem = "issue is required; supply issue or set LEGION_ISSUE";
|
|
32145
32442
|
function toolSchema(tool) {
|
|
32146
32443
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
32147
32444
|
if (!spec)
|
|
32148
32445
|
throw new Error(`Unknown Dispatch tool: ${tool}`);
|
|
32149
32446
|
return dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
|
|
32150
32447
|
}
|
|
32151
|
-
async function resolveOwnerArguments(tool,
|
|
32448
|
+
async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, problems) {
|
|
32152
32449
|
if (issueFreeTools[tool] === true)
|
|
32153
|
-
return { args, ref: null, owner: null };
|
|
32154
|
-
const refArgument =
|
|
32155
|
-
|
|
32156
|
-
|
|
32157
|
-
|
|
32158
|
-
|
|
32159
|
-
|
|
32160
|
-
|
|
32450
|
+
return { args: input, ref: null, owner: null };
|
|
32451
|
+
const refArgument = input.ref;
|
|
32452
|
+
let ref = null;
|
|
32453
|
+
let args = input;
|
|
32454
|
+
if (typeof refArgument === "string") {
|
|
32455
|
+
const refText = dispatchRefFromUrl(refArgument, serverUrl) ?? refArgument;
|
|
32456
|
+
if (refText !== refArgument)
|
|
32457
|
+
args = { ...input, ref: refText };
|
|
32458
|
+
ref = parseDispatchRef(refText);
|
|
32459
|
+
if (ref === null) {
|
|
32460
|
+
problems.push(tool === "dispatch_ask" && !refText.startsWith("dispatch://") ? "ref must be a dispatch:// reference" : refGrammarProblem);
|
|
32461
|
+
}
|
|
32462
|
+
}
|
|
32161
32463
|
const issueArgument = args.issue;
|
|
32162
32464
|
const projectArgument = args.project;
|
|
32163
32465
|
const artifactArgument = args.artifact;
|
|
32164
32466
|
const versionArgument = args.version;
|
|
32165
32467
|
if (issueArgument !== undefined && projectArgument !== undefined) {
|
|
32166
|
-
|
|
32468
|
+
problems.push("exactly one of issue and project is required");
|
|
32167
32469
|
}
|
|
32168
32470
|
if (typeof projectArgument === "string") {
|
|
32169
32471
|
if (!/^[A-Z][A-Z0-9]{1,9}$/.test(projectArgument)) {
|
|
32170
|
-
|
|
32472
|
+
problems.push("project must be a project key such as CORE");
|
|
32171
32473
|
}
|
|
32172
|
-
|
|
32173
|
-
|
|
32474
|
+
const refDocument = ref?.owner.kind === "project" ? ref.artifact ?? ref.id : undefined;
|
|
32475
|
+
if (ref?.owner.kind === "project" && (ref.owner.project !== projectArgument || artifactArgument !== undefined && artifactArgument !== refDocument)) {
|
|
32476
|
+
problems.push("project and ref must name the same document");
|
|
32174
32477
|
}
|
|
32175
32478
|
return {
|
|
32176
32479
|
args: {
|
|
32177
32480
|
...args,
|
|
32178
|
-
...artifactArgument === undefined &&
|
|
32481
|
+
...artifactArgument === undefined && refDocument !== undefined ? { artifact: refDocument } : {},
|
|
32179
32482
|
...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
|
|
32180
32483
|
},
|
|
32181
32484
|
ref,
|
|
@@ -32187,7 +32490,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
32187
32490
|
args: {
|
|
32188
32491
|
...args,
|
|
32189
32492
|
project: ref.owner.project,
|
|
32190
|
-
...artifactArgument === undefined ? { artifact: ref.id } : {},
|
|
32493
|
+
...artifactArgument === undefined ? { artifact: ref.artifact ?? ref.id } : {},
|
|
32191
32494
|
...versionArgument === undefined && ref.version !== undefined ? { version: ref.version } : {}
|
|
32192
32495
|
},
|
|
32193
32496
|
ref,
|
|
@@ -32208,20 +32511,25 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
32208
32511
|
};
|
|
32209
32512
|
}
|
|
32210
32513
|
const legionIssue = env.LEGION_ISSUE;
|
|
32211
|
-
if (!legionIssue)
|
|
32212
|
-
|
|
32514
|
+
if (!legionIssue) {
|
|
32515
|
+
problems.push(ownerRequiredProblem);
|
|
32516
|
+
return { args, ref, owner: null };
|
|
32517
|
+
}
|
|
32213
32518
|
if (nativeIssueKeyPattern.test(legionIssue) || externalIssueRefPattern.test(legionIssue)) {
|
|
32214
32519
|
const issue = canonicalExternalIssueRef(legionIssue);
|
|
32215
|
-
return { args: { ...args, issue }, ref
|
|
32520
|
+
return { args: { ...args, issue }, ref, owner: { kind: "issue", issue } };
|
|
32216
32521
|
}
|
|
32217
32522
|
if (!bareIssueNumberPattern.test(legionIssue)) {
|
|
32218
|
-
|
|
32523
|
+
problems.push("LEGION_ISSUE must be a native issue key (e.g. LEGION-3), an external owner/repo#n reference, or a bare positive issue number");
|
|
32524
|
+
return { args, ref, owner: null };
|
|
32219
32525
|
}
|
|
32220
32526
|
const repo = await resolveCwdRepo(cwd, exec);
|
|
32221
|
-
if (!repo)
|
|
32222
|
-
|
|
32527
|
+
if (!repo) {
|
|
32528
|
+
problems.push("issue is required; LEGION_ISSUE needs a GitHub repository in cwd");
|
|
32529
|
+
return { args, ref, owner: null };
|
|
32530
|
+
}
|
|
32223
32531
|
const issue = `${repo}#${legionIssue}`;
|
|
32224
|
-
return { args: { ...args, issue }, ref
|
|
32532
|
+
return { args: { ...args, issue }, ref, owner: { kind: "issue", issue } };
|
|
32225
32533
|
}
|
|
32226
32534
|
async function resolveArtifact(client, owner, artifactReference) {
|
|
32227
32535
|
if (owner.kind === "project") {
|
|
@@ -32252,7 +32560,8 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
32252
32560
|
const issue = await client.getIssue(owner.issue);
|
|
32253
32561
|
const artifact = artifactReference === undefined || artifactReference === "spec" ? issue.artifacts.find((candidate) => candidate.primary || candidate.id === issue.primary_artifact_id) : issue.artifacts.find((candidate) => candidate.id === artifactReference || candidate.slug === artifactReference || candidate.name === artifactReference);
|
|
32254
32562
|
if (!artifact) {
|
|
32255
|
-
|
|
32563
|
+
const slugs = issue.artifacts.map((candidate) => `${candidate.slug}${candidate.primary || candidate.id === issue.primary_artifact_id ? " (primary)" : ""}`);
|
|
32564
|
+
throw new Error(`artifact "${artifactReference ?? "spec"}" was not found on issue ${issue.key}; artifacts: ${slugs.length === 0 ? "none" : slugs.join(", ")}`);
|
|
32256
32565
|
}
|
|
32257
32566
|
return { owner, issue, artifact };
|
|
32258
32567
|
}
|
|
@@ -32309,7 +32618,7 @@ function issueSummary(issue, events, references) {
|
|
|
32309
32618
|
...typeof references === "string" ? [`- ${references}`] : references.members.length === 0 ? ["- none"] : references.members.map(({ artifact, depth, via }) => `- ${artifact.project}/${artifact.slug} \xB7 depth ${depth} via ${via.kind} ${via.id}`),
|
|
32310
32619
|
...typeof references === "string" || !references.truncated ? [] : ["- more references beyond 8 hops"],
|
|
32311
32620
|
"Events:",
|
|
32312
|
-
...events.length === 0 ? ["- none"] : events.map(
|
|
32621
|
+
...events.length === 0 ? ["- none"] : events.map(eventLine)
|
|
32313
32622
|
].join(`
|
|
32314
32623
|
`);
|
|
32315
32624
|
}
|
|
@@ -32317,10 +32626,41 @@ function logSummary(issue, events) {
|
|
|
32317
32626
|
return [
|
|
32318
32627
|
`Key: ${issue.key}`,
|
|
32319
32628
|
"Events:",
|
|
32320
|
-
...events.length === 0 ? ["- none"] : events.map(
|
|
32629
|
+
...events.length === 0 ? ["- none"] : events.map(eventLine)
|
|
32321
32630
|
].join(`
|
|
32322
32631
|
`);
|
|
32323
32632
|
}
|
|
32633
|
+
function eventHead(event) {
|
|
32634
|
+
switch (event.type) {
|
|
32635
|
+
case "ask.opened":
|
|
32636
|
+
case "ask.edited":
|
|
32637
|
+
case "ask.resolved":
|
|
32638
|
+
return textHead(event.payload.question);
|
|
32639
|
+
case "ask.answered":
|
|
32640
|
+
return `${textHead(event.payload.question)} -> ${textHead(askAnswerText(event.payload.answer))}`;
|
|
32641
|
+
case "comment.created":
|
|
32642
|
+
case "comment.edited":
|
|
32643
|
+
case "comment.resolved":
|
|
32644
|
+
case "comment.reopened":
|
|
32645
|
+
case "suggestion.accepted":
|
|
32646
|
+
case "suggestion.rejected":
|
|
32647
|
+
case "message.created":
|
|
32648
|
+
case "message.answered":
|
|
32649
|
+
return textHead(event.payload.body);
|
|
32650
|
+
case "artifact.version":
|
|
32651
|
+
return textHead(`${event.payload.name} v${event.payload.version.number}${event.payload.version.summary ? `: ${event.payload.version.summary}` : ""}`);
|
|
32652
|
+
case "issue.created":
|
|
32653
|
+
case "issue.updated":
|
|
32654
|
+
case "issue.closed":
|
|
32655
|
+
return `status ${event.payload.status}`;
|
|
32656
|
+
default:
|
|
32657
|
+
return;
|
|
32658
|
+
}
|
|
32659
|
+
}
|
|
32660
|
+
function eventLine(event) {
|
|
32661
|
+
const head = eventHead(event);
|
|
32662
|
+
return `- #${event.seq} ${event.type} \xB7 ${event.actor.kind} ${event.actor.id} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
|
|
32663
|
+
}
|
|
32324
32664
|
function childrenSummary(issue) {
|
|
32325
32665
|
return [
|
|
32326
32666
|
`Key: ${issue.key}`,
|
|
@@ -32455,19 +32795,29 @@ async function executeDispatchTool(input) {
|
|
|
32455
32795
|
throw error;
|
|
32456
32796
|
}
|
|
32457
32797
|
}, { preconnect: baseFetch.preconnect });
|
|
32798
|
+
const env = input.env ?? process.env;
|
|
32799
|
+
const exec = input.exec ?? defaultExec;
|
|
32800
|
+
const problems = [];
|
|
32801
|
+
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec, configUrl, problems);
|
|
32802
|
+
const ownerMissing = issueFreeTools[input.tool] !== true && ownerArguments.owner === null;
|
|
32803
|
+
const schema = toolSchema(input.tool);
|
|
32804
|
+
const parsed = schema.safeParse(ownerArguments.args, { reportInput: true });
|
|
32805
|
+
if (!parsed.success) {
|
|
32806
|
+
const issues = parsed.error.issues.filter((issue) => !ownerMissing || !(issue.code === "invalid_type" && issue.path.length === 1 && issue.path[0] === "issue" && issue.input === undefined || issue.code === "custom" && issue.path.length === 0 && issue.message.startsWith("Exactly one of issue and project is required")));
|
|
32807
|
+
problems.push(...formatZodIssues(issues, schema));
|
|
32808
|
+
}
|
|
32809
|
+
problems.push(...argumentProblems(input.tool, ownerArguments.args));
|
|
32810
|
+
if (problems.length > 0)
|
|
32811
|
+
throw new ToolInputError(input.tool, problems);
|
|
32458
32812
|
if (input.tool === "dispatch_open_asks") {
|
|
32459
32813
|
const sessionId = input.sessionId?.trim();
|
|
32460
32814
|
if (!sessionId)
|
|
32461
32815
|
throw new Error("host session id is required for dispatch_open_asks");
|
|
32462
|
-
toolSchema(input.tool).parse(input.args);
|
|
32463
32816
|
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
32464
32817
|
const response = await client.openAsks(sessionId);
|
|
32465
32818
|
return { text: formatOpenAsksSummary(response, configUrl), details: { ...response } };
|
|
32466
32819
|
}
|
|
32467
|
-
const
|
|
32468
|
-
const exec = input.exec ?? defaultExec;
|
|
32469
|
-
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
|
|
32470
|
-
const args = toolSchema(input.tool).parse(ownerArguments.args);
|
|
32820
|
+
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
32471
32821
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
32472
32822
|
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
32473
32823
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
@@ -32550,8 +32900,6 @@ async function executeDispatchTool(input) {
|
|
|
32550
32900
|
}
|
|
32551
32901
|
case "dispatch_resolve_ask": {
|
|
32552
32902
|
const kind = stringArg(args, "kind");
|
|
32553
|
-
if (kind !== "retracted" && kind !== "resolved")
|
|
32554
|
-
throw new Error("kind must be retracted or resolved");
|
|
32555
32903
|
const ask = await client.resolveAsk(stringArg(args, "ask"), {
|
|
32556
32904
|
kind,
|
|
32557
32905
|
reason: stringArg(args, "reason"),
|
|
@@ -32608,24 +32956,12 @@ async function executeDispatchTool(input) {
|
|
|
32608
32956
|
}
|
|
32609
32957
|
case "dispatch_comment": {
|
|
32610
32958
|
const artifactReference = optionalString(args, "artifact");
|
|
32611
|
-
if (optionalString(args, "quote") !== undefined && artifactReference === undefined) {
|
|
32612
|
-
throw new Error("artifact is required when quote is supplied");
|
|
32613
|
-
}
|
|
32614
32959
|
const owner = documentOwner();
|
|
32615
32960
|
const resolved = owner.kind === "project" || artifactReference === undefined ? owner.kind === "project" ? await resolveArtifact(client, owner, artifactReference) : undefined : await resolveArtifact(client, owner, artifactReference);
|
|
32616
32961
|
const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
|
|
32617
32962
|
const replyTo = optionalString(args, "reply_to");
|
|
32618
32963
|
const replyToAsk = optionalString(args, "reply_to_ask");
|
|
32619
|
-
if (replyTo !== undefined && replyToAsk !== undefined) {
|
|
32620
|
-
throw new Error("reply_to and reply_to_ask cannot both be set");
|
|
32621
|
-
}
|
|
32622
32964
|
const requestedTurn = optionalString(args, "turn");
|
|
32623
|
-
if (requestedTurn !== undefined && replyToAsk === undefined) {
|
|
32624
|
-
throw new Error("turn requires reply_to_ask");
|
|
32625
|
-
}
|
|
32626
|
-
if (requestedTurn !== undefined && requestedTurn !== "agent" && requestedTurn !== "human") {
|
|
32627
|
-
throw new Error("turn must be agent or human");
|
|
32628
|
-
}
|
|
32629
32965
|
const commentInput = {
|
|
32630
32966
|
body: stringArg(args, "body"),
|
|
32631
32967
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
@@ -32779,17 +33115,21 @@ ${trailer.join(`
|
|
|
32779
33115
|
}
|
|
32780
33116
|
case "dispatch_read": {
|
|
32781
33117
|
if (ownerArguments.ref?.kind === "ask") {
|
|
32782
|
-
const
|
|
33118
|
+
const ref = ownerArguments.ref;
|
|
33119
|
+
const id = await resolveIdPrefix(input.tool, "ask", ref, async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
|
|
33120
|
+
const askRead = await client.getAsk(id);
|
|
32783
33121
|
return {
|
|
32784
33122
|
text: askSummary(askRead),
|
|
32785
|
-
details:
|
|
33123
|
+
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue }
|
|
32786
33124
|
};
|
|
32787
33125
|
}
|
|
32788
33126
|
if (ownerArguments.ref?.kind === "comment") {
|
|
32789
|
-
const
|
|
33127
|
+
const ref = ownerArguments.ref;
|
|
33128
|
+
const id = await resolveIdPrefix(input.tool, "comment", ref, async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
|
|
33129
|
+
const comment = await client.getComment(id);
|
|
32790
33130
|
return {
|
|
32791
33131
|
text: commentSummary(comment),
|
|
32792
|
-
details:
|
|
33132
|
+
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: comment.comment.issue_key }
|
|
32793
33133
|
};
|
|
32794
33134
|
}
|
|
32795
33135
|
if (ownerArguments.ref?.kind === "message") {
|
|
@@ -32918,8 +33258,8 @@ function messageMetadataShape(schema) {
|
|
|
32918
33258
|
return {
|
|
32919
33259
|
in_reply_to: schema.string().optional(),
|
|
32920
33260
|
supersedes: schema.string().optional(),
|
|
32921
|
-
urgency: schema.enum(URGENCY_VALUES).optional(),
|
|
32922
|
-
expects_reply: schema.enum(EXPECTS_REPLY_VALUES).optional(),
|
|
33261
|
+
urgency: schema.enum(URGENCY_VALUES).describe("low | med | high | blocking").optional(),
|
|
33262
|
+
expects_reply: schema.enum(EXPECTS_REPLY_VALUES).describe("none | optional | required").optional(),
|
|
32923
33263
|
expires_at: schema.number({ int: true }).optional()
|
|
32924
33264
|
};
|
|
32925
33265
|
}
|
|
@@ -33029,6 +33369,17 @@ var envoyToolSpecs = [
|
|
|
33029
33369
|
requiresSubscriptionCapability: false
|
|
33030
33370
|
}
|
|
33031
33371
|
];
|
|
33372
|
+
function parseEnvoyToolArguments(operation, parameters) {
|
|
33373
|
+
const spec = envoyToolSpecs.find((candidate) => candidate.operation === operation);
|
|
33374
|
+
if (spec === undefined)
|
|
33375
|
+
throw new Error(`missing Envoy tool specification for ${operation}`);
|
|
33376
|
+
const schema = object(spec.arguments(zodSchemaApi(exports_external))).strict();
|
|
33377
|
+
const parsed = schema.safeParse(parameters, { reportInput: true });
|
|
33378
|
+
if (!parsed.success) {
|
|
33379
|
+
throw new ToolInputError(spec.name, formatZodIssues(parsed.error.issues, schema));
|
|
33380
|
+
}
|
|
33381
|
+
return parsed.data;
|
|
33382
|
+
}
|
|
33032
33383
|
|
|
33033
33384
|
// ../envoy-client/src/transport.ts
|
|
33034
33385
|
var DEFAULT_TIMEOUT_MS = 5000;
|
|
@@ -33912,6 +34263,7 @@ function envoyExtension(pi) {
|
|
|
33912
34263
|
label: spec.name,
|
|
33913
34264
|
description: spec.description,
|
|
33914
34265
|
parameters: schemaFor(pi, spec.operation),
|
|
34266
|
+
lenientArgValidation: true,
|
|
33915
34267
|
execute: async (_id, parameters) => execute(spec.operation, parameters)
|
|
33916
34268
|
});
|
|
33917
34269
|
}
|
|
@@ -33922,6 +34274,7 @@ function envoyExtension(pi) {
|
|
|
33922
34274
|
label: spec.name,
|
|
33923
34275
|
description: spec.description,
|
|
33924
34276
|
parameters: dispatchToolSchema(spec, zodSchemaApi(pi.zod)),
|
|
34277
|
+
lenientArgValidation: true,
|
|
33925
34278
|
execute: async (_id, params, signal, _onUpdate, context) => {
|
|
33926
34279
|
try {
|
|
33927
34280
|
const result = await executeDispatchTool({
|
|
@@ -33994,8 +34347,9 @@ ${formatOpenAsksSummary(snapshot, currentDispatchConfig().url)}`,
|
|
|
33994
34347
|
activeSessionContext?.ui.notify(`envoy: dispatch reply auto-subscribe failed (${messageFor(error)}); run envoy_subscribe ${topic}`, "warning");
|
|
33995
34348
|
}
|
|
33996
34349
|
});
|
|
33997
|
-
async function execute(operation,
|
|
34350
|
+
async function execute(operation, rawParameters) {
|
|
33998
34351
|
try {
|
|
34352
|
+
const parameters = parseEnvoyToolArguments(operation, rawParameters);
|
|
33999
34353
|
switch (operation) {
|
|
34000
34354
|
case EnvoyToolOperation.subscribe: {
|
|
34001
34355
|
const added = [];
|
package/dist/legion.js
CHANGED
|
@@ -29177,7 +29177,7 @@ var dispatchToolSpecs = [
|
|
|
29177
29177
|
options: z.array(z.object({
|
|
29178
29178
|
label: z.string().describe("Selectable option label."),
|
|
29179
29179
|
description: z.string().describe("Optional option context.").optional()
|
|
29180
|
-
}), { max: 8 }).describe("
|
|
29180
|
+
}), { max: 8 }).describe("Up to 8 choices, each an object { label, description? } (never a bare string).").optional(),
|
|
29181
29181
|
multiple: z.boolean().describe("Whether multiple choices may be selected.").optional(),
|
|
29182
29182
|
urgency: z.enum(ASK_URGENCIES).describe("Optional decision urgency.").optional(),
|
|
29183
29183
|
anchor: z.object({
|
|
@@ -29229,7 +29229,7 @@ var dispatchToolSpecs = [
|
|
|
29229
29229
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
29230
29230
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
29231
29231
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
29232
|
-
reply_to: z.string().describe("
|
|
29232
|
+
reply_to: z.string().describe("A comment id (uuid); replying to any comment in a thread continues that thread (an " + "ask's clarification thread included). To reply to an ask, use reply_to_ask with the " + "ask id instead.").optional(),
|
|
29233
29233
|
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional(),
|
|
29234
29234
|
turn: z.enum(["agent", "human"]).describe("Only with reply_to_ask: who holds the turn after this reply. agent: a progress note - " + "you keep the turn and the ask stays 'Waiting on agents' for the human; human (default): " + "you need the human to act - the ask returns to 'Waiting on you'.").optional()
|
|
29235
29235
|
}),
|
|
@@ -29261,7 +29261,7 @@ var dispatchToolSpecs = [
|
|
|
29261
29261
|
},
|
|
29262
29262
|
{
|
|
29263
29263
|
name: "dispatch_doc_edit",
|
|
29264
|
-
description: "Apply deterministic document edits, including retyping an identified paragraph into a schema-declared typed block. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " +
|
|
29264
|
+
description: "Apply deterministic document edits, including retyping an identified paragraph into a schema-declared typed block. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + 'For replace, delete, and quote insert anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading \'# \' matches a heading. Insert anchors also accept "start", "end", and "heading:<exact heading text>". ' + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
29265
29265
|
arguments: (z) => ({
|
|
29266
29266
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29267
29267
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -29269,12 +29269,12 @@ var dispatchToolSpecs = [
|
|
|
29269
29269
|
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
29270
29270
|
ops: z.array(z.object({
|
|
29271
29271
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
29272
|
-
find: z.string().describe("Text
|
|
29272
|
+
find: z.string().describe("Text of the target block as rendered, for replace or delete; inline markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading.").optional(),
|
|
29273
29273
|
with: z.string().describe("Replacement text for replace.").optional(),
|
|
29274
29274
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
29275
29275
|
markdown: z.string().describe("Markdown to insert.").optional(),
|
|
29276
|
-
after: z.string().describe(
|
|
29277
|
-
before: z.string().describe(
|
|
29276
|
+
after: z.string().describe(`Insert after this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>".`).optional(),
|
|
29277
|
+
before: z.string().describe(`Insert before this anchor: a quote of the neighbouring block's text, or one of "start", "end", "heading:<exact heading text>".`).optional(),
|
|
29278
29278
|
block: z.string().describe("Block id to retype.").optional(),
|
|
29279
29279
|
type: z.string().describe("Typed block name for retype.").optional(),
|
|
29280
29280
|
attributes: z.unknown().describe("Typed block attributes for retype.").optional()
|
|
@@ -29778,6 +29778,9 @@ function canonicalRepo(owner, repo) {
|
|
|
29778
29778
|
return `${owner.trim().toLowerCase()}/${repo.trim().toLowerCase().replace(/\.git$/, "")}`;
|
|
29779
29779
|
}
|
|
29780
29780
|
// ../contracts/src/tool-schema.ts
|
|
29781
|
+
function overCapMessage(length, max) {
|
|
29782
|
+
return `is ${length - max} characters over the ${max}-character limit (${length}/${max})`;
|
|
29783
|
+
}
|
|
29781
29784
|
function zodSchemaApi(zod) {
|
|
29782
29785
|
const api = zod;
|
|
29783
29786
|
return {
|
|
@@ -29785,8 +29788,12 @@ function zodSchemaApi(zod) {
|
|
|
29785
29788
|
let schema = api.string();
|
|
29786
29789
|
if (opts.min !== undefined)
|
|
29787
29790
|
schema = schema.min(opts.min);
|
|
29788
|
-
if (opts.max !== undefined)
|
|
29789
|
-
|
|
29791
|
+
if (opts.max !== undefined) {
|
|
29792
|
+
const max = opts.max;
|
|
29793
|
+
schema = schema.max(max, {
|
|
29794
|
+
error: (issue) => overCapMessage(typeof issue.input === "string" ? issue.input.length : max + 1, max)
|
|
29795
|
+
});
|
|
29796
|
+
}
|
|
29790
29797
|
return schema;
|
|
29791
29798
|
},
|
|
29792
29799
|
number: (opts = {}) => {
|
|
@@ -10,7 +10,9 @@ decide, never a log of your work. The transcript is your scratch pad; progress a
|
|
|
10
10
|
goes through a `dispatch_*` tool.
|
|
11
11
|
|
|
12
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
|
|
13
|
+
most 2,000 characters; an artifact is at most 25 MiB. It refuses over-limit input with the number to trim (`question is 50 characters over
|
|
14
|
+
the 800-character limit (850/800)`); it never truncates it. A tool call with several problems is refused once, every problem listed
|
|
15
|
+
(`<tool> was not called: N problems`), so one corrected call lands. GitHub threads and markers no
|
|
14
16
|
longer exist.
|
|
15
17
|
|
|
16
18
|
## Writing for the human
|
|
@@ -273,7 +275,8 @@ within one textblock; split changes that span separate blocks into separate oper
|
|
|
273
275
|
insert anchor is a quote, `"start"`, `"end"`, or `"heading:Title"`. Ordinary inserts create a sibling block before or after the quote or
|
|
274
276
|
heading's enclosing document block; `"start"` and `"end"` select the document edges. At a table-cell quote, a body-row fragment (no
|
|
275
277
|
header or delimiter rows) extends that table before or after the matched row instead; short rows are padded, wider rows are rejected,
|
|
276
|
-
and deleting a cell's quoted text removes only that text.
|
|
278
|
+
and deleting a cell's quoted text removes only that text. A `find` or quote anchor tolerates inline Markdown (`**bold**`, `` `code` ``)
|
|
279
|
+
and a leading `# ` selects a heading by its text; a miss names the three nearest blocks so the next quote lands.
|
|
277
280
|
|
|
278
281
|
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target; re-read a missing or ambiguous target before
|
|
279
282
|
retrying. Pass `summary` to name the version when recording a decision.
|
|
@@ -400,7 +403,7 @@ It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 char
|
|
|
400
403
|
|
|
401
404
|
A human — or any bearer caller over HTTP, such as a test rig — can target the issue message at a
|
|
402
405
|
live Envoy session or role as **BTW**, **Aside**, or **Steer**. The incoming Dispatch frame names
|
|
403
|
-
the issue and includes a `reply_with`
|
|
406
|
+
the issue and includes a `reply_with` hint (`{ tool, args }`, ready to issue on any host); reply on the same open issue with the existing
|
|
404
407
|
tool, never a new targeted send:
|
|
405
408
|
|
|
406
409
|
```ts
|
|
@@ -458,7 +461,11 @@ dispatch://PROJECT/artifact/<document-ref>/ask/<id>
|
|
|
458
461
|
dispatch://PROJECT/artifact/<document-ref>/comment/<id>
|
|
459
462
|
```
|
|
460
463
|
|
|
461
|
-
A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records.
|
|
464
|
+
A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records. `dispatch_read` also
|
|
465
|
+
accepts the dashboard URL of an issue, spec, artifact, ask, comment, or project document on the configured server (it maps to the
|
|
466
|
+
`dispatch://` form above), and an ask or comment id may be a unique prefix of at least 8 hex characters; a message id is always the
|
|
467
|
+
full uuid. A non-uuid id on `GET /asks/{id}`, `/comments/{id}`, or `/issues/{key}/messages/{id}` is a 400 `ASK_ID_INPUT` /
|
|
468
|
+
`COMMENT_ID_INPUT` / `MESSAGE_ID_INPUT`, never a 500.
|
|
462
469
|
|
|
463
470
|
## Before / after
|
|
464
471
|
|
|
@@ -47,8 +47,17 @@ envoy:
|
|
|
47
47
|
expects_reply: required
|
|
48
48
|
re: agent-message-1
|
|
49
49
|
supersedes: agent-message-0
|
|
50
|
-
reply_with:
|
|
51
|
-
|
|
50
|
+
reply_with:
|
|
51
|
+
tool: envoy_send
|
|
52
|
+
args:
|
|
53
|
+
session_id: 01a0bbbb-cccc-7ddd-eeee-0123456789ab
|
|
54
|
+
in_reply_to: agent-message-2
|
|
55
|
+
message: ...
|
|
56
|
+
reply_role:
|
|
57
|
+
tool: envoy_publish
|
|
58
|
+
args:
|
|
59
|
+
topic: notifications.role.legion-reviewer
|
|
60
|
+
message: ...
|
|
52
61
|
summary: Deployment needs confirmation.
|
|
53
62
|
message: "Confirm the listener health check passed.\n\nThen publish the release."
|
|
54
63
|
note: body names session 01a0cccc-dddd-7eee-ffff-0123456789ab; the sender is 01a0bbbb-cccc-7ddd-eeee-0123456789ab
|
|
@@ -62,7 +71,10 @@ envoy:
|
|
|
62
71
|
- `by` is the expiry deadline, when the sender supplied one.
|
|
63
72
|
- `urgency` is the sender's priority classification.
|
|
64
73
|
- `expects_reply` states whether a reply is `none`, `optional`, or `required`.
|
|
65
|
-
- `re` names the delivery this message replies to.
|
|
74
|
+
- `re` names the delivery this message replies to. A Dispatch frame names it as a `dispatch://`
|
|
75
|
+
ref — the ask (`dispatch://KEY/ask/<id>`, or `dispatch://PROJECT/artifact/<slug>/ask/<id>` for a
|
|
76
|
+
document ask) or the message (`dispatch://KEY/message/<id>`) — never the quoted text; the
|
|
77
|
+
question head, when there is one, is `dispatch.question`.
|
|
66
78
|
- `supersedes` names an earlier delivery this one replaces.
|
|
67
79
|
- `reply_with` is the direct-reply call for the sender.
|
|
68
80
|
- `reply_role` is the role-publish reply call when the sender has a role.
|