@sjawhar/opencode-legion-envoy 1.25.0 → 1.26.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/src/server.js +368 -85
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +11 -4
- package/skills/envoy/SKILL.md +15 -3
package/dist/src/server.js
CHANGED
|
@@ -13806,7 +13806,7 @@ var dispatchToolSpecs = [
|
|
|
13806
13806
|
options: z.array(z.object({
|
|
13807
13807
|
label: z.string().describe("Selectable option label."),
|
|
13808
13808
|
description: z.string().describe("Optional option context.").optional()
|
|
13809
|
-
}), { max: 8 }).describe("
|
|
13809
|
+
}), { max: 8 }).describe("Up to 8 choices, each an object { label, description? } (never a bare string).").optional(),
|
|
13810
13810
|
multiple: z.boolean().describe("Whether multiple choices may be selected.").optional(),
|
|
13811
13811
|
urgency: z.enum(ASK_URGENCIES).describe("Optional decision urgency.").optional(),
|
|
13812
13812
|
anchor: z.object({
|
|
@@ -13858,7 +13858,7 @@ var dispatchToolSpecs = [
|
|
|
13858
13858
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
13859
13859
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
13860
13860
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
13861
|
-
reply_to: z.string().describe("
|
|
13861
|
+
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(),
|
|
13862
13862
|
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional(),
|
|
13863
13863
|
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()
|
|
13864
13864
|
}),
|
|
@@ -13890,7 +13890,7 @@ var dispatchToolSpecs = [
|
|
|
13890
13890
|
},
|
|
13891
13891
|
{
|
|
13892
13892
|
name: "dispatch_doc_edit",
|
|
13893
|
-
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. " +
|
|
13893
|
+
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}`,
|
|
13894
13894
|
arguments: (z) => ({
|
|
13895
13895
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13896
13896
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -13898,12 +13898,12 @@ var dispatchToolSpecs = [
|
|
|
13898
13898
|
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional(),
|
|
13899
13899
|
ops: z.array(z.object({
|
|
13900
13900
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
13901
|
-
find: z.string().describe("Text
|
|
13901
|
+
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(),
|
|
13902
13902
|
with: z.string().describe("Replacement text for replace.").optional(),
|
|
13903
13903
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
13904
13904
|
markdown: z.string().describe("Markdown to insert.").optional(),
|
|
13905
|
-
after: z.string().describe(
|
|
13906
|
-
before: z.string().describe(
|
|
13905
|
+
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(),
|
|
13906
|
+
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(),
|
|
13907
13907
|
block: z.string().describe("Block id to retype.").optional(),
|
|
13908
13908
|
type: z.string().describe("Typed block name for retype.").optional(),
|
|
13909
13909
|
attributes: z.unknown().describe("Typed block attributes for retype.").optional()
|
|
@@ -14390,6 +14390,9 @@ function canonicalRepo(owner, repo) {
|
|
|
14390
14390
|
return `${owner.trim().toLowerCase()}/${repo.trim().toLowerCase().replace(/\.git$/, "")}`;
|
|
14391
14391
|
}
|
|
14392
14392
|
// ../contracts/src/tool-schema.ts
|
|
14393
|
+
function overCapMessage(length, max) {
|
|
14394
|
+
return `is ${length - max} characters over the ${max}-character limit (${length}/${max})`;
|
|
14395
|
+
}
|
|
14393
14396
|
function zodSchemaApi(zod) {
|
|
14394
14397
|
const api = zod;
|
|
14395
14398
|
return {
|
|
@@ -14397,8 +14400,12 @@ function zodSchemaApi(zod) {
|
|
|
14397
14400
|
let schema = api.string();
|
|
14398
14401
|
if (opts.min !== undefined)
|
|
14399
14402
|
schema = schema.min(opts.min);
|
|
14400
|
-
if (opts.max !== undefined)
|
|
14401
|
-
|
|
14403
|
+
if (opts.max !== undefined) {
|
|
14404
|
+
const max = opts.max;
|
|
14405
|
+
schema = schema.max(max, {
|
|
14406
|
+
error: (issue) => overCapMessage(typeof issue.input === "string" ? issue.input.length : max + 1, max)
|
|
14407
|
+
});
|
|
14408
|
+
}
|
|
14402
14409
|
return schema;
|
|
14403
14410
|
},
|
|
14404
14411
|
number: (opts = {}) => {
|
|
@@ -14597,6 +14604,24 @@ function resolveDispatchConfig(env, options = {}) {
|
|
|
14597
14604
|
// ../envoy-client/src/dispatch-execute.ts
|
|
14598
14605
|
import { resolve as resolvePath } from "path";
|
|
14599
14606
|
|
|
14607
|
+
// ../envoy-client/src/ask-answer.ts
|
|
14608
|
+
var HEAD_LENGTH = 120;
|
|
14609
|
+
function textHead(text, limit = HEAD_LENGTH) {
|
|
14610
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
14611
|
+
return flat.length > limit ? `${flat.slice(0, limit)}\u2026` : flat;
|
|
14612
|
+
}
|
|
14613
|
+
function askAnswerText(answer) {
|
|
14614
|
+
if (answer === null || answer === undefined)
|
|
14615
|
+
return "";
|
|
14616
|
+
const selected = (answer.selected ?? []).join(", ");
|
|
14617
|
+
const text = answer.text ?? "";
|
|
14618
|
+
if (text === "")
|
|
14619
|
+
return selected;
|
|
14620
|
+
if (selected === "")
|
|
14621
|
+
return text;
|
|
14622
|
+
return `${selected} - ${text}`;
|
|
14623
|
+
}
|
|
14624
|
+
|
|
14600
14625
|
// ../envoy-client/src/dispatch-cwd.ts
|
|
14601
14626
|
import { execFile } from "child_process";
|
|
14602
14627
|
import { promisify } from "util";
|
|
@@ -14778,6 +14803,9 @@ class DispatchClient {
|
|
|
14778
14803
|
async comment(issue, input) {
|
|
14779
14804
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], input);
|
|
14780
14805
|
}
|
|
14806
|
+
async listIssueAsks(issue, state) {
|
|
14807
|
+
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "asks"], undefined, state === undefined ? undefined : { state });
|
|
14808
|
+
}
|
|
14781
14809
|
async getArtifactAsks(id, state) {
|
|
14782
14810
|
return this.#json("GET", ["api", "v1", "artifacts", id, "asks"], undefined, state === undefined ? undefined : { state });
|
|
14783
14811
|
}
|
|
@@ -14976,6 +15004,116 @@ class DispatchClient {
|
|
|
14976
15004
|
}
|
|
14977
15005
|
}
|
|
14978
15006
|
|
|
15007
|
+
// ../envoy-client/src/tool-input-errors.ts
|
|
15008
|
+
class ToolInputError extends Error {
|
|
15009
|
+
tool;
|
|
15010
|
+
problems;
|
|
15011
|
+
constructor(tool, problems) {
|
|
15012
|
+
const count = problems.length;
|
|
15013
|
+
super([
|
|
15014
|
+
`${tool} was not called: ${count} problem${count === 1 ? "" : "s"}`,
|
|
15015
|
+
...problems.map((problem) => `- ${problem}`)
|
|
15016
|
+
].join(`
|
|
15017
|
+
`));
|
|
15018
|
+
this.name = "ToolInputError";
|
|
15019
|
+
this.tool = tool;
|
|
15020
|
+
this.problems = problems;
|
|
15021
|
+
}
|
|
15022
|
+
}
|
|
15023
|
+
function unwrap(schema) {
|
|
15024
|
+
let current = schema;
|
|
15025
|
+
while (current !== undefined) {
|
|
15026
|
+
const def = current.def;
|
|
15027
|
+
if (!("innerType" in def) || !(def.type === "optional" || def.type === "nullable" || def.type === "default"))
|
|
15028
|
+
break;
|
|
15029
|
+
current = def.innerType;
|
|
15030
|
+
}
|
|
15031
|
+
return current;
|
|
15032
|
+
}
|
|
15033
|
+
function shapeOf(schema) {
|
|
15034
|
+
const unwrapped = unwrap(schema);
|
|
15035
|
+
if (unwrapped === undefined)
|
|
15036
|
+
return;
|
|
15037
|
+
const def = unwrapped.def;
|
|
15038
|
+
return def.type === "object" && "shape" in def ? def.shape : undefined;
|
|
15039
|
+
}
|
|
15040
|
+
function schemaAt(schema, path) {
|
|
15041
|
+
let current = schema;
|
|
15042
|
+
for (const key of path) {
|
|
15043
|
+
const unwrapped = unwrap(current);
|
|
15044
|
+
if (unwrapped === undefined)
|
|
15045
|
+
return;
|
|
15046
|
+
const def = unwrapped.def;
|
|
15047
|
+
if (def.type === "object" && "shape" in def) {
|
|
15048
|
+
current = shapeOf(unwrapped)?.[String(key)];
|
|
15049
|
+
} else if (def.type === "array" && "element" in def) {
|
|
15050
|
+
current = def.element;
|
|
15051
|
+
} else {
|
|
15052
|
+
return;
|
|
15053
|
+
}
|
|
15054
|
+
}
|
|
15055
|
+
return unwrap(current);
|
|
15056
|
+
}
|
|
15057
|
+
function describeInput(value) {
|
|
15058
|
+
if (value === null)
|
|
15059
|
+
return "null";
|
|
15060
|
+
if (Array.isArray(value))
|
|
15061
|
+
return "an array";
|
|
15062
|
+
switch (typeof value) {
|
|
15063
|
+
case "number":
|
|
15064
|
+
case "boolean":
|
|
15065
|
+
return String(value);
|
|
15066
|
+
case "string":
|
|
15067
|
+
return "a string";
|
|
15068
|
+
case "object":
|
|
15069
|
+
return "an object";
|
|
15070
|
+
default:
|
|
15071
|
+
return typeof value;
|
|
15072
|
+
}
|
|
15073
|
+
}
|
|
15074
|
+
function describeExpected(expected, schema) {
|
|
15075
|
+
switch (expected) {
|
|
15076
|
+
case "int":
|
|
15077
|
+
return "an integer";
|
|
15078
|
+
case "object": {
|
|
15079
|
+
const shape = shapeOf(schema);
|
|
15080
|
+
const keys = shape === undefined ? [] : Object.entries(shape).map(([key, field]) => `${key}${field.def.type === "optional" ? "?" : ""}`);
|
|
15081
|
+
return keys.length === 0 ? "an object" : `an object {${keys.join(", ")}}`;
|
|
15082
|
+
}
|
|
15083
|
+
case "array":
|
|
15084
|
+
return "an array";
|
|
15085
|
+
default:
|
|
15086
|
+
return `a ${expected}`;
|
|
15087
|
+
}
|
|
15088
|
+
}
|
|
15089
|
+
function formatZodIssues(issues, schema) {
|
|
15090
|
+
const allowed = Object.keys(shapeOf(schema) ?? {}).join(", ");
|
|
15091
|
+
return issues.flatMap((issue) => {
|
|
15092
|
+
const path = issue.path.map(String).join(".");
|
|
15093
|
+
switch (issue.code) {
|
|
15094
|
+
case "invalid_type":
|
|
15095
|
+
return issue.input === undefined ? [`${path} is required (${issue.expected})`] : [
|
|
15096
|
+
`${path} must be ${describeExpected(issue.expected, schemaAt(schema, issue.path))}, not ${describeInput(issue.input)}`
|
|
15097
|
+
];
|
|
15098
|
+
case "unrecognized_keys":
|
|
15099
|
+
return issue.keys.map((key) => `unknown field "${key}"; allowed: ${allowed}`);
|
|
15100
|
+
case "invalid_value":
|
|
15101
|
+
return [
|
|
15102
|
+
`${path} must be one of ${issue.values.map(String).join("|")}; got ${JSON.stringify(issue.input)}`
|
|
15103
|
+
];
|
|
15104
|
+
case "too_big":
|
|
15105
|
+
if (issue.origin === "array" && Array.isArray(issue.input)) {
|
|
15106
|
+
return [`${path} has ${issue.input.length} items; the limit is ${issue.maximum}`];
|
|
15107
|
+
}
|
|
15108
|
+
return [`${path} ${issue.message}`];
|
|
15109
|
+
case "custom":
|
|
15110
|
+
return [issue.message];
|
|
15111
|
+
default:
|
|
15112
|
+
return [path === "" ? issue.message : `${path}: ${issue.message}`];
|
|
15113
|
+
}
|
|
15114
|
+
});
|
|
15115
|
+
}
|
|
15116
|
+
|
|
14979
15117
|
// ../envoy-client/src/dispatch-execute.ts
|
|
14980
15118
|
function documentResultDetails(artifact) {
|
|
14981
15119
|
return {
|
|
@@ -15070,23 +15208,19 @@ function askUrgency(args) {
|
|
|
15070
15208
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
15071
15209
|
}
|
|
15072
15210
|
function askKind(args) {
|
|
15073
|
-
|
|
15074
|
-
if (value === undefined || value === "action")
|
|
15075
|
-
return value;
|
|
15076
|
-
throw new Error("kind must be action");
|
|
15211
|
+
return optionalString(args, "kind") === "action" ? "action" : undefined;
|
|
15077
15212
|
}
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
if (ref === undefined || question.includes(ref))
|
|
15082
|
-
return question;
|
|
15083
|
-
const withRef = `${question}
|
|
15213
|
+
var maxAskQuestion16 = 800;
|
|
15214
|
+
function questionWithRef(question, ref) {
|
|
15215
|
+
return ref === undefined || question.includes(ref) ? question : `${question}
|
|
15084
15216
|
|
|
15085
15217
|
Ref: ${ref}`;
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15218
|
+
}
|
|
15219
|
+
function askQuestionWithRef(args) {
|
|
15220
|
+
return questionWithRef(stringArg(args, "question"), optionalString(args, "ref"));
|
|
15221
|
+
}
|
|
15222
|
+
function askQuestionProblem(withRef) {
|
|
15223
|
+
return withRef.length > maxAskQuestion16 ? `question plus ref ${overCapMessage(withRef.length, maxAskQuestion16)}; shorten the question or drop the ref` : undefined;
|
|
15090
15224
|
}
|
|
15091
15225
|
function parseDispatchRef(ref) {
|
|
15092
15226
|
const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
|
|
@@ -15108,7 +15242,8 @@ function parseDispatchRef(ref) {
|
|
|
15108
15242
|
return {
|
|
15109
15243
|
owner: { kind: "project", project },
|
|
15110
15244
|
kind: targetKind,
|
|
15111
|
-
id: targetID
|
|
15245
|
+
id: targetID,
|
|
15246
|
+
artifact
|
|
15112
15247
|
};
|
|
15113
15248
|
}
|
|
15114
15249
|
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\/([^/]+))?$/);
|
|
@@ -15140,60 +15275,171 @@ function parseDispatchRef(ref) {
|
|
|
15140
15275
|
return { owner, kind: "message", id: message };
|
|
15141
15276
|
return { owner, kind: "issue", id: issue };
|
|
15142
15277
|
}
|
|
15278
|
+
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15279
|
+
var askIdProblem = "ask must be a bare ask id or a dispatch://.../ask/<id> reference";
|
|
15280
|
+
var messageIdProblem = "in_reply_to must be a full message id (uuid) or a dispatch://KEY/message/<id> reference";
|
|
15143
15281
|
function askId(args) {
|
|
15144
15282
|
const ask = stringArg(args, "ask");
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15283
|
+
return ask.startsWith("dispatch://") ? parseDispatchRef(ask)?.id ?? ask : ask;
|
|
15284
|
+
}
|
|
15285
|
+
function messageIdOf(value) {
|
|
15286
|
+
const id = value.startsWith("dispatch://") ? (() => {
|
|
15287
|
+
const reference = parseDispatchRef(value);
|
|
15288
|
+
return reference?.kind === "message" ? reference.id : undefined;
|
|
15289
|
+
})() : value;
|
|
15290
|
+
return id !== undefined && uuidPattern.test(id) ? id : undefined;
|
|
15291
|
+
}
|
|
15292
|
+
var idPrefixPattern = /^[0-9a-f][0-9a-f-]{7,}$/i;
|
|
15293
|
+
async function resolveIdPrefix(tool, kind, ref, list) {
|
|
15294
|
+
if (uuidPattern.test(ref.id))
|
|
15295
|
+
return ref.id;
|
|
15296
|
+
const ownerName = ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
|
|
15297
|
+
if (!idPrefixPattern.test(ref.id)) {
|
|
15298
|
+
throw new ToolInputError(tool, [
|
|
15299
|
+
`${kind} id ${ref.id} must be a full uuid or a prefix of at least 8 hex characters`
|
|
15300
|
+
]);
|
|
15150
15301
|
}
|
|
15151
|
-
|
|
15302
|
+
const prefix = ref.id.toLowerCase();
|
|
15303
|
+
const matches = (await list()).filter((item) => item.id.toLowerCase().startsWith(prefix));
|
|
15304
|
+
if (matches.length === 1 && matches[0] !== undefined)
|
|
15305
|
+
return matches[0].id;
|
|
15306
|
+
throw new ToolInputError(tool, [
|
|
15307
|
+
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`
|
|
15308
|
+
]);
|
|
15152
15309
|
}
|
|
15153
15310
|
function messageInReplyTo(args) {
|
|
15154
15311
|
const inReplyTo = optionalString(args, "in_reply_to");
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15312
|
+
return inReplyTo === undefined ? undefined : messageIdOf(inReplyTo);
|
|
15313
|
+
}
|
|
15314
|
+
function argumentProblems(tool, args) {
|
|
15315
|
+
const problems = [];
|
|
15316
|
+
switch (tool) {
|
|
15317
|
+
case "dispatch_ask": {
|
|
15318
|
+
const question = optionalString(args, "question");
|
|
15319
|
+
const ref = optionalString(args, "ref");
|
|
15320
|
+
if (question !== undefined && ref !== undefined && question.length <= maxAskQuestion16) {
|
|
15321
|
+
const problem = askQuestionProblem(questionWithRef(question, ref));
|
|
15322
|
+
if (problem !== undefined)
|
|
15323
|
+
problems.push(problem);
|
|
15324
|
+
}
|
|
15325
|
+
break;
|
|
15326
|
+
}
|
|
15327
|
+
case "dispatch_edit_ask":
|
|
15328
|
+
case "dispatch_resolve_ask": {
|
|
15329
|
+
const ask = optionalString(args, "ask");
|
|
15330
|
+
if (ask?.startsWith("dispatch://") && parseDispatchRef(ask)?.kind !== "ask") {
|
|
15331
|
+
problems.push(askIdProblem);
|
|
15332
|
+
}
|
|
15333
|
+
break;
|
|
15334
|
+
}
|
|
15335
|
+
case "dispatch_comment": {
|
|
15336
|
+
if (optionalString(args, "quote") !== undefined && optionalString(args, "artifact") === undefined) {
|
|
15337
|
+
problems.push("artifact is required when quote is supplied");
|
|
15338
|
+
}
|
|
15339
|
+
if (optionalString(args, "reply_to") !== undefined && optionalString(args, "reply_to_ask") !== undefined) {
|
|
15340
|
+
problems.push("reply_to and reply_to_ask cannot both be set");
|
|
15341
|
+
}
|
|
15342
|
+
break;
|
|
15343
|
+
}
|
|
15344
|
+
case "dispatch_message": {
|
|
15345
|
+
const inReplyTo = optionalString(args, "in_reply_to");
|
|
15346
|
+
if (inReplyTo !== undefined && messageIdOf(inReplyTo) === undefined) {
|
|
15347
|
+
problems.push(messageIdProblem);
|
|
15348
|
+
}
|
|
15349
|
+
break;
|
|
15350
|
+
}
|
|
15160
15351
|
}
|
|
15161
|
-
return
|
|
15352
|
+
return problems;
|
|
15162
15353
|
}
|
|
15354
|
+
function dispatchRefFromUrl(value, serverUrl) {
|
|
15355
|
+
let url;
|
|
15356
|
+
let origin;
|
|
15357
|
+
try {
|
|
15358
|
+
url = new URL(value);
|
|
15359
|
+
origin = new URL(serverUrl).origin;
|
|
15360
|
+
} catch {
|
|
15361
|
+
return;
|
|
15362
|
+
}
|
|
15363
|
+
if (url.origin !== origin)
|
|
15364
|
+
return;
|
|
15365
|
+
const versionOf = (name) => {
|
|
15366
|
+
const value = url.searchParams.get(name);
|
|
15367
|
+
return value !== null && /^[1-9][0-9]*$/.test(value) ? `@v${value}` : "";
|
|
15368
|
+
};
|
|
15369
|
+
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\/([^/]+))?\/?$/);
|
|
15370
|
+
if (issuePage) {
|
|
15371
|
+
const [, key, page, artifact, ask, comment, message] = issuePage;
|
|
15372
|
+
const version = versionOf("v");
|
|
15373
|
+
if (page === "spec" && version !== "")
|
|
15374
|
+
return `dispatch://${key}/artifact/spec${version}`;
|
|
15375
|
+
if (page !== undefined)
|
|
15376
|
+
return `dispatch://${key}/${page === "conversation" ? "log" : page}`;
|
|
15377
|
+
if (artifact !== undefined) {
|
|
15378
|
+
return `dispatch://${key}/artifact/${decodeURIComponent(artifact)}${version}`;
|
|
15379
|
+
}
|
|
15380
|
+
if (ask !== undefined)
|
|
15381
|
+
return `dispatch://${key}/ask/${ask}`;
|
|
15382
|
+
if (comment !== undefined)
|
|
15383
|
+
return `dispatch://${key}/comment/${comment}`;
|
|
15384
|
+
if (message !== undefined)
|
|
15385
|
+
return `dispatch://${key}/message/${message}`;
|
|
15386
|
+
return `dispatch://${key}`;
|
|
15387
|
+
}
|
|
15388
|
+
const documentPage = url.pathname.match(/^\/projects\/([A-Z][A-Z0-9]{1,9})\/documents\/([^/]+)\/?$/);
|
|
15389
|
+
if (!documentPage)
|
|
15390
|
+
return;
|
|
15391
|
+
const [, project, slug] = documentPage;
|
|
15392
|
+
const document = `dispatch://${project}/artifact/${decodeURIComponent(slug ?? "")}${versionOf("version")}`;
|
|
15393
|
+
const ask = url.searchParams.get("ask");
|
|
15394
|
+
if (ask !== null)
|
|
15395
|
+
return `${document}/ask/${ask}`;
|
|
15396
|
+
const comment = url.searchParams.get("comment");
|
|
15397
|
+
if (comment !== null)
|
|
15398
|
+
return `${document}/comment/${comment}`;
|
|
15399
|
+
return document;
|
|
15400
|
+
}
|
|
15401
|
+
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";
|
|
15402
|
+
var ownerRequiredProblem = "issue is required; supply issue or set LEGION_ISSUE";
|
|
15163
15403
|
function toolSchema(tool) {
|
|
15164
15404
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
15165
15405
|
if (!spec)
|
|
15166
15406
|
throw new Error(`Unknown Dispatch tool: ${tool}`);
|
|
15167
15407
|
return dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
|
|
15168
15408
|
}
|
|
15169
|
-
async function resolveOwnerArguments(tool,
|
|
15409
|
+
async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, problems) {
|
|
15170
15410
|
if (issueFreeTools[tool] === true)
|
|
15171
|
-
return { args, ref: null, owner: null };
|
|
15172
|
-
const refArgument =
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15411
|
+
return { args: input, ref: null, owner: null };
|
|
15412
|
+
const refArgument = input.ref;
|
|
15413
|
+
let ref = null;
|
|
15414
|
+
let args = input;
|
|
15415
|
+
if (typeof refArgument === "string") {
|
|
15416
|
+
const refText = dispatchRefFromUrl(refArgument, serverUrl) ?? refArgument;
|
|
15417
|
+
if (refText !== refArgument)
|
|
15418
|
+
args = { ...input, ref: refText };
|
|
15419
|
+
ref = parseDispatchRef(refText);
|
|
15420
|
+
if (ref === null) {
|
|
15421
|
+
problems.push(tool === "dispatch_ask" && !refText.startsWith("dispatch://") ? "ref must be a dispatch:// reference" : refGrammarProblem);
|
|
15422
|
+
}
|
|
15423
|
+
}
|
|
15179
15424
|
const issueArgument = args.issue;
|
|
15180
15425
|
const projectArgument = args.project;
|
|
15181
15426
|
const artifactArgument = args.artifact;
|
|
15182
15427
|
const versionArgument = args.version;
|
|
15183
15428
|
if (issueArgument !== undefined && projectArgument !== undefined) {
|
|
15184
|
-
|
|
15429
|
+
problems.push("exactly one of issue and project is required");
|
|
15185
15430
|
}
|
|
15186
15431
|
if (typeof projectArgument === "string") {
|
|
15187
15432
|
if (!/^[A-Z][A-Z0-9]{1,9}$/.test(projectArgument)) {
|
|
15188
|
-
|
|
15433
|
+
problems.push("project must be a project key such as CORE");
|
|
15189
15434
|
}
|
|
15190
|
-
|
|
15191
|
-
|
|
15435
|
+
const refDocument = ref?.owner.kind === "project" ? ref.artifact ?? ref.id : undefined;
|
|
15436
|
+
if (ref?.owner.kind === "project" && (ref.owner.project !== projectArgument || artifactArgument !== undefined && artifactArgument !== refDocument)) {
|
|
15437
|
+
problems.push("project and ref must name the same document");
|
|
15192
15438
|
}
|
|
15193
15439
|
return {
|
|
15194
15440
|
args: {
|
|
15195
15441
|
...args,
|
|
15196
|
-
...artifactArgument === undefined &&
|
|
15442
|
+
...artifactArgument === undefined && refDocument !== undefined ? { artifact: refDocument } : {},
|
|
15197
15443
|
...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
|
|
15198
15444
|
},
|
|
15199
15445
|
ref,
|
|
@@ -15205,7 +15451,7 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
15205
15451
|
args: {
|
|
15206
15452
|
...args,
|
|
15207
15453
|
project: ref.owner.project,
|
|
15208
|
-
...artifactArgument === undefined ? { artifact: ref.id } : {},
|
|
15454
|
+
...artifactArgument === undefined ? { artifact: ref.artifact ?? ref.id } : {},
|
|
15209
15455
|
...versionArgument === undefined && ref.version !== undefined ? { version: ref.version } : {}
|
|
15210
15456
|
},
|
|
15211
15457
|
ref,
|
|
@@ -15226,20 +15472,25 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
15226
15472
|
};
|
|
15227
15473
|
}
|
|
15228
15474
|
const legionIssue = env.LEGION_ISSUE;
|
|
15229
|
-
if (!legionIssue)
|
|
15230
|
-
|
|
15475
|
+
if (!legionIssue) {
|
|
15476
|
+
problems.push(ownerRequiredProblem);
|
|
15477
|
+
return { args, ref, owner: null };
|
|
15478
|
+
}
|
|
15231
15479
|
if (nativeIssueKeyPattern.test(legionIssue) || externalIssueRefPattern.test(legionIssue)) {
|
|
15232
15480
|
const issue = canonicalExternalIssueRef(legionIssue);
|
|
15233
|
-
return { args: { ...args, issue }, ref
|
|
15481
|
+
return { args: { ...args, issue }, ref, owner: { kind: "issue", issue } };
|
|
15234
15482
|
}
|
|
15235
15483
|
if (!bareIssueNumberPattern.test(legionIssue)) {
|
|
15236
|
-
|
|
15484
|
+
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");
|
|
15485
|
+
return { args, ref, owner: null };
|
|
15237
15486
|
}
|
|
15238
15487
|
const repo = await resolveCwdRepo(cwd, exec);
|
|
15239
|
-
if (!repo)
|
|
15240
|
-
|
|
15488
|
+
if (!repo) {
|
|
15489
|
+
problems.push("issue is required; LEGION_ISSUE needs a GitHub repository in cwd");
|
|
15490
|
+
return { args, ref, owner: null };
|
|
15491
|
+
}
|
|
15241
15492
|
const issue = `${repo}#${legionIssue}`;
|
|
15242
|
-
return { args: { ...args, issue }, ref
|
|
15493
|
+
return { args: { ...args, issue }, ref, owner: { kind: "issue", issue } };
|
|
15243
15494
|
}
|
|
15244
15495
|
async function resolveArtifact(client, owner, artifactReference) {
|
|
15245
15496
|
if (owner.kind === "project") {
|
|
@@ -15270,7 +15521,8 @@ async function resolveArtifact(client, owner, artifactReference) {
|
|
|
15270
15521
|
const issue = await client.getIssue(owner.issue);
|
|
15271
15522
|
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);
|
|
15272
15523
|
if (!artifact) {
|
|
15273
|
-
|
|
15524
|
+
const slugs = issue.artifacts.map((candidate) => `${candidate.slug}${candidate.primary || candidate.id === issue.primary_artifact_id ? " (primary)" : ""}`);
|
|
15525
|
+
throw new Error(`artifact "${artifactReference ?? "spec"}" was not found on issue ${issue.key}; artifacts: ${slugs.length === 0 ? "none" : slugs.join(", ")}`);
|
|
15274
15526
|
}
|
|
15275
15527
|
return { owner, issue, artifact };
|
|
15276
15528
|
}
|
|
@@ -15327,7 +15579,7 @@ function issueSummary(issue, events, references) {
|
|
|
15327
15579
|
...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}`),
|
|
15328
15580
|
...typeof references === "string" || !references.truncated ? [] : ["- more references beyond 8 hops"],
|
|
15329
15581
|
"Events:",
|
|
15330
|
-
...events.length === 0 ? ["- none"] : events.map(
|
|
15582
|
+
...events.length === 0 ? ["- none"] : events.map(eventLine)
|
|
15331
15583
|
].join(`
|
|
15332
15584
|
`);
|
|
15333
15585
|
}
|
|
@@ -15335,10 +15587,41 @@ function logSummary(issue, events) {
|
|
|
15335
15587
|
return [
|
|
15336
15588
|
`Key: ${issue.key}`,
|
|
15337
15589
|
"Events:",
|
|
15338
|
-
...events.length === 0 ? ["- none"] : events.map(
|
|
15590
|
+
...events.length === 0 ? ["- none"] : events.map(eventLine)
|
|
15339
15591
|
].join(`
|
|
15340
15592
|
`);
|
|
15341
15593
|
}
|
|
15594
|
+
function eventHead(event) {
|
|
15595
|
+
switch (event.type) {
|
|
15596
|
+
case "ask.opened":
|
|
15597
|
+
case "ask.edited":
|
|
15598
|
+
case "ask.resolved":
|
|
15599
|
+
return textHead(event.payload.question);
|
|
15600
|
+
case "ask.answered":
|
|
15601
|
+
return `${textHead(event.payload.question)} -> ${textHead(askAnswerText(event.payload.answer))}`;
|
|
15602
|
+
case "comment.created":
|
|
15603
|
+
case "comment.edited":
|
|
15604
|
+
case "comment.resolved":
|
|
15605
|
+
case "comment.reopened":
|
|
15606
|
+
case "suggestion.accepted":
|
|
15607
|
+
case "suggestion.rejected":
|
|
15608
|
+
case "message.created":
|
|
15609
|
+
case "message.answered":
|
|
15610
|
+
return textHead(event.payload.body);
|
|
15611
|
+
case "artifact.version":
|
|
15612
|
+
return textHead(`${event.payload.name} v${event.payload.version.number}${event.payload.version.summary ? `: ${event.payload.version.summary}` : ""}`);
|
|
15613
|
+
case "issue.created":
|
|
15614
|
+
case "issue.updated":
|
|
15615
|
+
case "issue.closed":
|
|
15616
|
+
return `status ${event.payload.status}`;
|
|
15617
|
+
default:
|
|
15618
|
+
return;
|
|
15619
|
+
}
|
|
15620
|
+
}
|
|
15621
|
+
function eventLine(event) {
|
|
15622
|
+
const head = eventHead(event);
|
|
15623
|
+
return `- #${event.seq} ${event.type} \xB7 ${event.actor.kind} ${event.actor.id} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
|
|
15624
|
+
}
|
|
15342
15625
|
function childrenSummary(issue) {
|
|
15343
15626
|
return [
|
|
15344
15627
|
`Key: ${issue.key}`,
|
|
@@ -15473,19 +15756,29 @@ async function executeDispatchTool(input) {
|
|
|
15473
15756
|
throw error;
|
|
15474
15757
|
}
|
|
15475
15758
|
}, { preconnect: baseFetch.preconnect });
|
|
15759
|
+
const env = input.env ?? process.env;
|
|
15760
|
+
const exec = input.exec ?? defaultExec;
|
|
15761
|
+
const problems = [];
|
|
15762
|
+
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec, configUrl, problems);
|
|
15763
|
+
const ownerMissing = issueFreeTools[input.tool] !== true && ownerArguments.owner === null;
|
|
15764
|
+
const schema = toolSchema(input.tool);
|
|
15765
|
+
const parsed = schema.safeParse(ownerArguments.args, { reportInput: true });
|
|
15766
|
+
if (!parsed.success) {
|
|
15767
|
+
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")));
|
|
15768
|
+
problems.push(...formatZodIssues(issues, schema));
|
|
15769
|
+
}
|
|
15770
|
+
problems.push(...argumentProblems(input.tool, ownerArguments.args));
|
|
15771
|
+
if (problems.length > 0)
|
|
15772
|
+
throw new ToolInputError(input.tool, problems);
|
|
15476
15773
|
if (input.tool === "dispatch_open_asks") {
|
|
15477
15774
|
const sessionId = input.sessionId?.trim();
|
|
15478
15775
|
if (!sessionId)
|
|
15479
15776
|
throw new Error("host session id is required for dispatch_open_asks");
|
|
15480
|
-
toolSchema(input.tool).parse(input.args);
|
|
15481
15777
|
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
15482
15778
|
const response = await client.openAsks(sessionId);
|
|
15483
15779
|
return { text: formatOpenAsksSummary(response, configUrl), details: { ...response } };
|
|
15484
15780
|
}
|
|
15485
|
-
const
|
|
15486
|
-
const exec = input.exec ?? defaultExec;
|
|
15487
|
-
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
|
|
15488
|
-
const args = toolSchema(input.tool).parse(ownerArguments.args);
|
|
15781
|
+
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
15489
15782
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
15490
15783
|
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
15491
15784
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
@@ -15568,8 +15861,6 @@ async function executeDispatchTool(input) {
|
|
|
15568
15861
|
}
|
|
15569
15862
|
case "dispatch_resolve_ask": {
|
|
15570
15863
|
const kind = stringArg(args, "kind");
|
|
15571
|
-
if (kind !== "retracted" && kind !== "resolved")
|
|
15572
|
-
throw new Error("kind must be retracted or resolved");
|
|
15573
15864
|
const ask = await client.resolveAsk(stringArg(args, "ask"), {
|
|
15574
15865
|
kind,
|
|
15575
15866
|
reason: stringArg(args, "reason"),
|
|
@@ -15626,24 +15917,12 @@ async function executeDispatchTool(input) {
|
|
|
15626
15917
|
}
|
|
15627
15918
|
case "dispatch_comment": {
|
|
15628
15919
|
const artifactReference = optionalString(args, "artifact");
|
|
15629
|
-
if (optionalString(args, "quote") !== undefined && artifactReference === undefined) {
|
|
15630
|
-
throw new Error("artifact is required when quote is supplied");
|
|
15631
|
-
}
|
|
15632
15920
|
const owner = documentOwner();
|
|
15633
15921
|
const resolved = owner.kind === "project" || artifactReference === undefined ? owner.kind === "project" ? await resolveArtifact(client, owner, artifactReference) : undefined : await resolveArtifact(client, owner, artifactReference);
|
|
15634
15922
|
const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
|
|
15635
15923
|
const replyTo = optionalString(args, "reply_to");
|
|
15636
15924
|
const replyToAsk = optionalString(args, "reply_to_ask");
|
|
15637
|
-
if (replyTo !== undefined && replyToAsk !== undefined) {
|
|
15638
|
-
throw new Error("reply_to and reply_to_ask cannot both be set");
|
|
15639
|
-
}
|
|
15640
15925
|
const requestedTurn = optionalString(args, "turn");
|
|
15641
|
-
if (requestedTurn !== undefined && replyToAsk === undefined) {
|
|
15642
|
-
throw new Error("turn requires reply_to_ask");
|
|
15643
|
-
}
|
|
15644
|
-
if (requestedTurn !== undefined && requestedTurn !== "agent" && requestedTurn !== "human") {
|
|
15645
|
-
throw new Error("turn must be agent or human");
|
|
15646
|
-
}
|
|
15647
15926
|
const commentInput = {
|
|
15648
15927
|
body: stringArg(args, "body"),
|
|
15649
15928
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
@@ -15797,17 +16076,21 @@ ${trailer.join(`
|
|
|
15797
16076
|
}
|
|
15798
16077
|
case "dispatch_read": {
|
|
15799
16078
|
if (ownerArguments.ref?.kind === "ask") {
|
|
15800
|
-
const
|
|
16079
|
+
const ref = ownerArguments.ref;
|
|
16080
|
+
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"));
|
|
16081
|
+
const askRead = await client.getAsk(id);
|
|
15801
16082
|
return {
|
|
15802
16083
|
text: askSummary(askRead),
|
|
15803
|
-
details:
|
|
16084
|
+
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue }
|
|
15804
16085
|
};
|
|
15805
16086
|
}
|
|
15806
16087
|
if (ownerArguments.ref?.kind === "comment") {
|
|
15807
|
-
const
|
|
16088
|
+
const ref = ownerArguments.ref;
|
|
16089
|
+
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));
|
|
16090
|
+
const comment = await client.getComment(id);
|
|
15808
16091
|
return {
|
|
15809
16092
|
text: commentSummary(comment),
|
|
15810
|
-
details:
|
|
16093
|
+
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: comment.comment.issue_key }
|
|
15811
16094
|
};
|
|
15812
16095
|
}
|
|
15813
16096
|
if (ownerArguments.ref?.kind === "message") {
|
|
@@ -15896,8 +16179,8 @@ function messageMetadataShape(schema) {
|
|
|
15896
16179
|
return {
|
|
15897
16180
|
in_reply_to: schema.string().optional(),
|
|
15898
16181
|
supersedes: schema.string().optional(),
|
|
15899
|
-
urgency: schema.enum(URGENCY_VALUES).optional(),
|
|
15900
|
-
expects_reply: schema.enum(EXPECTS_REPLY_VALUES).optional(),
|
|
16182
|
+
urgency: schema.enum(URGENCY_VALUES).describe("low | med | high | blocking").optional(),
|
|
16183
|
+
expects_reply: schema.enum(EXPECTS_REPLY_VALUES).describe("none | optional | required").optional(),
|
|
15901
16184
|
expires_at: schema.number({ int: true }).optional()
|
|
15902
16185
|
};
|
|
15903
16186
|
}
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -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
|
|
package/skills/envoy/SKILL.md
CHANGED
|
@@ -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.
|