@sjawhar/pi-legion-envoy 1.48.0 → 1.49.1
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 +271 -159
- package/dist/legion.js +36 -30
- package/dist/skills/dispatch/SKILL.md +15 -0
- package/dist/skills/legion-architect/SKILL.md +4 -0
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29649,6 +29649,7 @@ function date4(params) {
|
|
|
29649
29649
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
29650
29650
|
config(en_default());
|
|
29651
29651
|
// ../contracts/src/dispatch-api.ts
|
|
29652
|
+
var ASK_TURNS = ["human", "agent"];
|
|
29652
29653
|
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
29653
29654
|
var DispatchEventSchema = object({
|
|
29654
29655
|
issue_key: string2().nullable(),
|
|
@@ -29729,8 +29730,8 @@ var CommentEventPayloadSchema = object({
|
|
|
29729
29730
|
ask_id: string2().nullish(),
|
|
29730
29731
|
ask_question: string2().optional(),
|
|
29731
29732
|
ask_state: _enum2(["open", "answered", "resolved"]).optional(),
|
|
29732
|
-
ask_waiting_on: _enum2(
|
|
29733
|
-
turn: _enum2(
|
|
29733
|
+
ask_waiting_on: _enum2(ASK_TURNS).optional(),
|
|
29734
|
+
turn: _enum2(ASK_TURNS).nullish(),
|
|
29734
29735
|
anchor: object({ block_id: string2().nullable().optional(), quote: string2().optional() }).nullish(),
|
|
29735
29736
|
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
29736
29737
|
author: object({ kind: string2(), id: string2() }).optional(),
|
|
@@ -29853,16 +29854,22 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
|
|
|
29853
29854
|
message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29854
29855
|
};
|
|
29855
29856
|
}
|
|
29856
|
-
var
|
|
29857
|
-
|
|
29858
|
-
|
|
29859
|
-
|
|
29860
|
-
|
|
29861
|
-
|
|
29862
|
-
|
|
29863
|
-
|
|
29864
|
-
|
|
29865
|
-
|
|
29857
|
+
var commentOwner = documentOwnerValidation(true);
|
|
29858
|
+
var commentValidation = {
|
|
29859
|
+
check: (value) => {
|
|
29860
|
+
const input = value;
|
|
29861
|
+
return commentOwner.check(value) && (input.turn === undefined || typeof input.reply_to_ask === "string");
|
|
29862
|
+
},
|
|
29863
|
+
message: `${commentOwner.message} turn requires reply_to_ask.`
|
|
29864
|
+
};
|
|
29865
|
+
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
29866
|
+
function componentsArgument(z) {
|
|
29867
|
+
return z.object({
|
|
29868
|
+
mode: z.enum(ISSUE_COMPONENTS_MODES).describe("inherit: take the parent chain's attachment (the default; deletes this issue's own). explicit: attach to ids. none: not architectural work, with reason."),
|
|
29869
|
+
ids: z.array(z.string({ min: 1 }), { min: 1, max: 50 }).describe("For mode explicit: bare component ids from the project's architecture model (web, dispatch-server), not external ones.").optional(),
|
|
29870
|
+
reason: z.string({ min: 1 }).describe("For mode none: why this issue is not architectural (process, hiring, ops).").optional()
|
|
29871
|
+
}).describe("Attach the issue to architecture components. Attach the root before decomposing it; children inherit unless they choose.");
|
|
29872
|
+
}
|
|
29866
29873
|
var SPEC_SECTIONS = [
|
|
29867
29874
|
"Summary",
|
|
29868
29875
|
"Decisions needed",
|
|
@@ -29876,6 +29883,7 @@ var SPEC_SECTIONS = [
|
|
|
29876
29883
|
];
|
|
29877
29884
|
var SPEC_WRITING_GUIDANCE = `When writing a spec, use these sections in order: ${SPEC_SECTIONS.join(", ")}. ` + "Write for a reader who has not seen the code: plain sentences, every identifier expanded on " + "first use, no coined shorthand; see skills/dispatch Writing for the human and Writing a spec.";
|
|
29878
29885
|
var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
29886
|
+
var ASK_QUESTION_MAX = 800;
|
|
29879
29887
|
var ISSUE_STATUSES = [
|
|
29880
29888
|
"triage",
|
|
29881
29889
|
"icebox",
|
|
@@ -29887,6 +29895,9 @@ var ISSUE_STATUSES = [
|
|
|
29887
29895
|
"retro",
|
|
29888
29896
|
"done"
|
|
29889
29897
|
];
|
|
29898
|
+
function isIssueStatus(value) {
|
|
29899
|
+
return ISSUE_STATUSES.includes(value);
|
|
29900
|
+
}
|
|
29890
29901
|
var DOC_EDIT_OPS = ["replace", "delete", "insert", "retype", "move"];
|
|
29891
29902
|
var dispatchToolSpecs = [
|
|
29892
29903
|
{
|
|
@@ -29901,12 +29912,13 @@ var dispatchToolSpecs = [
|
|
|
29901
29912
|
spec: z.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_GUIDANCE}`).optional(),
|
|
29902
29913
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
|
|
29903
29914
|
priority: z.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority: P0 is highest and P3 is lowest.").optional(),
|
|
29904
|
-
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional()
|
|
29915
|
+
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional(),
|
|
29916
|
+
components: componentsArgument(z).optional()
|
|
29905
29917
|
})
|
|
29906
29918
|
},
|
|
29907
29919
|
{
|
|
29908
29920
|
name: "dispatch_issue_update",
|
|
29909
|
-
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route,
|
|
29921
|
+
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route, set or clear its parent, " + "or attach it to architecture components. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. external_links are " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link. components replaces the issue's own attachment and is allowed on a " + "closed issue. Priority is the human's and is not settable here. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
29910
29922
|
arguments: (z) => ({
|
|
29911
29923
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29912
29924
|
status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
@@ -29914,26 +29926,27 @@ var dispatchToolSpecs = [
|
|
|
29914
29926
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Replacement label set, at most 20 labels of up to 40 characters; replaces every existing label.").optional(),
|
|
29915
29927
|
external_links: z.array(z.string({ min: 1 })).describe("URLs to link; merged into the issue's existing external links by URL.").optional(),
|
|
29916
29928
|
route: z.string().describe("Route the issue to role:<name> or session:<id>; an empty string clears it.").optional(),
|
|
29917
|
-
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional()
|
|
29929
|
+
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional(),
|
|
29930
|
+
components: componentsArgument(z).optional()
|
|
29918
29931
|
}),
|
|
29919
29932
|
validation: {
|
|
29920
29933
|
check: (value) => {
|
|
29921
29934
|
const input = value;
|
|
29922
|
-
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string";
|
|
29935
|
+
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string" || typeof input.components === "object" && input.components !== null;
|
|
29923
29936
|
},
|
|
29924
|
-
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, or
|
|
29937
|
+
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, parent, or components."
|
|
29925
29938
|
},
|
|
29926
29939
|
strict: true
|
|
29927
29940
|
},
|
|
29928
29941
|
{
|
|
29929
29942
|
name: "dispatch_ask",
|
|
29930
|
-
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " +
|
|
29943
|
+
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
29931
29944
|
arguments: (z) => ({
|
|
29932
29945
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29933
29946
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29934
29947
|
artifact: z.string().describe("Project document artifact id, slug, or filename.").optional(),
|
|
29935
29948
|
ref: z.string().describe("Optional dispatch:// reference (issue, document, message, or ask); appended to the question and rendered as a link.").optional(),
|
|
29936
|
-
question: z.string({ max:
|
|
29949
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Decision question, at most ${ASK_QUESTION_MAX} characters.`),
|
|
29937
29950
|
options: z.array(z.object({
|
|
29938
29951
|
label: z.string().describe("Selectable option label."),
|
|
29939
29952
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -29953,7 +29966,7 @@ var dispatchToolSpecs = [
|
|
|
29953
29966
|
description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited.",
|
|
29954
29967
|
arguments: (z) => ({
|
|
29955
29968
|
ask: z.string().describe("Ask id to edit."),
|
|
29956
|
-
question: z.string({ max:
|
|
29969
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
|
|
29957
29970
|
options: z.array(z.object({
|
|
29958
29971
|
label: z.string().describe("Selectable option label."),
|
|
29959
29972
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -31184,9 +31197,9 @@ function resolveOptions(options) {
|
|
|
31184
31197
|
|
|
31185
31198
|
// ../envoy-client/src/ask-answer.ts
|
|
31186
31199
|
var HEAD_LENGTH = 120;
|
|
31187
|
-
function textHead(text
|
|
31200
|
+
function textHead(text) {
|
|
31188
31201
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
31189
|
-
return flat.length >
|
|
31202
|
+
return flat.length > HEAD_LENGTH ? `${flat.slice(0, HEAD_LENGTH)}\u2026` : flat;
|
|
31190
31203
|
}
|
|
31191
31204
|
function askAnswerText(answer) {
|
|
31192
31205
|
if (answer === null || answer === undefined)
|
|
@@ -31200,6 +31213,29 @@ function askAnswerText(answer) {
|
|
|
31200
31213
|
return `${selected} - ${text}`;
|
|
31201
31214
|
}
|
|
31202
31215
|
|
|
31216
|
+
// ../envoy-client/src/dispatch-owner.ts
|
|
31217
|
+
function documentLabel(project, slug) {
|
|
31218
|
+
return `${project}/${slug}`;
|
|
31219
|
+
}
|
|
31220
|
+
function issueTopic(key) {
|
|
31221
|
+
return { label: key, topic: dispatchIssueSubject(key, ">") };
|
|
31222
|
+
}
|
|
31223
|
+
function documentTopicOf(project, slug) {
|
|
31224
|
+
return {
|
|
31225
|
+
label: documentLabel(project, slug),
|
|
31226
|
+
topic: dispatchDocumentSubject(project, slug, ">")
|
|
31227
|
+
};
|
|
31228
|
+
}
|
|
31229
|
+
function dispatchIssueRef(key) {
|
|
31230
|
+
return `dispatch://${key}`;
|
|
31231
|
+
}
|
|
31232
|
+
function dispatchDocumentRef(owner, slug) {
|
|
31233
|
+
return `dispatch://${owner}/artifact/${slug}`;
|
|
31234
|
+
}
|
|
31235
|
+
function dispatchChildRef(ownerRef, kind, id) {
|
|
31236
|
+
return `${ownerRef}/${kind}/${id}`;
|
|
31237
|
+
}
|
|
31238
|
+
|
|
31203
31239
|
// ../envoy-client/src/delivery.ts
|
|
31204
31240
|
var KNOWN_SOURCES = EnvelopeSchema.shape.source.enum;
|
|
31205
31241
|
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;
|
|
@@ -31237,6 +31273,31 @@ var TolerantInboundEnvelopeSchema = object({
|
|
|
31237
31273
|
source: InboundEnvelopeSchema.shape.source.optional().catch(undefined),
|
|
31238
31274
|
sender: object(tolerantShape(InboundSenderSchema.shape)).optional().catch(undefined)
|
|
31239
31275
|
}).passthrough();
|
|
31276
|
+
async function postDeliveryReply(config, sessionId, delivery, result) {
|
|
31277
|
+
const response = await fetch(`${config.url}/api/v1/messages/${delivery.messageID}/reply`, {
|
|
31278
|
+
method: "POST",
|
|
31279
|
+
headers: { Authorization: `Bearer ${config.token}`, "Content-Type": "application/json" },
|
|
31280
|
+
body: JSON.stringify({
|
|
31281
|
+
actor: { kind: "session", id: sessionId },
|
|
31282
|
+
attempt: delivery.attempt,
|
|
31283
|
+
...result
|
|
31284
|
+
})
|
|
31285
|
+
});
|
|
31286
|
+
if (!response.ok) {
|
|
31287
|
+
throw new Error(`Dispatch reply failed: ${response.status} ${await response.text()}`);
|
|
31288
|
+
}
|
|
31289
|
+
}
|
|
31290
|
+
function expectsLaneReceipt(frame) {
|
|
31291
|
+
return frame.reply !== undefined && frame.reply !== "" && frame.subject === frame.directSubject && frame.envelopeTopic !== undefined && frame.envelopeTopic !== frame.directSubject;
|
|
31292
|
+
}
|
|
31293
|
+
function rememberBounded(seen, key, limit) {
|
|
31294
|
+
seen.add(key);
|
|
31295
|
+
if (seen.size > limit) {
|
|
31296
|
+
const oldest = seen.values().next();
|
|
31297
|
+
if (!oldest.done)
|
|
31298
|
+
seen.delete(oldest.value);
|
|
31299
|
+
}
|
|
31300
|
+
}
|
|
31240
31301
|
var DispatchDeliveryRequestSchema = object({
|
|
31241
31302
|
attempt: number2().int().positive(),
|
|
31242
31303
|
mode: _enum2(DELIVERY_CAPABILITIES)
|
|
@@ -31303,23 +31364,20 @@ function dispatchOwner(event, topic) {
|
|
|
31303
31364
|
return `${document.project} / ${document.slug}`;
|
|
31304
31365
|
return event.artifact_id ?? "unknown";
|
|
31305
31366
|
}
|
|
31306
|
-
function dispatchCommentReplyWith(event, topic) {
|
|
31307
|
-
if (
|
|
31367
|
+
function dispatchCommentReplyWith(event, topic, comment) {
|
|
31368
|
+
if (comment === undefined || comment.id === undefined)
|
|
31308
31369
|
return;
|
|
31309
|
-
const
|
|
31310
|
-
if (!comment.success || comment.data.id === undefined)
|
|
31311
|
-
return;
|
|
31312
|
-
const reply = comment.data.ask_id;
|
|
31370
|
+
const reply = comment.ask_id;
|
|
31313
31371
|
const thread = reply === undefined || reply === null || reply === "" ? "reply_to" : "reply_to_ask";
|
|
31314
|
-
const threadID = thread === "reply_to" ? comment.
|
|
31372
|
+
const threadID = thread === "reply_to" ? comment.id : String(reply);
|
|
31315
31373
|
if (event.issue_key !== null) {
|
|
31316
31374
|
return {
|
|
31317
31375
|
tool: "dispatch_comment",
|
|
31318
31376
|
args: { issue: event.issue_key, [thread]: threadID, body: "..." }
|
|
31319
31377
|
};
|
|
31320
31378
|
}
|
|
31321
|
-
let project = comment.
|
|
31322
|
-
let artifact = comment.
|
|
31379
|
+
let project = comment.project_key;
|
|
31380
|
+
let artifact = comment.artifact_slug;
|
|
31323
31381
|
if (project === undefined || project === "" || artifact === undefined || artifact === "") {
|
|
31324
31382
|
[project, artifact] = dispatchOwner(event, topic).split(" / ", 2);
|
|
31325
31383
|
}
|
|
@@ -31348,13 +31406,10 @@ function dispatchPayload(event) {
|
|
|
31348
31406
|
const parsed = schema.safeParse(event.payload);
|
|
31349
31407
|
return parsed.success ? parsed.data : event.payload;
|
|
31350
31408
|
}
|
|
31351
|
-
function dispatchAskQuestion(
|
|
31352
|
-
|
|
31353
|
-
return;
|
|
31354
|
-
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31355
|
-
return parsed.success && parsed.data.ask_question !== undefined && parsed.data.ask_question !== "" ? textHead(parsed.data.ask_question) : undefined;
|
|
31409
|
+
function dispatchAskQuestion(comment) {
|
|
31410
|
+
return comment?.ask_question !== undefined && comment.ask_question !== "" ? textHead(comment.ask_question) : undefined;
|
|
31356
31411
|
}
|
|
31357
|
-
function dispatchReplyRef(event, topic, inReplyTo) {
|
|
31412
|
+
function dispatchReplyRef(event, topic, inReplyTo, comment) {
|
|
31358
31413
|
let kind;
|
|
31359
31414
|
let document = topicDocument(topic);
|
|
31360
31415
|
if (event.type.startsWith("ask.")) {
|
|
@@ -31362,23 +31417,23 @@ function dispatchReplyRef(event, topic, inReplyTo) {
|
|
|
31362
31417
|
} else if (event.type.startsWith("message.")) {
|
|
31363
31418
|
kind = "message";
|
|
31364
31419
|
} else if (event.type === "comment.created") {
|
|
31365
|
-
|
|
31366
|
-
if (!comment.success || comment.data.ask_id === undefined || comment.data.ask_id === null) {
|
|
31420
|
+
if (comment === undefined || comment.ask_id === undefined || comment.ask_id === null) {
|
|
31367
31421
|
return inReplyTo;
|
|
31368
31422
|
}
|
|
31369
31423
|
kind = "ask";
|
|
31370
|
-
const { project_key: project, artifact_slug: slug } = comment
|
|
31424
|
+
const { project_key: project, artifact_slug: slug } = comment;
|
|
31371
31425
|
if (project !== undefined && project !== "" && slug !== undefined && slug !== "") {
|
|
31372
31426
|
document = { project, slug };
|
|
31373
31427
|
}
|
|
31374
31428
|
} else {
|
|
31375
31429
|
return inReplyTo;
|
|
31376
31430
|
}
|
|
31377
|
-
if (event.issue_key !== null)
|
|
31378
|
-
return
|
|
31431
|
+
if (event.issue_key !== null) {
|
|
31432
|
+
return dispatchChildRef(dispatchIssueRef(event.issue_key), kind, inReplyTo);
|
|
31433
|
+
}
|
|
31379
31434
|
if (document === undefined)
|
|
31380
31435
|
return inReplyTo;
|
|
31381
|
-
return
|
|
31436
|
+
return dispatchChildRef(dispatchDocumentRef(document.project, document.slug), kind, inReplyTo);
|
|
31382
31437
|
}
|
|
31383
31438
|
function isObject2(value) {
|
|
31384
31439
|
return typeof value === "object" && value !== null;
|
|
@@ -31390,16 +31445,16 @@ function parseDispatchFrame(rawPayload) {
|
|
|
31390
31445
|
} catch {
|
|
31391
31446
|
return { raw: rawPayload };
|
|
31392
31447
|
}
|
|
31393
|
-
const targeted = DispatchTargetedFrameSchema.safeParse(value);
|
|
31394
|
-
if (targeted.success) {
|
|
31395
|
-
const wireFrame = value;
|
|
31396
|
-
return {
|
|
31397
|
-
event: targeted.data.event,
|
|
31398
|
-
notify: wireFrame.event.notify,
|
|
31399
|
-
delivery: targeted.data.delivery
|
|
31400
|
-
};
|
|
31401
|
-
}
|
|
31402
31448
|
if (isObject2(value) && "delivery" in value) {
|
|
31449
|
+
const targeted = DispatchTargetedFrameSchema.safeParse(value);
|
|
31450
|
+
if (targeted.success) {
|
|
31451
|
+
const wireFrame = value;
|
|
31452
|
+
return {
|
|
31453
|
+
event: targeted.data.event,
|
|
31454
|
+
notify: wireFrame.event.notify,
|
|
31455
|
+
delivery: targeted.data.delivery
|
|
31456
|
+
};
|
|
31457
|
+
}
|
|
31403
31458
|
const recoverable = RecoverableDispatchDeliveryFailureSchema.safeParse(value);
|
|
31404
31459
|
if (recoverable.success) {
|
|
31405
31460
|
return {
|
|
@@ -31471,12 +31526,13 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31471
31526
|
let askAuthor;
|
|
31472
31527
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31473
31528
|
if (envelope.source === "dispatch") {
|
|
31529
|
+
const topic = subject ?? envelope.topic;
|
|
31474
31530
|
if (envelope.payload === undefined) {
|
|
31475
31531
|
dispatchIssue = "payload";
|
|
31476
31532
|
} else {
|
|
31477
31533
|
const frame = parseDispatchFrame(envelope.payload);
|
|
31478
31534
|
if ("event" in frame) {
|
|
31479
|
-
if (frame.notify === false &&
|
|
31535
|
+
if (frame.notify === false && topic?.startsWith(DISPATCH_TOPIC_PREFIX) === true) {
|
|
31480
31536
|
return { skip: true, content: "", envelope };
|
|
31481
31537
|
}
|
|
31482
31538
|
if (frame.event.actor.kind === "session" && frame.event.actor.id === sessionID) {
|
|
@@ -31490,7 +31546,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31490
31546
|
const who = removed.data.by?.id ?? "someone";
|
|
31491
31547
|
return {
|
|
31492
31548
|
skip: false,
|
|
31493
|
-
content: `Unsubscribed from ${dispatchOwner(frame.event,
|
|
31549
|
+
content: `Unsubscribed from ${dispatchOwner(frame.event, topic)} by ${who}`,
|
|
31494
31550
|
envelope
|
|
31495
31551
|
};
|
|
31496
31552
|
}
|
|
@@ -31500,7 +31556,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31500
31556
|
return { skip: true, content: "", envelope };
|
|
31501
31557
|
}
|
|
31502
31558
|
const who = follower.data.by?.id ?? "someone";
|
|
31503
|
-
const owner = dispatchOwner(frame.event,
|
|
31559
|
+
const owner = dispatchOwner(frame.event, topic);
|
|
31504
31560
|
const ask = follower.data.ask_id ?? "?";
|
|
31505
31561
|
return {
|
|
31506
31562
|
skip: false,
|
|
@@ -31508,8 +31564,10 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31508
31564
|
envelope
|
|
31509
31565
|
};
|
|
31510
31566
|
}
|
|
31567
|
+
const comment = frame.event.type === "comment.created" ? CommentEventPayloadSchema.safeParse(frame.event.payload) : undefined;
|
|
31568
|
+
const commentPayload = comment?.success === true ? comment.data : undefined;
|
|
31511
31569
|
const answered = dispatchAskAnswer(frame.event);
|
|
31512
|
-
const question = answered?.question ?? dispatchAskQuestion(
|
|
31570
|
+
const question = answered?.question ?? dispatchAskQuestion(commentPayload);
|
|
31513
31571
|
if (frame.event.type.startsWith("ask.")) {
|
|
31514
31572
|
const asked = AskAuthorPayloadSchema.safeParse(frame.event.payload);
|
|
31515
31573
|
if (asked.success && asked.data.author.kind === "session") {
|
|
@@ -31517,9 +31575,9 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31517
31575
|
}
|
|
31518
31576
|
}
|
|
31519
31577
|
if (inReplyTo !== undefined) {
|
|
31520
|
-
inReplyTo = dispatchReplyRef(frame.event,
|
|
31578
|
+
inReplyTo = dispatchReplyRef(frame.event, topic, inReplyTo, commentPayload);
|
|
31521
31579
|
}
|
|
31522
|
-
dispatchReply = dispatchCommentReplyWith(frame.event,
|
|
31580
|
+
dispatchReply = dispatchCommentReplyWith(frame.event, topic, commentPayload);
|
|
31523
31581
|
if (frame.event.type === "message.created") {
|
|
31524
31582
|
const message = MessageEventPayloadSchema.safeParse(frame.event.payload);
|
|
31525
31583
|
const requested = DispatchDeliveryRequestSchema.safeParse(frame.delivery);
|
|
@@ -31540,11 +31598,9 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31540
31598
|
}
|
|
31541
31599
|
}
|
|
31542
31600
|
dispatchEvent = {
|
|
31543
|
-
owner: dispatchOwner(frame.event,
|
|
31601
|
+
owner: dispatchOwner(frame.event, topic),
|
|
31544
31602
|
...frame.event.issue_key === null ? {
|
|
31545
|
-
...
|
|
31546
|
-
document: dispatchOwner(frame.event, subject ?? envelope.topic).replace(" / ", "/")
|
|
31547
|
-
} : {},
|
|
31603
|
+
...topic?.startsWith(DISPATCH_DOCUMENT_TOPIC_PREFIX) === true ? { document: dispatchOwner(frame.event, topic).replace(" / ", "/") } : {},
|
|
31548
31604
|
...frame.event.artifact_id === undefined || frame.event.artifact_id === null ? {} : { artifact_id: frame.event.artifact_id }
|
|
31549
31605
|
} : { issue_key: frame.event.issue_key },
|
|
31550
31606
|
type: frame.event.type,
|
|
@@ -31572,14 +31628,16 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31572
31628
|
const payloadSummary = envelope.payload_summary ?? "unknown";
|
|
31573
31629
|
const message = !dispatchRendered && envelope.payload !== undefined && envelope.payload !== envelope.payload_summary ? parsedPayload : undefined;
|
|
31574
31630
|
const summaryIsHead = typeof message === "string" && envelope.payload_summary !== undefined && message.startsWith(envelope.payload_summary.endsWith("\u2026") ? envelope.payload_summary.slice(0, -1) : envelope.payload_summary);
|
|
31575
|
-
const body = `${envelope.payload_summary ?? ""}
|
|
31576
|
-
${envelope.payload ?? ""}`;
|
|
31577
31631
|
let foreignSession;
|
|
31578
|
-
for (const
|
|
31579
|
-
|
|
31580
|
-
|
|
31581
|
-
|
|
31632
|
+
for (const part of [envelope.payload_summary ?? "", envelope.payload ?? ""]) {
|
|
31633
|
+
for (const match of part.matchAll(FOREIGN_SESSION_ID)) {
|
|
31634
|
+
if (match[0] !== envelope.source_session && match[0] !== sessionID && match[0] !== askAuthor) {
|
|
31635
|
+
foreignSession = match[0];
|
|
31636
|
+
break;
|
|
31637
|
+
}
|
|
31582
31638
|
}
|
|
31639
|
+
if (foreignSession !== undefined)
|
|
31640
|
+
break;
|
|
31583
31641
|
}
|
|
31584
31642
|
const role = envelope.sender?.roles?.[0];
|
|
31585
31643
|
const reply = dispatchReply ?? replyWith(envelope);
|
|
@@ -32290,14 +32348,8 @@ function formatZodIssues(issues, schema) {
|
|
|
32290
32348
|
}
|
|
32291
32349
|
|
|
32292
32350
|
// ../envoy-client/src/dispatch-execute.ts
|
|
32293
|
-
function issueTopic(key) {
|
|
32294
|
-
return { label: key, topic: dispatchIssueSubject(key, ">") };
|
|
32295
|
-
}
|
|
32296
32351
|
function documentTopic(artifact) {
|
|
32297
|
-
return
|
|
32298
|
-
label: `${artifact.project}/${artifact.slug}`,
|
|
32299
|
-
topic: dispatchDocumentSubject(artifact.project, artifact.slug, ">")
|
|
32300
|
-
};
|
|
32352
|
+
return documentTopicOf(artifact.project, artifact.slug);
|
|
32301
32353
|
}
|
|
32302
32354
|
function resolvedTopic(resolved) {
|
|
32303
32355
|
if (resolved.owner.kind === "project")
|
|
@@ -32316,7 +32368,7 @@ function documentResultDetails(artifact) {
|
|
|
32316
32368
|
return {
|
|
32317
32369
|
project: artifact.project,
|
|
32318
32370
|
artifact: artifact.id,
|
|
32319
|
-
document:
|
|
32371
|
+
document: documentLabel(artifact.project, artifact.slug)
|
|
32320
32372
|
};
|
|
32321
32373
|
}
|
|
32322
32374
|
function writeResultDetails(resolved, fields) {
|
|
@@ -32327,15 +32379,15 @@ function writeResultDetails(resolved, fields) {
|
|
|
32327
32379
|
throw new Error("issue document is missing its issue");
|
|
32328
32380
|
return { issue: resolved.issue.key, ...fields };
|
|
32329
32381
|
}
|
|
32330
|
-
async function askOwnerDetails(client, ask,
|
|
32382
|
+
async function askOwnerDetails(client, ask, artifact) {
|
|
32331
32383
|
if (ask.issue_key !== null) {
|
|
32332
32384
|
return { issue: ask.issue_key, ask: ask.id };
|
|
32333
32385
|
}
|
|
32334
32386
|
if (ask.artifact_id === undefined || ask.artifact_id === null) {
|
|
32335
32387
|
throw new Error("document ask is missing its artifact ID");
|
|
32336
32388
|
}
|
|
32337
|
-
const
|
|
32338
|
-
return { ...documentResultDetails(
|
|
32389
|
+
const owner = artifact ?? await client.getArtifact(ask.artifact_id);
|
|
32390
|
+
return { ...documentResultDetails(owner), ask: ask.id };
|
|
32339
32391
|
}
|
|
32340
32392
|
async function commentOwnerResult(client, comment, artifact) {
|
|
32341
32393
|
if (comment.issue_key !== null) {
|
|
@@ -32346,12 +32398,12 @@ async function commentOwnerResult(client, comment, artifact) {
|
|
|
32346
32398
|
}
|
|
32347
32399
|
const owner = artifact ?? await client.getArtifact(comment.artifact_id);
|
|
32348
32400
|
return {
|
|
32349
|
-
label:
|
|
32401
|
+
label: documentLabel(owner.project, owner.slug),
|
|
32350
32402
|
details: { ...documentResultDetails(owner), comment: comment.id }
|
|
32351
32403
|
};
|
|
32352
32404
|
}
|
|
32353
|
-
async function followedAskDetails(client, ask,
|
|
32354
|
-
return { ...await askOwnerDetails(client, ask,
|
|
32405
|
+
async function followedAskDetails(client, ask, artifact) {
|
|
32406
|
+
return { ...await askOwnerDetails(client, ask, artifact), follows: { ask: ask.id } };
|
|
32355
32407
|
}
|
|
32356
32408
|
var nativeIssueKeyPattern = /^[A-Z][A-Z0-9]{1,9}-[0-9]+$/;
|
|
32357
32409
|
var externalIssueRefPattern = /^([^/\s]+)\/([^/\s#]+)#([1-9][0-9]*)$/;
|
|
@@ -32390,6 +32442,27 @@ function optionalNumber(args, name) {
|
|
|
32390
32442
|
const value = args[name];
|
|
32391
32443
|
return typeof value === "number" ? value : undefined;
|
|
32392
32444
|
}
|
|
32445
|
+
function optionalComponents(args, name) {
|
|
32446
|
+
const value = args[name];
|
|
32447
|
+
if (typeof value !== "object" || value === null)
|
|
32448
|
+
return;
|
|
32449
|
+
const input = value;
|
|
32450
|
+
return {
|
|
32451
|
+
mode: input.mode,
|
|
32452
|
+
...input.ids === undefined ? {} : { ids: input.ids },
|
|
32453
|
+
...input.reason === undefined ? {} : { reason: input.reason }
|
|
32454
|
+
};
|
|
32455
|
+
}
|
|
32456
|
+
function componentsChange(input, after) {
|
|
32457
|
+
switch (input.mode) {
|
|
32458
|
+
case "explicit":
|
|
32459
|
+
return `components -> explicit [${after.ids.join(", ")}]`;
|
|
32460
|
+
case "none":
|
|
32461
|
+
return `components -> none (${after.reason ?? input.reason ?? ""})`;
|
|
32462
|
+
case "inherit":
|
|
32463
|
+
return "components -> inherit";
|
|
32464
|
+
}
|
|
32465
|
+
}
|
|
32393
32466
|
function isDuplicateCandidate(value) {
|
|
32394
32467
|
if (typeof value !== "object" || value === null)
|
|
32395
32468
|
return false;
|
|
@@ -32405,7 +32478,7 @@ function searchResultLine(result, baseUrl) {
|
|
|
32405
32478
|
const href = new URL(result.href, baseUrl).toString();
|
|
32406
32479
|
const { owner } = result;
|
|
32407
32480
|
if (owner.kind === "document") {
|
|
32408
|
-
const reference =
|
|
32481
|
+
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
32409
32482
|
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
32410
32483
|
}
|
|
32411
32484
|
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
@@ -32416,7 +32489,6 @@ function askUrgency(args) {
|
|
|
32416
32489
|
const value = args.urgency;
|
|
32417
32490
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
32418
32491
|
}
|
|
32419
|
-
var maxAskQuestion16 = 800;
|
|
32420
32492
|
function questionWithRef(question, ref) {
|
|
32421
32493
|
return ref === undefined || question.includes(ref) ? question : `${question}
|
|
32422
32494
|
|
|
@@ -32426,7 +32498,7 @@ function askQuestionWithRef(args) {
|
|
|
32426
32498
|
return questionWithRef(stringArg(args, "question"), optionalString(args, "ref"));
|
|
32427
32499
|
}
|
|
32428
32500
|
function askQuestionProblem(withRef) {
|
|
32429
|
-
return withRef.length >
|
|
32501
|
+
return withRef.length > ASK_QUESTION_MAX ? `question plus ref ${overCapMessage(withRef.length, ASK_QUESTION_MAX)}; shorten the question or drop the ref` : undefined;
|
|
32430
32502
|
}
|
|
32431
32503
|
function parseDispatchRef(ref) {
|
|
32432
32504
|
const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
|
|
@@ -32481,6 +32553,10 @@ function parseDispatchRef(ref) {
|
|
|
32481
32553
|
return { owner, kind: "message", id: message };
|
|
32482
32554
|
return { owner, kind: "issue", id: issue };
|
|
32483
32555
|
}
|
|
32556
|
+
function refTarget(ref, kind, id) {
|
|
32557
|
+
const ownerRef = ref.owner.kind === "issue" ? dispatchIssueRef(ref.owner.issue) : dispatchDocumentRef(ref.owner.project, `${ref.artifact}`);
|
|
32558
|
+
return dispatchChildRef(ownerRef, kind, id);
|
|
32559
|
+
}
|
|
32484
32560
|
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
32485
32561
|
var askIdProblem = "ask must be a bare ask id or a dispatch://.../ask/<id> reference";
|
|
32486
32562
|
var commentIdProblem = "comment must be a bare comment id or a dispatch://.../comment/<id> reference";
|
|
@@ -32524,7 +32600,7 @@ function argumentProblems(tool, args) {
|
|
|
32524
32600
|
case "dispatch_ask": {
|
|
32525
32601
|
const question = optionalString(args, "question");
|
|
32526
32602
|
const ref = optionalString(args, "ref");
|
|
32527
|
-
if (question !== undefined && ref !== undefined && question.length <=
|
|
32603
|
+
if (question !== undefined && ref !== undefined && question.length <= ASK_QUESTION_MAX) {
|
|
32528
32604
|
const problem = askQuestionProblem(questionWithRef(question, ref));
|
|
32529
32605
|
if (problem !== undefined)
|
|
32530
32606
|
problems.push(problem);
|
|
@@ -32614,11 +32690,17 @@ function dispatchRefFromUrl(value, serverUrl) {
|
|
|
32614
32690
|
}
|
|
32615
32691
|
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";
|
|
32616
32692
|
var ownerRequiredProblem = "issue is required; supply issue or set LEGION_ISSUE";
|
|
32693
|
+
var toolSchemas = new Map;
|
|
32617
32694
|
function toolSchema(tool) {
|
|
32695
|
+
const cached = toolSchemas.get(tool);
|
|
32696
|
+
if (cached !== undefined)
|
|
32697
|
+
return cached;
|
|
32618
32698
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
32619
32699
|
if (!spec)
|
|
32620
32700
|
throw new Error(`Unknown Dispatch tool: ${tool}`);
|
|
32621
|
-
|
|
32701
|
+
const schema = dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
|
|
32702
|
+
toolSchemas.set(tool, schema);
|
|
32703
|
+
return schema;
|
|
32622
32704
|
}
|
|
32623
32705
|
async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, problems) {
|
|
32624
32706
|
if (issueFreeTools[tool] === true)
|
|
@@ -32773,6 +32855,18 @@ function approvalLine(artifact) {
|
|
|
32773
32855
|
return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
|
|
32774
32856
|
}
|
|
32775
32857
|
}
|
|
32858
|
+
function componentsLine(components) {
|
|
32859
|
+
const inherited = components.inherited_from === null ? "" : ` (inherited from ${components.inherited_from})`;
|
|
32860
|
+
const retired = components.unknown.length === 0 ? "" : ` (retired: ${components.unknown.join(", ")})`;
|
|
32861
|
+
switch (components.mode) {
|
|
32862
|
+
case "explicit":
|
|
32863
|
+
return `Components: ${components.ids.length === 0 ? "none live" : components.ids.join(", ")}${inherited}${retired}`;
|
|
32864
|
+
case "none":
|
|
32865
|
+
return `Components: none \u2014 ${components.reason ?? ""}${inherited}`;
|
|
32866
|
+
case "inherit":
|
|
32867
|
+
return "Components: unassigned";
|
|
32868
|
+
}
|
|
32869
|
+
}
|
|
32776
32870
|
function issueSummary(issue, events, references, graph) {
|
|
32777
32871
|
const asks = issue.open_asks;
|
|
32778
32872
|
const spec = issue.artifacts?.find((artifact) => artifact.primary);
|
|
@@ -32781,6 +32875,8 @@ function issueSummary(issue, events, references, graph) {
|
|
|
32781
32875
|
throw new Error("Dispatch issue is missing priority");
|
|
32782
32876
|
if (issue.assignee === undefined)
|
|
32783
32877
|
throw new Error("Dispatch issue is missing assignee");
|
|
32878
|
+
if (issue.components === undefined)
|
|
32879
|
+
throw new Error("Dispatch issue is missing components");
|
|
32784
32880
|
return [
|
|
32785
32881
|
`Title: ${issue.title}`,
|
|
32786
32882
|
`Key: ${issue.key}`,
|
|
@@ -32788,6 +32884,7 @@ function issueSummary(issue, events, references, graph) {
|
|
|
32788
32884
|
`Assignee: ${issue.assignee ?? "unassigned"}`,
|
|
32789
32885
|
...issue.priority === null ? [] : [`Priority: P${issue.priority}`],
|
|
32790
32886
|
`Labels: ${issue.labels.length === 0 ? "none" : issue.labels.join(", ")}`,
|
|
32887
|
+
componentsLine(issue.components),
|
|
32791
32888
|
`Route: ${issue.route ?? "none"}`,
|
|
32792
32889
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
32793
32890
|
"Open asks:",
|
|
@@ -32811,11 +32908,21 @@ function referenceLines(edges) {
|
|
|
32811
32908
|
return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
|
|
32812
32909
|
});
|
|
32813
32910
|
}
|
|
32911
|
+
function unavailableReason(error) {
|
|
32912
|
+
return error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${messageFor(error)}`;
|
|
32913
|
+
}
|
|
32814
32914
|
async function graphEdges(client, query) {
|
|
32815
32915
|
try {
|
|
32816
32916
|
return (await client.getReferences(query)).edges;
|
|
32817
32917
|
} catch (error) {
|
|
32818
|
-
return
|
|
32918
|
+
return unavailableReason(error);
|
|
32919
|
+
}
|
|
32920
|
+
}
|
|
32921
|
+
async function issueReferencesOrUnavailable(client, key) {
|
|
32922
|
+
try {
|
|
32923
|
+
return await client.getIssueReferences(key);
|
|
32924
|
+
} catch (error) {
|
|
32925
|
+
return unavailableReason(error);
|
|
32819
32926
|
}
|
|
32820
32927
|
}
|
|
32821
32928
|
async function graphSections(client, ref) {
|
|
@@ -33025,8 +33132,9 @@ async function executeDispatchTool(input) {
|
|
|
33025
33132
|
problems.push(...argumentProblems(input.tool, ownerArguments.args));
|
|
33026
33133
|
if (problems.length > 0)
|
|
33027
33134
|
throw new ToolInputError(input.tool, problems);
|
|
33135
|
+
const dispatchClient = () => new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
33028
33136
|
if (input.tool === "dispatch_open_asks") {
|
|
33029
|
-
const client =
|
|
33137
|
+
const client = dispatchClient();
|
|
33030
33138
|
const project = optionalString(ownerArguments.args, "project");
|
|
33031
33139
|
if (project !== undefined) {
|
|
33032
33140
|
const response = await client.openAsksForProject(project);
|
|
@@ -33042,7 +33150,7 @@ async function executeDispatchTool(input) {
|
|
|
33042
33150
|
const sessionId = input.sessionId?.trim();
|
|
33043
33151
|
if (!sessionId)
|
|
33044
33152
|
throw new Error("host session id is required for dispatch_whoami");
|
|
33045
|
-
const client =
|
|
33153
|
+
const client = dispatchClient();
|
|
33046
33154
|
const identity = await client.whoami();
|
|
33047
33155
|
const owner = identity.kind === "agent" ? identity.owner : identity.login.toLowerCase();
|
|
33048
33156
|
return {
|
|
@@ -33052,7 +33160,7 @@ async function executeDispatchTool(input) {
|
|
|
33052
33160
|
}
|
|
33053
33161
|
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
33054
33162
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
33055
|
-
const client =
|
|
33163
|
+
const client = dispatchClient();
|
|
33056
33164
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
33057
33165
|
kind: "issue",
|
|
33058
33166
|
issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
|
|
@@ -33077,6 +33185,7 @@ async function executeDispatchTool(input) {
|
|
|
33077
33185
|
const spec = optionalString(args, "spec");
|
|
33078
33186
|
const priority = optionalNumber(args, "priority");
|
|
33079
33187
|
const assignee = optionalString(args, "assignee");
|
|
33188
|
+
const components = optionalComponents(args, "components");
|
|
33080
33189
|
const labels = args.labels;
|
|
33081
33190
|
try {
|
|
33082
33191
|
const created = await client.issue({
|
|
@@ -33088,6 +33197,7 @@ async function executeDispatchTool(input) {
|
|
|
33088
33197
|
...spec === undefined ? {} : { spec },
|
|
33089
33198
|
...priority === undefined ? {} : { priority },
|
|
33090
33199
|
...assignee === undefined ? {} : { assignee },
|
|
33200
|
+
...components === undefined ? {} : { components },
|
|
33091
33201
|
...Array.isArray(labels) ? { labels } : {},
|
|
33092
33202
|
actor
|
|
33093
33203
|
});
|
|
@@ -33120,6 +33230,7 @@ async function executeDispatchTool(input) {
|
|
|
33120
33230
|
const title = optionalString(args, "title");
|
|
33121
33231
|
const route = optionalString(args, "route");
|
|
33122
33232
|
const parent = optionalString(args, "parent");
|
|
33233
|
+
const components = optionalComponents(args, "components");
|
|
33123
33234
|
const labels = Array.isArray(args.labels) ? args.labels : undefined;
|
|
33124
33235
|
const requestedLinks = Array.isArray(args.external_links) ? [...new Set(args.external_links)] : undefined;
|
|
33125
33236
|
let newLinks = [];
|
|
@@ -33133,6 +33244,7 @@ async function executeDispatchTool(input) {
|
|
|
33133
33244
|
...labels === undefined ? {} : { labels },
|
|
33134
33245
|
...route === undefined ? {} : { route },
|
|
33135
33246
|
...parent === undefined ? {} : { parent: parent === "" ? null : parent },
|
|
33247
|
+
...components === undefined ? {} : { components },
|
|
33136
33248
|
...requestedLinks === undefined ? {} : { external_links: [...before.external_links, ...newLinks.map((url) => ({ url }))] },
|
|
33137
33249
|
actor
|
|
33138
33250
|
});
|
|
@@ -33145,7 +33257,8 @@ async function executeDispatchTool(input) {
|
|
|
33145
33257
|
newLinks.length === 0 ? `already linked ${requestedLinks.join(", ")} ${linkCount}` : `linked ${newLinks.join(", ")} ${linkCount}`
|
|
33146
33258
|
],
|
|
33147
33259
|
...route === undefined ? [] : [after.route === null ? "route cleared" : `route ${after.route}`],
|
|
33148
|
-
...parent === undefined ? [] : [after.parent === null ? "parent cleared" : `parent -> ${after.parent}`]
|
|
33260
|
+
...parent === undefined ? [] : [after.parent === null ? "parent cleared" : `parent -> ${after.parent}`],
|
|
33261
|
+
...components === undefined ? [] : [componentsChange(components, after.components)]
|
|
33149
33262
|
];
|
|
33150
33263
|
return {
|
|
33151
33264
|
text: `${after.key}: ${changes.join("; ")} ${notSubscribed(issueTopic(after.key))}`,
|
|
@@ -33286,7 +33399,7 @@ async function executeDispatchTool(input) {
|
|
|
33286
33399
|
return {
|
|
33287
33400
|
text: `Asked ${ask.id} on ${askOwner.label} (urgency ${ask.urgency}): ${ask.question}
|
|
33288
33401
|
${followsAsk(askOwner)}`,
|
|
33289
|
-
details: await followedAskDetails(client, ask, resolved)
|
|
33402
|
+
details: await followedAskDetails(client, ask, resolved?.artifact)
|
|
33290
33403
|
};
|
|
33291
33404
|
}
|
|
33292
33405
|
case "dispatch_edit_ask": {
|
|
@@ -33368,7 +33481,7 @@ ${followsAsk(askOwner)}`,
|
|
|
33368
33481
|
...inReplyTo === undefined ? {} : { in_reply_to: inReplyTo },
|
|
33369
33482
|
actor
|
|
33370
33483
|
});
|
|
33371
|
-
const messageRef =
|
|
33484
|
+
const messageRef = dispatchChildRef(dispatchIssueRef(issueKey), "message", message.id);
|
|
33372
33485
|
return {
|
|
33373
33486
|
text: `Posted message ${message.id} (${messageRef}) ${notSubscribed(issueTopic(issueKey))}`,
|
|
33374
33487
|
details: { issue: issueKey, message: message.id }
|
|
@@ -33430,7 +33543,7 @@ ${trailer.join(`
|
|
|
33430
33543
|
}
|
|
33431
33544
|
};
|
|
33432
33545
|
}
|
|
33433
|
-
const details = await followedAskDetails(client, result.ask, resolved);
|
|
33546
|
+
const details = await followedAskDetails(client, result.ask, resolved.artifact);
|
|
33434
33547
|
return {
|
|
33435
33548
|
text: `Approval requested for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}). The answer arrives as artifact.approved or artifact.changes_requested; an edit after approval makes it stale, so request again for the new version.`,
|
|
33436
33549
|
details: { ...details, artifact: resolved.artifact.id, version: result.version }
|
|
@@ -33453,7 +33566,7 @@ ${trailer.join(`
|
|
|
33453
33566
|
};
|
|
33454
33567
|
const artifactOwner = documentOwner();
|
|
33455
33568
|
const result = artifactOwner.kind === "project" ? await client.projectArtifact(artifactOwner.project, artifactInput) : await client.artifact(issue(), artifactInput);
|
|
33456
|
-
const artifactRef = artifactOwner.kind === "project" ?
|
|
33569
|
+
const artifactRef = dispatchDocumentRef(artifactOwner.kind === "project" ? artifactOwner.project : issue(), result.artifact.slug);
|
|
33457
33570
|
const uploadOwner = artifactOwner.kind === "project" ? documentTopic(result.artifact) : issueTopic(issue());
|
|
33458
33571
|
return {
|
|
33459
33572
|
text: `Uploaded ${result.artifact.name} as version ${result.version.number} (artifact slug ${result.artifact.slug}; ${artifactRef}) ${notSubscribed(uploadOwner)}`,
|
|
@@ -33489,7 +33602,7 @@ ${trailer.join(`
|
|
|
33489
33602
|
const ref = ownerArguments.ref;
|
|
33490
33603
|
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"));
|
|
33491
33604
|
const askRead = await client.getAsk(id);
|
|
33492
|
-
const askRef = ref
|
|
33605
|
+
const askRef = refTarget(ref, "ask", id);
|
|
33493
33606
|
return {
|
|
33494
33607
|
text: askSummary(askRead, await graphSections(client, askRef)),
|
|
33495
33608
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue }
|
|
@@ -33499,7 +33612,7 @@ ${trailer.join(`
|
|
|
33499
33612
|
const ref = ownerArguments.ref;
|
|
33500
33613
|
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));
|
|
33501
33614
|
const comment = await client.getComment(id);
|
|
33502
|
-
const commentRef = ref
|
|
33615
|
+
const commentRef = refTarget(ref, "comment", id);
|
|
33503
33616
|
return {
|
|
33504
33617
|
text: commentSummary(comment, await graphSections(client, commentRef)),
|
|
33505
33618
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: comment.comment.issue_key }
|
|
@@ -33510,7 +33623,7 @@ ${trailer.join(`
|
|
|
33510
33623
|
throw new Error("message references are issue-scoped");
|
|
33511
33624
|
}
|
|
33512
33625
|
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
33513
|
-
const messageRef =
|
|
33626
|
+
const messageRef = dispatchChildRef(dispatchIssueRef(ownerArguments.ref.owner.issue), "message", ownerArguments.ref.id);
|
|
33514
33627
|
return {
|
|
33515
33628
|
text: messageSummary(messageRead, await graphSections(client, messageRef)),
|
|
33516
33629
|
details: { issue: messageRead.message.issue_key }
|
|
@@ -33518,7 +33631,7 @@ ${trailer.join(`
|
|
|
33518
33631
|
}
|
|
33519
33632
|
if (documentOwner().kind === "project") {
|
|
33520
33633
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
33521
|
-
const documentRef =
|
|
33634
|
+
const documentRef = dispatchDocumentRef(resolved.artifact.project, resolved.artifact.slug);
|
|
33522
33635
|
return {
|
|
33523
33636
|
text: [
|
|
33524
33637
|
`Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
|
|
@@ -33530,7 +33643,7 @@ ${trailer.join(`
|
|
|
33530
33643
|
`),
|
|
33531
33644
|
details: {
|
|
33532
33645
|
project: resolved.artifact.project,
|
|
33533
|
-
document:
|
|
33646
|
+
document: documentLabel(resolved.artifact.project, resolved.artifact.slug)
|
|
33534
33647
|
}
|
|
33535
33648
|
};
|
|
33536
33649
|
}
|
|
@@ -33547,14 +33660,12 @@ ${trailer.join(`
|
|
|
33547
33660
|
details: { issue: read.issue.key }
|
|
33548
33661
|
};
|
|
33549
33662
|
}
|
|
33550
|
-
|
|
33551
|
-
|
|
33552
|
-
|
|
33553
|
-
|
|
33554
|
-
references = error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
33555
|
-
}
|
|
33663
|
+
const [references, graph] = await Promise.all([
|
|
33664
|
+
issueReferencesOrUnavailable(client, read.issue.key),
|
|
33665
|
+
graphSections(client, dispatchIssueRef(read.issue.key))
|
|
33666
|
+
]);
|
|
33556
33667
|
return {
|
|
33557
|
-
text: issueSummary(read.issue, read.events, references,
|
|
33668
|
+
text: issueSummary(read.issue, read.events, references, graph),
|
|
33558
33669
|
details: { issue: read.issue.key }
|
|
33559
33670
|
};
|
|
33560
33671
|
}
|
|
@@ -33589,12 +33700,12 @@ function dispatchFollowNotice(details) {
|
|
|
33589
33700
|
return null;
|
|
33590
33701
|
let owner;
|
|
33591
33702
|
if (typeof issue === "string" && issue !== "") {
|
|
33592
|
-
owner =
|
|
33703
|
+
owner = issueTopic(issue);
|
|
33593
33704
|
} else if (typeof document === "string") {
|
|
33594
33705
|
const [project, slug] = document.split("/", 2);
|
|
33595
33706
|
if (!project || !slug)
|
|
33596
33707
|
return null;
|
|
33597
|
-
owner = { label: document, topic:
|
|
33708
|
+
owner = { label: document, topic: documentTopicOf(project, slug).topic };
|
|
33598
33709
|
} else {
|
|
33599
33710
|
return null;
|
|
33600
33711
|
}
|
|
@@ -33603,6 +33714,16 @@ function dispatchFollowNotice(details) {
|
|
|
33603
33714
|
text: `Following ask ${ask} on ${owner.label}: its answer and replies reach you directly (dispatch_follow unfollow to stop). For every event on ${owner.label}: envoy_subscribe ${owner.topic}.`
|
|
33604
33715
|
};
|
|
33605
33716
|
}
|
|
33717
|
+
function createFollowAnnouncer(emit) {
|
|
33718
|
+
const announced = new Set;
|
|
33719
|
+
return (details) => {
|
|
33720
|
+
const notice = dispatchFollowNotice(details);
|
|
33721
|
+
if (notice === null || announced.has(notice.ask))
|
|
33722
|
+
return;
|
|
33723
|
+
announced.add(notice.ask);
|
|
33724
|
+
return emit(notice.text);
|
|
33725
|
+
};
|
|
33726
|
+
}
|
|
33606
33727
|
function subscriptionRemovedTopics(raw, sessionID) {
|
|
33607
33728
|
let envelope;
|
|
33608
33729
|
try {
|
|
@@ -33750,11 +33871,19 @@ var envoyToolSpecs = [
|
|
|
33750
33871
|
requiresSubscriptionCapability: false
|
|
33751
33872
|
}
|
|
33752
33873
|
];
|
|
33874
|
+
function sendConfirmationText(result) {
|
|
33875
|
+
return `sent ${result.envelope.event_id} to ${result.recipient}${result.confirmed ? "" : " (recipient unconfirmed by listener)"}`;
|
|
33876
|
+
}
|
|
33877
|
+
var envoyToolSchemas = new Map;
|
|
33753
33878
|
function parseEnvoyToolArguments(operation, parameters) {
|
|
33754
33879
|
const spec = envoyToolSpecs.find((candidate) => candidate.operation === operation);
|
|
33755
33880
|
if (spec === undefined)
|
|
33756
33881
|
throw new Error(`missing Envoy tool specification for ${operation}`);
|
|
33757
|
-
|
|
33882
|
+
let schema = envoyToolSchemas.get(operation);
|
|
33883
|
+
if (schema === undefined) {
|
|
33884
|
+
schema = object(spec.arguments(zodSchemaApi(exports_external))).strict();
|
|
33885
|
+
envoyToolSchemas.set(operation, schema);
|
|
33886
|
+
}
|
|
33758
33887
|
const parsed = schema.safeParse(parameters, { reportInput: true });
|
|
33759
33888
|
if (!parsed.success) {
|
|
33760
33889
|
throw new ToolInputError(spec.name, formatZodIssues(parsed.error.issues, schema));
|
|
@@ -33803,6 +33932,17 @@ var EnvelopeResponseSchema = EnvelopeSchema.extend({
|
|
|
33803
33932
|
recipient: string2().optional(),
|
|
33804
33933
|
holder: string2().optional()
|
|
33805
33934
|
});
|
|
33935
|
+
function mergeInterestSources(registryTopics, liveTopics) {
|
|
33936
|
+
const interests = new Map;
|
|
33937
|
+
for (const topic of registryTopics) {
|
|
33938
|
+
interests.set(topic, liveTopics.includes(topic) ? "both" : "registry");
|
|
33939
|
+
}
|
|
33940
|
+
for (const topic of liveTopics) {
|
|
33941
|
+
if (!interests.has(topic))
|
|
33942
|
+
interests.set(topic, "live");
|
|
33943
|
+
}
|
|
33944
|
+
return interests;
|
|
33945
|
+
}
|
|
33806
33946
|
function expandSubscriptionTopics(topics) {
|
|
33807
33947
|
const expanded = new Set;
|
|
33808
33948
|
for (const topic of topics) {
|
|
@@ -34084,7 +34224,6 @@ async function copySessionID(copyToClipboard, sessionID) {
|
|
|
34084
34224
|
// extensions/envoy.ts
|
|
34085
34225
|
var codec2 = import_nats.StringCodec();
|
|
34086
34226
|
var NATS_RETRY_INTERVAL_MS = 15000;
|
|
34087
|
-
var ALL_CAPABILITIES = DELIVERY_CAPABILITIES;
|
|
34088
34227
|
var CAPABILITIES_WITHOUT_BTW = DELIVERY_CAPABILITIES.filter((capability) => capability !== "btw");
|
|
34089
34228
|
var ROLE_CLAIM_ENTRY = "envoy-role-claim";
|
|
34090
34229
|
var OPEN_ASKS_TIMEOUT_MS = 3000;
|
|
@@ -34157,7 +34296,7 @@ function envoyExtension(pi) {
|
|
|
34157
34296
|
return null;
|
|
34158
34297
|
const snapshot = await new DispatchClient(config.url, config.token, fetch, AbortSignal.timeout(OPEN_ASKS_TIMEOUT_MS)).openAsks(requestedSessionID);
|
|
34159
34298
|
availabilityWarningSessionIDs.delete(requestedSessionID);
|
|
34160
|
-
return snapshot;
|
|
34299
|
+
return { snapshot, url: config.url };
|
|
34161
34300
|
};
|
|
34162
34301
|
const restoreLocalSessionState = (context) => {
|
|
34163
34302
|
sessionDirectory = context.cwd;
|
|
@@ -34179,28 +34318,17 @@ function envoyExtension(pi) {
|
|
|
34179
34318
|
return connection;
|
|
34180
34319
|
};
|
|
34181
34320
|
const postDispatchReply = async (delivery, result) => {
|
|
34182
|
-
|
|
34183
|
-
const response = await fetch(`${config.url}/api/v1/messages/${delivery.messageID}/reply`, {
|
|
34184
|
-
method: "POST",
|
|
34185
|
-
headers: {
|
|
34186
|
-
Authorization: `Bearer ${config.token}`,
|
|
34187
|
-
"Content-Type": "application/json"
|
|
34188
|
-
},
|
|
34189
|
-
body: JSON.stringify({
|
|
34190
|
-
actor: { kind: "session", id: sessionID },
|
|
34191
|
-
attempt: delivery.attempt,
|
|
34192
|
-
...result
|
|
34193
|
-
})
|
|
34194
|
-
});
|
|
34195
|
-
if (!response.ok) {
|
|
34196
|
-
throw new Error(`Dispatch reply failed: ${response.status} ${await response.text()}`);
|
|
34197
|
-
}
|
|
34321
|
+
await postDeliveryReply(currentDispatchConfig(), sessionID, delivery, result);
|
|
34198
34322
|
};
|
|
34199
34323
|
const deliver = async (subject, raw, reply) => {
|
|
34200
34324
|
const rendered = renderInbound(raw, sessionID, subject);
|
|
34201
34325
|
const directSubject = agentSubject(sessionID);
|
|
34202
|
-
|
|
34203
|
-
|
|
34326
|
+
if (expectsLaneReceipt({
|
|
34327
|
+
subject,
|
|
34328
|
+
directSubject,
|
|
34329
|
+
envelopeTopic: rendered.envelope?.topic,
|
|
34330
|
+
reply
|
|
34331
|
+
})) {
|
|
34204
34332
|
try {
|
|
34205
34333
|
(await ensureConnection()).publish(reply);
|
|
34206
34334
|
} catch (error) {
|
|
@@ -34254,14 +34382,8 @@ function envoyExtension(pi) {
|
|
|
34254
34382
|
console.warn(`[envoy] failed to deliver envelope ${envelope?.event_id ?? "unknown"}`, error);
|
|
34255
34383
|
throw error;
|
|
34256
34384
|
}
|
|
34257
|
-
if (dedupeKey !== undefined)
|
|
34258
|
-
dedupeKeys
|
|
34259
|
-
if (dedupeKeys.size > 1000) {
|
|
34260
|
-
const oldest = dedupeKeys.values().next();
|
|
34261
|
-
if (!oldest.done)
|
|
34262
|
-
dedupeKeys.delete(oldest.value);
|
|
34263
|
-
}
|
|
34264
|
-
}
|
|
34385
|
+
if (dedupeKey !== undefined)
|
|
34386
|
+
rememberBounded(dedupeKeys, dedupeKey, 1000);
|
|
34265
34387
|
}
|
|
34266
34388
|
};
|
|
34267
34389
|
const RESUBSCRIBE_DELAY_MS = Number(process.env.ENVOY_RESUBSCRIBE_DELAY_MS ?? "") || 5000;
|
|
@@ -34327,7 +34449,7 @@ function envoyExtension(pi) {
|
|
|
34327
34449
|
topics: [...new Set([agentSubject(sessionID), ...subscriptions.keys()])],
|
|
34328
34450
|
port: 0,
|
|
34329
34451
|
title: activeSessionContext?.sessionManager.getSessionName?.() ?? "",
|
|
34330
|
-
capabilities: typeof pi.askEphemeral === "function" ?
|
|
34452
|
+
capabilities: typeof pi.askEphemeral === "function" ? DELIVERY_CAPABILITIES : CAPABILITIES_WITHOUT_BTW,
|
|
34331
34453
|
driving: false,
|
|
34332
34454
|
selfSubscribed: true
|
|
34333
34455
|
});
|
|
@@ -34683,14 +34805,14 @@ function envoyExtension(pi) {
|
|
|
34683
34805
|
if (id === "")
|
|
34684
34806
|
return;
|
|
34685
34807
|
try {
|
|
34686
|
-
const
|
|
34687
|
-
if (
|
|
34808
|
+
const open = await queryOpenAsks(id);
|
|
34809
|
+
if (open === null)
|
|
34688
34810
|
return;
|
|
34689
34811
|
return {
|
|
34690
34812
|
message: {
|
|
34691
34813
|
customType: "dispatch-open-asks",
|
|
34692
34814
|
content: `Dispatch authored-ask summary:
|
|
34693
|
-
${formatOpenAsksSummary(snapshot,
|
|
34815
|
+
${formatOpenAsksSummary(open.snapshot, open.url)}`,
|
|
34694
34816
|
display: false,
|
|
34695
34817
|
attribution: "agent"
|
|
34696
34818
|
}
|
|
@@ -34707,15 +34829,13 @@ ${formatOpenAsksSummary(snapshot, currentDispatchConfig().url)}`,
|
|
|
34707
34829
|
};
|
|
34708
34830
|
}
|
|
34709
34831
|
});
|
|
34710
|
-
const
|
|
34832
|
+
const announceFollow = createFollowAnnouncer((text) => {
|
|
34833
|
+
pi.sendMessage({ customType: "envoy-message", content: text, display: true }, { deliverAs: "steer", triggerTurn: false });
|
|
34834
|
+
});
|
|
34711
34835
|
pi.on("tool_result", async (event) => {
|
|
34712
34836
|
if (event.isError)
|
|
34713
34837
|
return;
|
|
34714
|
-
|
|
34715
|
-
if (notice === null || announcedFollows.has(notice.ask))
|
|
34716
|
-
return;
|
|
34717
|
-
announcedFollows.add(notice.ask);
|
|
34718
|
-
pi.sendMessage({ customType: "envoy-message", content: notice.text, display: true }, { deliverAs: "steer", triggerTurn: false });
|
|
34838
|
+
announceFollow(event.details);
|
|
34719
34839
|
});
|
|
34720
34840
|
async function execute(operation, rawParameters) {
|
|
34721
34841
|
try {
|
|
@@ -34765,14 +34885,7 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
34765
34885
|
}
|
|
34766
34886
|
case EnvoyToolOperation.listInterests: {
|
|
34767
34887
|
const registry = await client.getInterest(sessionID);
|
|
34768
|
-
const interests =
|
|
34769
|
-
for (const topic of registry.topics) {
|
|
34770
|
-
interests.set(topic, subscriptions.has(topic) ? "both" : "registry");
|
|
34771
|
-
}
|
|
34772
|
-
for (const topic of subscriptions.keys()) {
|
|
34773
|
-
if (!interests.has(topic))
|
|
34774
|
-
interests.set(topic, "live");
|
|
34775
|
-
}
|
|
34888
|
+
const interests = mergeInterestSources(registry.topics, [...subscriptions.keys()]);
|
|
34776
34889
|
return toolSuccess(JSON.stringify({ ...registry, topics: [...interests.keys()] }, null, 2), {
|
|
34777
34890
|
interests: [...interests].map(([topic, source]) => ({ topic, source }))
|
|
34778
34891
|
});
|
|
@@ -34787,8 +34900,7 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
34787
34900
|
message: stringFor(parameters, "message"),
|
|
34788
34901
|
...toMessageMetadata(parameters)
|
|
34789
34902
|
});
|
|
34790
|
-
|
|
34791
|
-
return toolSuccess(`sent ${result.envelope.event_id} to ${result.recipient}${confirmation}`, {
|
|
34903
|
+
return toolSuccess(sendConfirmationText(result), {
|
|
34792
34904
|
event_id: result.envelope.event_id,
|
|
34793
34905
|
recipient: result.recipient,
|
|
34794
34906
|
confirmed: result.confirmed
|
package/dist/legion.js
CHANGED
|
@@ -28923,6 +28923,7 @@ function preprocess(fn, schema) {
|
|
|
28923
28923
|
return pipe(transform(fn), schema);
|
|
28924
28924
|
}
|
|
28925
28925
|
// ../contracts/src/dispatch-api.ts
|
|
28926
|
+
var ASK_TURNS = ["human", "agent"];
|
|
28926
28927
|
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
28927
28928
|
var DispatchEventSchema = object({
|
|
28928
28929
|
issue_key: string2().nullable(),
|
|
@@ -29003,8 +29004,8 @@ var CommentEventPayloadSchema = object({
|
|
|
29003
29004
|
ask_id: string2().nullish(),
|
|
29004
29005
|
ask_question: string2().optional(),
|
|
29005
29006
|
ask_state: _enum2(["open", "answered", "resolved"]).optional(),
|
|
29006
|
-
ask_waiting_on: _enum2(
|
|
29007
|
-
turn: _enum2(
|
|
29007
|
+
ask_waiting_on: _enum2(ASK_TURNS).optional(),
|
|
29008
|
+
turn: _enum2(ASK_TURNS).nullish(),
|
|
29008
29009
|
anchor: object({ block_id: string2().nullable().optional(), quote: string2().optional() }).nullish(),
|
|
29009
29010
|
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
29010
29011
|
author: object({ kind: string2(), id: string2() }).optional(),
|
|
@@ -29127,16 +29128,22 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
|
|
|
29127
29128
|
message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29128
29129
|
};
|
|
29129
29130
|
}
|
|
29130
|
-
var
|
|
29131
|
-
|
|
29132
|
-
|
|
29133
|
-
|
|
29134
|
-
|
|
29135
|
-
|
|
29136
|
-
|
|
29137
|
-
|
|
29138
|
-
|
|
29139
|
-
|
|
29131
|
+
var commentOwner = documentOwnerValidation(true);
|
|
29132
|
+
var commentValidation = {
|
|
29133
|
+
check: (value) => {
|
|
29134
|
+
const input = value;
|
|
29135
|
+
return commentOwner.check(value) && (input.turn === undefined || typeof input.reply_to_ask === "string");
|
|
29136
|
+
},
|
|
29137
|
+
message: `${commentOwner.message} turn requires reply_to_ask.`
|
|
29138
|
+
};
|
|
29139
|
+
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
29140
|
+
function componentsArgument(z) {
|
|
29141
|
+
return z.object({
|
|
29142
|
+
mode: z.enum(ISSUE_COMPONENTS_MODES).describe("inherit: take the parent chain's attachment (the default; deletes this issue's own). explicit: attach to ids. none: not architectural work, with reason."),
|
|
29143
|
+
ids: z.array(z.string({ min: 1 }), { min: 1, max: 50 }).describe("For mode explicit: bare component ids from the project's architecture model (web, dispatch-server), not external ones.").optional(),
|
|
29144
|
+
reason: z.string({ min: 1 }).describe("For mode none: why this issue is not architectural (process, hiring, ops).").optional()
|
|
29145
|
+
}).describe("Attach the issue to architecture components. Attach the root before decomposing it; children inherit unless they choose.");
|
|
29146
|
+
}
|
|
29140
29147
|
var SPEC_SECTIONS = [
|
|
29141
29148
|
"Summary",
|
|
29142
29149
|
"Decisions needed",
|
|
@@ -29150,6 +29157,7 @@ var SPEC_SECTIONS = [
|
|
|
29150
29157
|
];
|
|
29151
29158
|
var SPEC_WRITING_GUIDANCE = `When writing a spec, use these sections in order: ${SPEC_SECTIONS.join(", ")}. ` + "Write for a reader who has not seen the code: plain sentences, every identifier expanded on " + "first use, no coined shorthand; see skills/dispatch Writing for the human and Writing a spec.";
|
|
29152
29159
|
var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
29160
|
+
var ASK_QUESTION_MAX = 800;
|
|
29153
29161
|
var ISSUE_STATUSES = [
|
|
29154
29162
|
"triage",
|
|
29155
29163
|
"icebox",
|
|
@@ -29161,6 +29169,9 @@ var ISSUE_STATUSES = [
|
|
|
29161
29169
|
"retro",
|
|
29162
29170
|
"done"
|
|
29163
29171
|
];
|
|
29172
|
+
function isIssueStatus(value) {
|
|
29173
|
+
return ISSUE_STATUSES.includes(value);
|
|
29174
|
+
}
|
|
29164
29175
|
var DOC_EDIT_OPS = ["replace", "delete", "insert", "retype", "move"];
|
|
29165
29176
|
var dispatchToolSpecs = [
|
|
29166
29177
|
{
|
|
@@ -29175,12 +29186,13 @@ var dispatchToolSpecs = [
|
|
|
29175
29186
|
spec: z.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_GUIDANCE}`).optional(),
|
|
29176
29187
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
|
|
29177
29188
|
priority: z.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority: P0 is highest and P3 is lowest.").optional(),
|
|
29178
|
-
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional()
|
|
29189
|
+
assignee: z.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional(),
|
|
29190
|
+
components: componentsArgument(z).optional()
|
|
29179
29191
|
})
|
|
29180
29192
|
},
|
|
29181
29193
|
{
|
|
29182
29194
|
name: "dispatch_issue_update",
|
|
29183
|
-
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route,
|
|
29195
|
+
description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route, set or clear its parent, " + "or attach it to architecture components. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. external_links are " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link. components replaces the issue's own attachment and is allowed on a " + "closed issue. Priority is the human's and is not settable here. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
29184
29196
|
arguments: (z) => ({
|
|
29185
29197
|
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29186
29198
|
status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
@@ -29188,26 +29200,27 @@ var dispatchToolSpecs = [
|
|
|
29188
29200
|
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Replacement label set, at most 20 labels of up to 40 characters; replaces every existing label.").optional(),
|
|
29189
29201
|
external_links: z.array(z.string({ min: 1 })).describe("URLs to link; merged into the issue's existing external links by URL.").optional(),
|
|
29190
29202
|
route: z.string().describe("Route the issue to role:<name> or session:<id>; an empty string clears it.").optional(),
|
|
29191
|
-
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional()
|
|
29203
|
+
parent: z.string().describe("Parent issue key in the same project; an empty string clears the parent.").optional(),
|
|
29204
|
+
components: componentsArgument(z).optional()
|
|
29192
29205
|
}),
|
|
29193
29206
|
validation: {
|
|
29194
29207
|
check: (value) => {
|
|
29195
29208
|
const input = value;
|
|
29196
|
-
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string";
|
|
29209
|
+
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string" || typeof input.parent === "string" || typeof input.components === "object" && input.components !== null;
|
|
29197
29210
|
},
|
|
29198
|
-
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, or
|
|
29211
|
+
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, route, parent, or components."
|
|
29199
29212
|
},
|
|
29200
29213
|
strict: true
|
|
29201
29214
|
},
|
|
29202
29215
|
{
|
|
29203
29216
|
name: "dispatch_ask",
|
|
29204
|
-
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " +
|
|
29217
|
+
description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
29205
29218
|
arguments: (z) => ({
|
|
29206
29219
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29207
29220
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29208
29221
|
artifact: z.string().describe("Project document artifact id, slug, or filename.").optional(),
|
|
29209
29222
|
ref: z.string().describe("Optional dispatch:// reference (issue, document, message, or ask); appended to the question and rendered as a link.").optional(),
|
|
29210
|
-
question: z.string({ max:
|
|
29223
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Decision question, at most ${ASK_QUESTION_MAX} characters.`),
|
|
29211
29224
|
options: z.array(z.object({
|
|
29212
29225
|
label: z.string().describe("Selectable option label."),
|
|
29213
29226
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -29227,7 +29240,7 @@ var dispatchToolSpecs = [
|
|
|
29227
29240
|
description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited.",
|
|
29228
29241
|
arguments: (z) => ({
|
|
29229
29242
|
ask: z.string().describe("Ask id to edit."),
|
|
29230
|
-
question: z.string({ max:
|
|
29243
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
|
|
29231
29244
|
options: z.array(z.object({
|
|
29232
29245
|
label: z.string().describe("Selectable option label."),
|
|
29233
29246
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -29980,11 +29993,7 @@ function generation(env) {
|
|
|
29980
29993
|
}
|
|
29981
29994
|
|
|
29982
29995
|
// src/legion/daemon-client.ts
|
|
29983
|
-
|
|
29984
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
29985
|
-
setTimeout(resolve, ms);
|
|
29986
|
-
return promise;
|
|
29987
|
-
}
|
|
29996
|
+
import { setTimeout as delay } from "timers/promises";
|
|
29988
29997
|
var SPAWN_WORKER_RETRY_DELAYS_MS = [2000, 5000];
|
|
29989
29998
|
var SPAWN_WORKER_ATTEMPTS = SPAWN_WORKER_RETRY_DELAYS_MS.length + 1;
|
|
29990
29999
|
|
|
@@ -30043,7 +30052,7 @@ function sessionCapability(body) {
|
|
|
30043
30052
|
return { sessionId: candidate.sessionId, secret: candidate.secret };
|
|
30044
30053
|
}
|
|
30045
30054
|
var WORKER_SESSION_PATH = "/legion/v1/worker-session";
|
|
30046
|
-
function createLegionDaemonClient(baseUrl, fetchFn = fetch, recovery, sleep =
|
|
30055
|
+
function createLegionDaemonClient(baseUrl, fetchFn = fetch, recovery, sleep = (ms) => delay(ms)) {
|
|
30047
30056
|
const endpoint = baseUrl.replace(/\/+$/, "");
|
|
30048
30057
|
const postOnce = async (path, body, schema, fetchImpl = fetchFn, onResponseReadError) => {
|
|
30049
30058
|
const response = await fetchImpl(`${endpoint}${path}`, {
|
|
@@ -30352,9 +30361,6 @@ function toolFailure(error) {
|
|
|
30352
30361
|
|
|
30353
30362
|
// src/legion/tools.ts
|
|
30354
30363
|
var jsonSuccess = (details) => toolSuccess(JSON.stringify(details), details);
|
|
30355
|
-
function isIssueStatus(value) {
|
|
30356
|
-
return ISSUE_STATUSES.includes(value);
|
|
30357
|
-
}
|
|
30358
30364
|
function isDispatchArtifactId(value) {
|
|
30359
30365
|
return LegionDaemonApi.GatesRegister.request.shape.artifactId.safeParse(value).success;
|
|
30360
30366
|
}
|
|
@@ -30613,7 +30619,7 @@ var JJ_LOG_REWRITE_WORDS = ["undo", "abandon"];
|
|
|
30613
30619
|
var JJ_OP_WORDS = ["op", "operation"];
|
|
30614
30620
|
var JJ_OP_LOG_REWRITE_WORDS = ["restore", "revert"];
|
|
30615
30621
|
var JJ_MENTION = /\bjj\b/;
|
|
30616
|
-
var JJ_LOG_REWRITE_MENTION =
|
|
30622
|
+
var JJ_LOG_REWRITE_MENTION = new RegExp(`\\b(?:${JJ_LOG_REWRITE_WORDS.join("|")})\\b|\\b(?:${JJ_OP_WORDS.join("|")})\\b\\W+(?:${JJ_OP_LOG_REWRITE_WORDS.join("|")})\\b`);
|
|
30617
30623
|
function jjLogRewriteMention(text) {
|
|
30618
30624
|
if (!JJ_MENTION.test(text))
|
|
30619
30625
|
return;
|
|
@@ -30,6 +30,8 @@ vocabulary, and is often on a phone. Write for that person.
|
|
|
30
30
|
what each costs, and your recommendation with its reason.
|
|
31
31
|
- Before posting, test it: could Sami, reading only this text on his phone, know what he is being
|
|
32
32
|
told or asked? If not, rewrite it. Length is not the problem; density is.
|
|
33
|
+
- When an ask or message communicates a judgment, lead with that judgment in one sentence and put the mechanism underneath it. Do not make the reader ask a second time whether the result is a win. This shapes communication only when a judgment exists; it does not pre-decide an open question or remove its genuine options. Inferred from the AGENTC-186 12-hour-cap incident (platform PO, 2026-09-17).
|
|
34
|
+
- When a Dispatch message states a root cause, include the reproducing command or test in that same message. Without it, label the diagnosis a hypothesis; a diagnosis still in progress may say so plainly. This boundary applies to causal claims, not to reporting that an investigation has started. Inferred from the astro lane's 2026-09-16 retro (platform PO, 2026-09-17).
|
|
33
35
|
|
|
34
36
|
## Agent authentication
|
|
35
37
|
|
|
@@ -248,6 +250,17 @@ that follow from it:
|
|
|
248
250
|
the question carries `dispatch://KEY/artifact/<slug>` (or `ref`), never just its filename. Text
|
|
249
251
|
they must read to decide belongs in the spec in the first place — see [Artifacts](#artifacts).
|
|
250
252
|
|
|
253
|
+
Attach an issue to the architecture components it changes, before decomposing it — children inherit the parent's attachment unless they choose their own, so attaching the root once classifies the whole tree:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
dispatch_issue({ project: "CORE", title: "...", components: { mode: "explicit", ids: ["dispatch-server", "web"] } })
|
|
257
|
+
dispatch_issue_update({ issue: "CORE-12", components: { mode: "explicit", ids: ["web"] } }) // this issue's own set, replacing what it inherited
|
|
258
|
+
dispatch_issue_update({ issue: "CORE-13", components: { mode: "none", reason: "hiring, not code" } })
|
|
259
|
+
dispatch_issue_update({ issue: "CORE-14", components: { mode: "inherit" } }) // back to the parent chain's attachment
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Component ids are the file names under the repository's `.dispatch/architecture/` (`web.md` → `web`); an id the model lacks, or an `external` component, is refused with `COMPONENTS_INPUT`. A closed issue can be classified without reopening. Two rules: declare and attach before decomposing work, and change code and its architecture description (`.dispatch/architecture/<id>.md`) in the same review — the tree at `GET /api/v1/projects/{key}/architecture` counts every issue whose effective set names a component or anything it contains.
|
|
263
|
+
|
|
251
264
|
Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
|
|
252
265
|
```ts
|
|
253
266
|
dispatch_architecture_sync({ project: "CORE" })
|
|
@@ -256,6 +269,8 @@ It returns the imported commit, or the recorded error when the model was rejecte
|
|
|
256
269
|
|
|
257
270
|
Before saying you are waiting for human input, call `dispatch_open_asks`. With no arguments it lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply. With `dispatch_open_asks({ project })` it lists every open ask in that project — on its issues and on its documents, whoever authored them — which is how you audit what a whole project is waiting on rather than just your own asks.
|
|
258
271
|
|
|
272
|
+
**Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that Sami has not already settled, send a one-line ask before the first implementation commit. A platform-PO schema or contract ruling does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request; it is inferred from AGENTC-186's 2026-09-16 retro (platform PO, 2026-09-17).
|
|
273
|
+
|
|
259
274
|
**Anything that needs the human is an ask, or it does not exist.** An approval, a credential,
|
|
260
275
|
a setting only they can change, a review click, a conflict between two of their own rules - if
|
|
261
276
|
your work waits on it, open a `dispatch_ask` the moment you know, the action as the question.
|
|
@@ -331,3 +331,7 @@ capacity, and cross-tree conflict. Use the Legion escalation operation for those
|
|
|
331
331
|
everything else in the tree, or use `dispatch_ask` for a human question; workers may reach
|
|
332
332
|
Sami directly with `dispatch_ask` the same way. Do not create a wait loop for any wake
|
|
333
333
|
source.
|
|
334
|
+
|
|
335
|
+
## Architecture components
|
|
336
|
+
|
|
337
|
+
Bootstrap on a root whose project has an architecture source: in the root's first PR (the implement worker pushes it), write `.dispatch/architecture/<id>.md` files for the planned components — front matter `title`, `parent`, `depends_on`, `external`; no `paths` yet. The importer reads the source branch's head, so the sync and the attach below work only once that PR has merged to the source branch: until then leave the root on `inherit` (the attach would answer `400 COMPONENTS_INPUT`, the component does not exist yet). After the merge, `dispatch_architecture_sync({ project })` and attach the root with `dispatch_issue_update({ issue, components: { mode: "explicit", ids: [...] } })`. Children inherit the root's attachment; give a child its own `components` only when it changes a narrower set, and `{ mode: "none", reason }` when it is not architectural work. Attach before decomposing, and require every implementer to change the component file beside the code it describes in the same review.
|