@sjawhar/pi-legion-envoy 1.49.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 +214 -153
- package/dist/legion.js +21 -25
- package/dist/skills/dispatch/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,14 @@ 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
|
+
};
|
|
29866
29865
|
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
29867
29866
|
function componentsArgument(z) {
|
|
29868
29867
|
return z.object({
|
|
@@ -29884,6 +29883,7 @@ var SPEC_SECTIONS = [
|
|
|
29884
29883
|
];
|
|
29885
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.";
|
|
29886
29885
|
var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
29886
|
+
var ASK_QUESTION_MAX = 800;
|
|
29887
29887
|
var ISSUE_STATUSES = [
|
|
29888
29888
|
"triage",
|
|
29889
29889
|
"icebox",
|
|
@@ -29895,6 +29895,9 @@ var ISSUE_STATUSES = [
|
|
|
29895
29895
|
"retro",
|
|
29896
29896
|
"done"
|
|
29897
29897
|
];
|
|
29898
|
+
function isIssueStatus(value) {
|
|
29899
|
+
return ISSUE_STATUSES.includes(value);
|
|
29900
|
+
}
|
|
29898
29901
|
var DOC_EDIT_OPS = ["replace", "delete", "insert", "retype", "move"];
|
|
29899
29902
|
var dispatchToolSpecs = [
|
|
29900
29903
|
{
|
|
@@ -29937,13 +29940,13 @@ var dispatchToolSpecs = [
|
|
|
29937
29940
|
},
|
|
29938
29941
|
{
|
|
29939
29942
|
name: "dispatch_ask",
|
|
29940
|
-
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}`,
|
|
29941
29944
|
arguments: (z) => ({
|
|
29942
29945
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29943
29946
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29944
29947
|
artifact: z.string().describe("Project document artifact id, slug, or filename.").optional(),
|
|
29945
29948
|
ref: z.string().describe("Optional dispatch:// reference (issue, document, message, or ask); appended to the question and rendered as a link.").optional(),
|
|
29946
|
-
question: z.string({ max:
|
|
29949
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Decision question, at most ${ASK_QUESTION_MAX} characters.`),
|
|
29947
29950
|
options: z.array(z.object({
|
|
29948
29951
|
label: z.string().describe("Selectable option label."),
|
|
29949
29952
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -29963,7 +29966,7 @@ var dispatchToolSpecs = [
|
|
|
29963
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.",
|
|
29964
29967
|
arguments: (z) => ({
|
|
29965
29968
|
ask: z.string().describe("Ask id to edit."),
|
|
29966
|
-
question: z.string({ max:
|
|
29969
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
|
|
29967
29970
|
options: z.array(z.object({
|
|
29968
29971
|
label: z.string().describe("Selectable option label."),
|
|
29969
29972
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -31194,9 +31197,9 @@ function resolveOptions(options) {
|
|
|
31194
31197
|
|
|
31195
31198
|
// ../envoy-client/src/ask-answer.ts
|
|
31196
31199
|
var HEAD_LENGTH = 120;
|
|
31197
|
-
function textHead(text
|
|
31200
|
+
function textHead(text) {
|
|
31198
31201
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
31199
|
-
return flat.length >
|
|
31202
|
+
return flat.length > HEAD_LENGTH ? `${flat.slice(0, HEAD_LENGTH)}\u2026` : flat;
|
|
31200
31203
|
}
|
|
31201
31204
|
function askAnswerText(answer) {
|
|
31202
31205
|
if (answer === null || answer === undefined)
|
|
@@ -31210,6 +31213,29 @@ function askAnswerText(answer) {
|
|
|
31210
31213
|
return `${selected} - ${text}`;
|
|
31211
31214
|
}
|
|
31212
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
|
+
|
|
31213
31239
|
// ../envoy-client/src/delivery.ts
|
|
31214
31240
|
var KNOWN_SOURCES = EnvelopeSchema.shape.source.enum;
|
|
31215
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;
|
|
@@ -31247,6 +31273,31 @@ var TolerantInboundEnvelopeSchema = object({
|
|
|
31247
31273
|
source: InboundEnvelopeSchema.shape.source.optional().catch(undefined),
|
|
31248
31274
|
sender: object(tolerantShape(InboundSenderSchema.shape)).optional().catch(undefined)
|
|
31249
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
|
+
}
|
|
31250
31301
|
var DispatchDeliveryRequestSchema = object({
|
|
31251
31302
|
attempt: number2().int().positive(),
|
|
31252
31303
|
mode: _enum2(DELIVERY_CAPABILITIES)
|
|
@@ -31313,23 +31364,20 @@ function dispatchOwner(event, topic) {
|
|
|
31313
31364
|
return `${document.project} / ${document.slug}`;
|
|
31314
31365
|
return event.artifact_id ?? "unknown";
|
|
31315
31366
|
}
|
|
31316
|
-
function dispatchCommentReplyWith(event, topic) {
|
|
31317
|
-
if (
|
|
31318
|
-
return;
|
|
31319
|
-
const comment = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31320
|
-
if (!comment.success || comment.data.id === undefined)
|
|
31367
|
+
function dispatchCommentReplyWith(event, topic, comment) {
|
|
31368
|
+
if (comment === undefined || comment.id === undefined)
|
|
31321
31369
|
return;
|
|
31322
|
-
const reply = comment.
|
|
31370
|
+
const reply = comment.ask_id;
|
|
31323
31371
|
const thread = reply === undefined || reply === null || reply === "" ? "reply_to" : "reply_to_ask";
|
|
31324
|
-
const threadID = thread === "reply_to" ? comment.
|
|
31372
|
+
const threadID = thread === "reply_to" ? comment.id : String(reply);
|
|
31325
31373
|
if (event.issue_key !== null) {
|
|
31326
31374
|
return {
|
|
31327
31375
|
tool: "dispatch_comment",
|
|
31328
31376
|
args: { issue: event.issue_key, [thread]: threadID, body: "..." }
|
|
31329
31377
|
};
|
|
31330
31378
|
}
|
|
31331
|
-
let project = comment.
|
|
31332
|
-
let artifact = comment.
|
|
31379
|
+
let project = comment.project_key;
|
|
31380
|
+
let artifact = comment.artifact_slug;
|
|
31333
31381
|
if (project === undefined || project === "" || artifact === undefined || artifact === "") {
|
|
31334
31382
|
[project, artifact] = dispatchOwner(event, topic).split(" / ", 2);
|
|
31335
31383
|
}
|
|
@@ -31358,13 +31406,10 @@ function dispatchPayload(event) {
|
|
|
31358
31406
|
const parsed = schema.safeParse(event.payload);
|
|
31359
31407
|
return parsed.success ? parsed.data : event.payload;
|
|
31360
31408
|
}
|
|
31361
|
-
function dispatchAskQuestion(
|
|
31362
|
-
|
|
31363
|
-
return;
|
|
31364
|
-
const parsed = CommentEventPayloadSchema.safeParse(event.payload);
|
|
31365
|
-
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;
|
|
31366
31411
|
}
|
|
31367
|
-
function dispatchReplyRef(event, topic, inReplyTo) {
|
|
31412
|
+
function dispatchReplyRef(event, topic, inReplyTo, comment) {
|
|
31368
31413
|
let kind;
|
|
31369
31414
|
let document = topicDocument(topic);
|
|
31370
31415
|
if (event.type.startsWith("ask.")) {
|
|
@@ -31372,23 +31417,23 @@ function dispatchReplyRef(event, topic, inReplyTo) {
|
|
|
31372
31417
|
} else if (event.type.startsWith("message.")) {
|
|
31373
31418
|
kind = "message";
|
|
31374
31419
|
} else if (event.type === "comment.created") {
|
|
31375
|
-
|
|
31376
|
-
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) {
|
|
31377
31421
|
return inReplyTo;
|
|
31378
31422
|
}
|
|
31379
31423
|
kind = "ask";
|
|
31380
|
-
const { project_key: project, artifact_slug: slug } = comment
|
|
31424
|
+
const { project_key: project, artifact_slug: slug } = comment;
|
|
31381
31425
|
if (project !== undefined && project !== "" && slug !== undefined && slug !== "") {
|
|
31382
31426
|
document = { project, slug };
|
|
31383
31427
|
}
|
|
31384
31428
|
} else {
|
|
31385
31429
|
return inReplyTo;
|
|
31386
31430
|
}
|
|
31387
|
-
if (event.issue_key !== null)
|
|
31388
|
-
return
|
|
31431
|
+
if (event.issue_key !== null) {
|
|
31432
|
+
return dispatchChildRef(dispatchIssueRef(event.issue_key), kind, inReplyTo);
|
|
31433
|
+
}
|
|
31389
31434
|
if (document === undefined)
|
|
31390
31435
|
return inReplyTo;
|
|
31391
|
-
return
|
|
31436
|
+
return dispatchChildRef(dispatchDocumentRef(document.project, document.slug), kind, inReplyTo);
|
|
31392
31437
|
}
|
|
31393
31438
|
function isObject2(value) {
|
|
31394
31439
|
return typeof value === "object" && value !== null;
|
|
@@ -31400,16 +31445,16 @@ function parseDispatchFrame(rawPayload) {
|
|
|
31400
31445
|
} catch {
|
|
31401
31446
|
return { raw: rawPayload };
|
|
31402
31447
|
}
|
|
31403
|
-
const targeted = DispatchTargetedFrameSchema.safeParse(value);
|
|
31404
|
-
if (targeted.success) {
|
|
31405
|
-
const wireFrame = value;
|
|
31406
|
-
return {
|
|
31407
|
-
event: targeted.data.event,
|
|
31408
|
-
notify: wireFrame.event.notify,
|
|
31409
|
-
delivery: targeted.data.delivery
|
|
31410
|
-
};
|
|
31411
|
-
}
|
|
31412
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
|
+
}
|
|
31413
31458
|
const recoverable = RecoverableDispatchDeliveryFailureSchema.safeParse(value);
|
|
31414
31459
|
if (recoverable.success) {
|
|
31415
31460
|
return {
|
|
@@ -31481,12 +31526,13 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31481
31526
|
let askAuthor;
|
|
31482
31527
|
const dispatchRendered = envelope.source === "dispatch" && envelope.payload !== undefined;
|
|
31483
31528
|
if (envelope.source === "dispatch") {
|
|
31529
|
+
const topic = subject ?? envelope.topic;
|
|
31484
31530
|
if (envelope.payload === undefined) {
|
|
31485
31531
|
dispatchIssue = "payload";
|
|
31486
31532
|
} else {
|
|
31487
31533
|
const frame = parseDispatchFrame(envelope.payload);
|
|
31488
31534
|
if ("event" in frame) {
|
|
31489
|
-
if (frame.notify === false &&
|
|
31535
|
+
if (frame.notify === false && topic?.startsWith(DISPATCH_TOPIC_PREFIX) === true) {
|
|
31490
31536
|
return { skip: true, content: "", envelope };
|
|
31491
31537
|
}
|
|
31492
31538
|
if (frame.event.actor.kind === "session" && frame.event.actor.id === sessionID) {
|
|
@@ -31500,7 +31546,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31500
31546
|
const who = removed.data.by?.id ?? "someone";
|
|
31501
31547
|
return {
|
|
31502
31548
|
skip: false,
|
|
31503
|
-
content: `Unsubscribed from ${dispatchOwner(frame.event,
|
|
31549
|
+
content: `Unsubscribed from ${dispatchOwner(frame.event, topic)} by ${who}`,
|
|
31504
31550
|
envelope
|
|
31505
31551
|
};
|
|
31506
31552
|
}
|
|
@@ -31510,7 +31556,7 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31510
31556
|
return { skip: true, content: "", envelope };
|
|
31511
31557
|
}
|
|
31512
31558
|
const who = follower.data.by?.id ?? "someone";
|
|
31513
|
-
const owner = dispatchOwner(frame.event,
|
|
31559
|
+
const owner = dispatchOwner(frame.event, topic);
|
|
31514
31560
|
const ask = follower.data.ask_id ?? "?";
|
|
31515
31561
|
return {
|
|
31516
31562
|
skip: false,
|
|
@@ -31518,8 +31564,10 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31518
31564
|
envelope
|
|
31519
31565
|
};
|
|
31520
31566
|
}
|
|
31567
|
+
const comment = frame.event.type === "comment.created" ? CommentEventPayloadSchema.safeParse(frame.event.payload) : undefined;
|
|
31568
|
+
const commentPayload = comment?.success === true ? comment.data : undefined;
|
|
31521
31569
|
const answered = dispatchAskAnswer(frame.event);
|
|
31522
|
-
const question = answered?.question ?? dispatchAskQuestion(
|
|
31570
|
+
const question = answered?.question ?? dispatchAskQuestion(commentPayload);
|
|
31523
31571
|
if (frame.event.type.startsWith("ask.")) {
|
|
31524
31572
|
const asked = AskAuthorPayloadSchema.safeParse(frame.event.payload);
|
|
31525
31573
|
if (asked.success && asked.data.author.kind === "session") {
|
|
@@ -31527,9 +31575,9 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31527
31575
|
}
|
|
31528
31576
|
}
|
|
31529
31577
|
if (inReplyTo !== undefined) {
|
|
31530
|
-
inReplyTo = dispatchReplyRef(frame.event,
|
|
31578
|
+
inReplyTo = dispatchReplyRef(frame.event, topic, inReplyTo, commentPayload);
|
|
31531
31579
|
}
|
|
31532
|
-
dispatchReply = dispatchCommentReplyWith(frame.event,
|
|
31580
|
+
dispatchReply = dispatchCommentReplyWith(frame.event, topic, commentPayload);
|
|
31533
31581
|
if (frame.event.type === "message.created") {
|
|
31534
31582
|
const message = MessageEventPayloadSchema.safeParse(frame.event.payload);
|
|
31535
31583
|
const requested = DispatchDeliveryRequestSchema.safeParse(frame.delivery);
|
|
@@ -31550,11 +31598,9 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31550
31598
|
}
|
|
31551
31599
|
}
|
|
31552
31600
|
dispatchEvent = {
|
|
31553
|
-
owner: dispatchOwner(frame.event,
|
|
31601
|
+
owner: dispatchOwner(frame.event, topic),
|
|
31554
31602
|
...frame.event.issue_key === null ? {
|
|
31555
|
-
...
|
|
31556
|
-
document: dispatchOwner(frame.event, subject ?? envelope.topic).replace(" / ", "/")
|
|
31557
|
-
} : {},
|
|
31603
|
+
...topic?.startsWith(DISPATCH_DOCUMENT_TOPIC_PREFIX) === true ? { document: dispatchOwner(frame.event, topic).replace(" / ", "/") } : {},
|
|
31558
31604
|
...frame.event.artifact_id === undefined || frame.event.artifact_id === null ? {} : { artifact_id: frame.event.artifact_id }
|
|
31559
31605
|
} : { issue_key: frame.event.issue_key },
|
|
31560
31606
|
type: frame.event.type,
|
|
@@ -31582,14 +31628,16 @@ function renderInbound(raw, sessionID, subject) {
|
|
|
31582
31628
|
const payloadSummary = envelope.payload_summary ?? "unknown";
|
|
31583
31629
|
const message = !dispatchRendered && envelope.payload !== undefined && envelope.payload !== envelope.payload_summary ? parsedPayload : undefined;
|
|
31584
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);
|
|
31585
|
-
const body = `${envelope.payload_summary ?? ""}
|
|
31586
|
-
${envelope.payload ?? ""}`;
|
|
31587
31631
|
let foreignSession;
|
|
31588
|
-
for (const
|
|
31589
|
-
|
|
31590
|
-
|
|
31591
|
-
|
|
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
|
+
}
|
|
31592
31638
|
}
|
|
31639
|
+
if (foreignSession !== undefined)
|
|
31640
|
+
break;
|
|
31593
31641
|
}
|
|
31594
31642
|
const role = envelope.sender?.roles?.[0];
|
|
31595
31643
|
const reply = dispatchReply ?? replyWith(envelope);
|
|
@@ -32300,14 +32348,8 @@ function formatZodIssues(issues, schema) {
|
|
|
32300
32348
|
}
|
|
32301
32349
|
|
|
32302
32350
|
// ../envoy-client/src/dispatch-execute.ts
|
|
32303
|
-
function issueTopic(key) {
|
|
32304
|
-
return { label: key, topic: dispatchIssueSubject(key, ">") };
|
|
32305
|
-
}
|
|
32306
32351
|
function documentTopic(artifact) {
|
|
32307
|
-
return
|
|
32308
|
-
label: `${artifact.project}/${artifact.slug}`,
|
|
32309
|
-
topic: dispatchDocumentSubject(artifact.project, artifact.slug, ">")
|
|
32310
|
-
};
|
|
32352
|
+
return documentTopicOf(artifact.project, artifact.slug);
|
|
32311
32353
|
}
|
|
32312
32354
|
function resolvedTopic(resolved) {
|
|
32313
32355
|
if (resolved.owner.kind === "project")
|
|
@@ -32326,7 +32368,7 @@ function documentResultDetails(artifact) {
|
|
|
32326
32368
|
return {
|
|
32327
32369
|
project: artifact.project,
|
|
32328
32370
|
artifact: artifact.id,
|
|
32329
|
-
document:
|
|
32371
|
+
document: documentLabel(artifact.project, artifact.slug)
|
|
32330
32372
|
};
|
|
32331
32373
|
}
|
|
32332
32374
|
function writeResultDetails(resolved, fields) {
|
|
@@ -32337,15 +32379,15 @@ function writeResultDetails(resolved, fields) {
|
|
|
32337
32379
|
throw new Error("issue document is missing its issue");
|
|
32338
32380
|
return { issue: resolved.issue.key, ...fields };
|
|
32339
32381
|
}
|
|
32340
|
-
async function askOwnerDetails(client, ask,
|
|
32382
|
+
async function askOwnerDetails(client, ask, artifact) {
|
|
32341
32383
|
if (ask.issue_key !== null) {
|
|
32342
32384
|
return { issue: ask.issue_key, ask: ask.id };
|
|
32343
32385
|
}
|
|
32344
32386
|
if (ask.artifact_id === undefined || ask.artifact_id === null) {
|
|
32345
32387
|
throw new Error("document ask is missing its artifact ID");
|
|
32346
32388
|
}
|
|
32347
|
-
const
|
|
32348
|
-
return { ...documentResultDetails(
|
|
32389
|
+
const owner = artifact ?? await client.getArtifact(ask.artifact_id);
|
|
32390
|
+
return { ...documentResultDetails(owner), ask: ask.id };
|
|
32349
32391
|
}
|
|
32350
32392
|
async function commentOwnerResult(client, comment, artifact) {
|
|
32351
32393
|
if (comment.issue_key !== null) {
|
|
@@ -32356,12 +32398,12 @@ async function commentOwnerResult(client, comment, artifact) {
|
|
|
32356
32398
|
}
|
|
32357
32399
|
const owner = artifact ?? await client.getArtifact(comment.artifact_id);
|
|
32358
32400
|
return {
|
|
32359
|
-
label:
|
|
32401
|
+
label: documentLabel(owner.project, owner.slug),
|
|
32360
32402
|
details: { ...documentResultDetails(owner), comment: comment.id }
|
|
32361
32403
|
};
|
|
32362
32404
|
}
|
|
32363
|
-
async function followedAskDetails(client, ask,
|
|
32364
|
-
return { ...await askOwnerDetails(client, ask,
|
|
32405
|
+
async function followedAskDetails(client, ask, artifact) {
|
|
32406
|
+
return { ...await askOwnerDetails(client, ask, artifact), follows: { ask: ask.id } };
|
|
32365
32407
|
}
|
|
32366
32408
|
var nativeIssueKeyPattern = /^[A-Z][A-Z0-9]{1,9}-[0-9]+$/;
|
|
32367
32409
|
var externalIssueRefPattern = /^([^/\s]+)\/([^/\s#]+)#([1-9][0-9]*)$/;
|
|
@@ -32436,7 +32478,7 @@ function searchResultLine(result, baseUrl) {
|
|
|
32436
32478
|
const href = new URL(result.href, baseUrl).toString();
|
|
32437
32479
|
const { owner } = result;
|
|
32438
32480
|
if (owner.kind === "document") {
|
|
32439
|
-
const reference =
|
|
32481
|
+
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
32440
32482
|
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
32441
32483
|
}
|
|
32442
32484
|
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
@@ -32447,7 +32489,6 @@ function askUrgency(args) {
|
|
|
32447
32489
|
const value = args.urgency;
|
|
32448
32490
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
32449
32491
|
}
|
|
32450
|
-
var maxAskQuestion16 = 800;
|
|
32451
32492
|
function questionWithRef(question, ref) {
|
|
32452
32493
|
return ref === undefined || question.includes(ref) ? question : `${question}
|
|
32453
32494
|
|
|
@@ -32457,7 +32498,7 @@ function askQuestionWithRef(args) {
|
|
|
32457
32498
|
return questionWithRef(stringArg(args, "question"), optionalString(args, "ref"));
|
|
32458
32499
|
}
|
|
32459
32500
|
function askQuestionProblem(withRef) {
|
|
32460
|
-
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;
|
|
32461
32502
|
}
|
|
32462
32503
|
function parseDispatchRef(ref) {
|
|
32463
32504
|
const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
|
|
@@ -32512,6 +32553,10 @@ function parseDispatchRef(ref) {
|
|
|
32512
32553
|
return { owner, kind: "message", id: message };
|
|
32513
32554
|
return { owner, kind: "issue", id: issue };
|
|
32514
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
|
+
}
|
|
32515
32560
|
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
32516
32561
|
var askIdProblem = "ask must be a bare ask id or a dispatch://.../ask/<id> reference";
|
|
32517
32562
|
var commentIdProblem = "comment must be a bare comment id or a dispatch://.../comment/<id> reference";
|
|
@@ -32555,7 +32600,7 @@ function argumentProblems(tool, args) {
|
|
|
32555
32600
|
case "dispatch_ask": {
|
|
32556
32601
|
const question = optionalString(args, "question");
|
|
32557
32602
|
const ref = optionalString(args, "ref");
|
|
32558
|
-
if (question !== undefined && ref !== undefined && question.length <=
|
|
32603
|
+
if (question !== undefined && ref !== undefined && question.length <= ASK_QUESTION_MAX) {
|
|
32559
32604
|
const problem = askQuestionProblem(questionWithRef(question, ref));
|
|
32560
32605
|
if (problem !== undefined)
|
|
32561
32606
|
problems.push(problem);
|
|
@@ -32645,11 +32690,17 @@ function dispatchRefFromUrl(value, serverUrl) {
|
|
|
32645
32690
|
}
|
|
32646
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";
|
|
32647
32692
|
var ownerRequiredProblem = "issue is required; supply issue or set LEGION_ISSUE";
|
|
32693
|
+
var toolSchemas = new Map;
|
|
32648
32694
|
function toolSchema(tool) {
|
|
32695
|
+
const cached = toolSchemas.get(tool);
|
|
32696
|
+
if (cached !== undefined)
|
|
32697
|
+
return cached;
|
|
32649
32698
|
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
32650
32699
|
if (!spec)
|
|
32651
32700
|
throw new Error(`Unknown Dispatch tool: ${tool}`);
|
|
32652
|
-
|
|
32701
|
+
const schema = dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
|
|
32702
|
+
toolSchemas.set(tool, schema);
|
|
32703
|
+
return schema;
|
|
32653
32704
|
}
|
|
32654
32705
|
async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, problems) {
|
|
32655
32706
|
if (issueFreeTools[tool] === true)
|
|
@@ -32857,11 +32908,21 @@ function referenceLines(edges) {
|
|
|
32857
32908
|
return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
|
|
32858
32909
|
});
|
|
32859
32910
|
}
|
|
32911
|
+
function unavailableReason(error) {
|
|
32912
|
+
return error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${messageFor(error)}`;
|
|
32913
|
+
}
|
|
32860
32914
|
async function graphEdges(client, query) {
|
|
32861
32915
|
try {
|
|
32862
32916
|
return (await client.getReferences(query)).edges;
|
|
32863
32917
|
} catch (error) {
|
|
32864
|
-
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);
|
|
32865
32926
|
}
|
|
32866
32927
|
}
|
|
32867
32928
|
async function graphSections(client, ref) {
|
|
@@ -33071,8 +33132,9 @@ async function executeDispatchTool(input) {
|
|
|
33071
33132
|
problems.push(...argumentProblems(input.tool, ownerArguments.args));
|
|
33072
33133
|
if (problems.length > 0)
|
|
33073
33134
|
throw new ToolInputError(input.tool, problems);
|
|
33135
|
+
const dispatchClient = () => new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
33074
33136
|
if (input.tool === "dispatch_open_asks") {
|
|
33075
|
-
const client =
|
|
33137
|
+
const client = dispatchClient();
|
|
33076
33138
|
const project = optionalString(ownerArguments.args, "project");
|
|
33077
33139
|
if (project !== undefined) {
|
|
33078
33140
|
const response = await client.openAsksForProject(project);
|
|
@@ -33088,7 +33150,7 @@ async function executeDispatchTool(input) {
|
|
|
33088
33150
|
const sessionId = input.sessionId?.trim();
|
|
33089
33151
|
if (!sessionId)
|
|
33090
33152
|
throw new Error("host session id is required for dispatch_whoami");
|
|
33091
|
-
const client =
|
|
33153
|
+
const client = dispatchClient();
|
|
33092
33154
|
const identity = await client.whoami();
|
|
33093
33155
|
const owner = identity.kind === "agent" ? identity.owner : identity.login.toLowerCase();
|
|
33094
33156
|
return {
|
|
@@ -33098,7 +33160,7 @@ async function executeDispatchTool(input) {
|
|
|
33098
33160
|
}
|
|
33099
33161
|
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
33100
33162
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
33101
|
-
const client =
|
|
33163
|
+
const client = dispatchClient();
|
|
33102
33164
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
33103
33165
|
kind: "issue",
|
|
33104
33166
|
issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
|
|
@@ -33337,7 +33399,7 @@ async function executeDispatchTool(input) {
|
|
|
33337
33399
|
return {
|
|
33338
33400
|
text: `Asked ${ask.id} on ${askOwner.label} (urgency ${ask.urgency}): ${ask.question}
|
|
33339
33401
|
${followsAsk(askOwner)}`,
|
|
33340
|
-
details: await followedAskDetails(client, ask, resolved)
|
|
33402
|
+
details: await followedAskDetails(client, ask, resolved?.artifact)
|
|
33341
33403
|
};
|
|
33342
33404
|
}
|
|
33343
33405
|
case "dispatch_edit_ask": {
|
|
@@ -33419,7 +33481,7 @@ ${followsAsk(askOwner)}`,
|
|
|
33419
33481
|
...inReplyTo === undefined ? {} : { in_reply_to: inReplyTo },
|
|
33420
33482
|
actor
|
|
33421
33483
|
});
|
|
33422
|
-
const messageRef =
|
|
33484
|
+
const messageRef = dispatchChildRef(dispatchIssueRef(issueKey), "message", message.id);
|
|
33423
33485
|
return {
|
|
33424
33486
|
text: `Posted message ${message.id} (${messageRef}) ${notSubscribed(issueTopic(issueKey))}`,
|
|
33425
33487
|
details: { issue: issueKey, message: message.id }
|
|
@@ -33481,7 +33543,7 @@ ${trailer.join(`
|
|
|
33481
33543
|
}
|
|
33482
33544
|
};
|
|
33483
33545
|
}
|
|
33484
|
-
const details = await followedAskDetails(client, result.ask, resolved);
|
|
33546
|
+
const details = await followedAskDetails(client, result.ask, resolved.artifact);
|
|
33485
33547
|
return {
|
|
33486
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.`,
|
|
33487
33549
|
details: { ...details, artifact: resolved.artifact.id, version: result.version }
|
|
@@ -33504,7 +33566,7 @@ ${trailer.join(`
|
|
|
33504
33566
|
};
|
|
33505
33567
|
const artifactOwner = documentOwner();
|
|
33506
33568
|
const result = artifactOwner.kind === "project" ? await client.projectArtifact(artifactOwner.project, artifactInput) : await client.artifact(issue(), artifactInput);
|
|
33507
|
-
const artifactRef = artifactOwner.kind === "project" ?
|
|
33569
|
+
const artifactRef = dispatchDocumentRef(artifactOwner.kind === "project" ? artifactOwner.project : issue(), result.artifact.slug);
|
|
33508
33570
|
const uploadOwner = artifactOwner.kind === "project" ? documentTopic(result.artifact) : issueTopic(issue());
|
|
33509
33571
|
return {
|
|
33510
33572
|
text: `Uploaded ${result.artifact.name} as version ${result.version.number} (artifact slug ${result.artifact.slug}; ${artifactRef}) ${notSubscribed(uploadOwner)}`,
|
|
@@ -33540,7 +33602,7 @@ ${trailer.join(`
|
|
|
33540
33602
|
const ref = ownerArguments.ref;
|
|
33541
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"));
|
|
33542
33604
|
const askRead = await client.getAsk(id);
|
|
33543
|
-
const askRef = ref
|
|
33605
|
+
const askRef = refTarget(ref, "ask", id);
|
|
33544
33606
|
return {
|
|
33545
33607
|
text: askSummary(askRead, await graphSections(client, askRef)),
|
|
33546
33608
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue }
|
|
@@ -33550,7 +33612,7 @@ ${trailer.join(`
|
|
|
33550
33612
|
const ref = ownerArguments.ref;
|
|
33551
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));
|
|
33552
33614
|
const comment = await client.getComment(id);
|
|
33553
|
-
const commentRef = ref
|
|
33615
|
+
const commentRef = refTarget(ref, "comment", id);
|
|
33554
33616
|
return {
|
|
33555
33617
|
text: commentSummary(comment, await graphSections(client, commentRef)),
|
|
33556
33618
|
details: ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: comment.comment.issue_key }
|
|
@@ -33561,7 +33623,7 @@ ${trailer.join(`
|
|
|
33561
33623
|
throw new Error("message references are issue-scoped");
|
|
33562
33624
|
}
|
|
33563
33625
|
const messageRead = await client.getMessage(ownerArguments.ref.owner.issue, ownerArguments.ref.id);
|
|
33564
|
-
const messageRef =
|
|
33626
|
+
const messageRef = dispatchChildRef(dispatchIssueRef(ownerArguments.ref.owner.issue), "message", ownerArguments.ref.id);
|
|
33565
33627
|
return {
|
|
33566
33628
|
text: messageSummary(messageRead, await graphSections(client, messageRef)),
|
|
33567
33629
|
details: { issue: messageRead.message.issue_key }
|
|
@@ -33569,7 +33631,7 @@ ${trailer.join(`
|
|
|
33569
33631
|
}
|
|
33570
33632
|
if (documentOwner().kind === "project") {
|
|
33571
33633
|
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
33572
|
-
const documentRef =
|
|
33634
|
+
const documentRef = dispatchDocumentRef(resolved.artifact.project, resolved.artifact.slug);
|
|
33573
33635
|
return {
|
|
33574
33636
|
text: [
|
|
33575
33637
|
`Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
|
|
@@ -33581,7 +33643,7 @@ ${trailer.join(`
|
|
|
33581
33643
|
`),
|
|
33582
33644
|
details: {
|
|
33583
33645
|
project: resolved.artifact.project,
|
|
33584
|
-
document:
|
|
33646
|
+
document: documentLabel(resolved.artifact.project, resolved.artifact.slug)
|
|
33585
33647
|
}
|
|
33586
33648
|
};
|
|
33587
33649
|
}
|
|
@@ -33598,14 +33660,12 @@ ${trailer.join(`
|
|
|
33598
33660
|
details: { issue: read.issue.key }
|
|
33599
33661
|
};
|
|
33600
33662
|
}
|
|
33601
|
-
|
|
33602
|
-
|
|
33603
|
-
|
|
33604
|
-
|
|
33605
|
-
references = error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
33606
|
-
}
|
|
33663
|
+
const [references, graph] = await Promise.all([
|
|
33664
|
+
issueReferencesOrUnavailable(client, read.issue.key),
|
|
33665
|
+
graphSections(client, dispatchIssueRef(read.issue.key))
|
|
33666
|
+
]);
|
|
33607
33667
|
return {
|
|
33608
|
-
text: issueSummary(read.issue, read.events, references,
|
|
33668
|
+
text: issueSummary(read.issue, read.events, references, graph),
|
|
33609
33669
|
details: { issue: read.issue.key }
|
|
33610
33670
|
};
|
|
33611
33671
|
}
|
|
@@ -33640,12 +33700,12 @@ function dispatchFollowNotice(details) {
|
|
|
33640
33700
|
return null;
|
|
33641
33701
|
let owner;
|
|
33642
33702
|
if (typeof issue === "string" && issue !== "") {
|
|
33643
|
-
owner =
|
|
33703
|
+
owner = issueTopic(issue);
|
|
33644
33704
|
} else if (typeof document === "string") {
|
|
33645
33705
|
const [project, slug] = document.split("/", 2);
|
|
33646
33706
|
if (!project || !slug)
|
|
33647
33707
|
return null;
|
|
33648
|
-
owner = { label: document, topic:
|
|
33708
|
+
owner = { label: document, topic: documentTopicOf(project, slug).topic };
|
|
33649
33709
|
} else {
|
|
33650
33710
|
return null;
|
|
33651
33711
|
}
|
|
@@ -33654,6 +33714,16 @@ function dispatchFollowNotice(details) {
|
|
|
33654
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}.`
|
|
33655
33715
|
};
|
|
33656
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
|
+
}
|
|
33657
33727
|
function subscriptionRemovedTopics(raw, sessionID) {
|
|
33658
33728
|
let envelope;
|
|
33659
33729
|
try {
|
|
@@ -33801,11 +33871,19 @@ var envoyToolSpecs = [
|
|
|
33801
33871
|
requiresSubscriptionCapability: false
|
|
33802
33872
|
}
|
|
33803
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;
|
|
33804
33878
|
function parseEnvoyToolArguments(operation, parameters) {
|
|
33805
33879
|
const spec = envoyToolSpecs.find((candidate) => candidate.operation === operation);
|
|
33806
33880
|
if (spec === undefined)
|
|
33807
33881
|
throw new Error(`missing Envoy tool specification for ${operation}`);
|
|
33808
|
-
|
|
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
|
+
}
|
|
33809
33887
|
const parsed = schema.safeParse(parameters, { reportInput: true });
|
|
33810
33888
|
if (!parsed.success) {
|
|
33811
33889
|
throw new ToolInputError(spec.name, formatZodIssues(parsed.error.issues, schema));
|
|
@@ -33854,6 +33932,17 @@ var EnvelopeResponseSchema = EnvelopeSchema.extend({
|
|
|
33854
33932
|
recipient: string2().optional(),
|
|
33855
33933
|
holder: string2().optional()
|
|
33856
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
|
+
}
|
|
33857
33946
|
function expandSubscriptionTopics(topics) {
|
|
33858
33947
|
const expanded = new Set;
|
|
33859
33948
|
for (const topic of topics) {
|
|
@@ -34135,7 +34224,6 @@ async function copySessionID(copyToClipboard, sessionID) {
|
|
|
34135
34224
|
// extensions/envoy.ts
|
|
34136
34225
|
var codec2 = import_nats.StringCodec();
|
|
34137
34226
|
var NATS_RETRY_INTERVAL_MS = 15000;
|
|
34138
|
-
var ALL_CAPABILITIES = DELIVERY_CAPABILITIES;
|
|
34139
34227
|
var CAPABILITIES_WITHOUT_BTW = DELIVERY_CAPABILITIES.filter((capability) => capability !== "btw");
|
|
34140
34228
|
var ROLE_CLAIM_ENTRY = "envoy-role-claim";
|
|
34141
34229
|
var OPEN_ASKS_TIMEOUT_MS = 3000;
|
|
@@ -34208,7 +34296,7 @@ function envoyExtension(pi) {
|
|
|
34208
34296
|
return null;
|
|
34209
34297
|
const snapshot = await new DispatchClient(config.url, config.token, fetch, AbortSignal.timeout(OPEN_ASKS_TIMEOUT_MS)).openAsks(requestedSessionID);
|
|
34210
34298
|
availabilityWarningSessionIDs.delete(requestedSessionID);
|
|
34211
|
-
return snapshot;
|
|
34299
|
+
return { snapshot, url: config.url };
|
|
34212
34300
|
};
|
|
34213
34301
|
const restoreLocalSessionState = (context) => {
|
|
34214
34302
|
sessionDirectory = context.cwd;
|
|
@@ -34230,28 +34318,17 @@ function envoyExtension(pi) {
|
|
|
34230
34318
|
return connection;
|
|
34231
34319
|
};
|
|
34232
34320
|
const postDispatchReply = async (delivery, result) => {
|
|
34233
|
-
|
|
34234
|
-
const response = await fetch(`${config.url}/api/v1/messages/${delivery.messageID}/reply`, {
|
|
34235
|
-
method: "POST",
|
|
34236
|
-
headers: {
|
|
34237
|
-
Authorization: `Bearer ${config.token}`,
|
|
34238
|
-
"Content-Type": "application/json"
|
|
34239
|
-
},
|
|
34240
|
-
body: JSON.stringify({
|
|
34241
|
-
actor: { kind: "session", id: sessionID },
|
|
34242
|
-
attempt: delivery.attempt,
|
|
34243
|
-
...result
|
|
34244
|
-
})
|
|
34245
|
-
});
|
|
34246
|
-
if (!response.ok) {
|
|
34247
|
-
throw new Error(`Dispatch reply failed: ${response.status} ${await response.text()}`);
|
|
34248
|
-
}
|
|
34321
|
+
await postDeliveryReply(currentDispatchConfig(), sessionID, delivery, result);
|
|
34249
34322
|
};
|
|
34250
34323
|
const deliver = async (subject, raw, reply) => {
|
|
34251
34324
|
const rendered = renderInbound(raw, sessionID, subject);
|
|
34252
34325
|
const directSubject = agentSubject(sessionID);
|
|
34253
|
-
|
|
34254
|
-
|
|
34326
|
+
if (expectsLaneReceipt({
|
|
34327
|
+
subject,
|
|
34328
|
+
directSubject,
|
|
34329
|
+
envelopeTopic: rendered.envelope?.topic,
|
|
34330
|
+
reply
|
|
34331
|
+
})) {
|
|
34255
34332
|
try {
|
|
34256
34333
|
(await ensureConnection()).publish(reply);
|
|
34257
34334
|
} catch (error) {
|
|
@@ -34305,14 +34382,8 @@ function envoyExtension(pi) {
|
|
|
34305
34382
|
console.warn(`[envoy] failed to deliver envelope ${envelope?.event_id ?? "unknown"}`, error);
|
|
34306
34383
|
throw error;
|
|
34307
34384
|
}
|
|
34308
|
-
if (dedupeKey !== undefined)
|
|
34309
|
-
dedupeKeys
|
|
34310
|
-
if (dedupeKeys.size > 1000) {
|
|
34311
|
-
const oldest = dedupeKeys.values().next();
|
|
34312
|
-
if (!oldest.done)
|
|
34313
|
-
dedupeKeys.delete(oldest.value);
|
|
34314
|
-
}
|
|
34315
|
-
}
|
|
34385
|
+
if (dedupeKey !== undefined)
|
|
34386
|
+
rememberBounded(dedupeKeys, dedupeKey, 1000);
|
|
34316
34387
|
}
|
|
34317
34388
|
};
|
|
34318
34389
|
const RESUBSCRIBE_DELAY_MS = Number(process.env.ENVOY_RESUBSCRIBE_DELAY_MS ?? "") || 5000;
|
|
@@ -34378,7 +34449,7 @@ function envoyExtension(pi) {
|
|
|
34378
34449
|
topics: [...new Set([agentSubject(sessionID), ...subscriptions.keys()])],
|
|
34379
34450
|
port: 0,
|
|
34380
34451
|
title: activeSessionContext?.sessionManager.getSessionName?.() ?? "",
|
|
34381
|
-
capabilities: typeof pi.askEphemeral === "function" ?
|
|
34452
|
+
capabilities: typeof pi.askEphemeral === "function" ? DELIVERY_CAPABILITIES : CAPABILITIES_WITHOUT_BTW,
|
|
34382
34453
|
driving: false,
|
|
34383
34454
|
selfSubscribed: true
|
|
34384
34455
|
});
|
|
@@ -34734,14 +34805,14 @@ function envoyExtension(pi) {
|
|
|
34734
34805
|
if (id === "")
|
|
34735
34806
|
return;
|
|
34736
34807
|
try {
|
|
34737
|
-
const
|
|
34738
|
-
if (
|
|
34808
|
+
const open = await queryOpenAsks(id);
|
|
34809
|
+
if (open === null)
|
|
34739
34810
|
return;
|
|
34740
34811
|
return {
|
|
34741
34812
|
message: {
|
|
34742
34813
|
customType: "dispatch-open-asks",
|
|
34743
34814
|
content: `Dispatch authored-ask summary:
|
|
34744
|
-
${formatOpenAsksSummary(snapshot,
|
|
34815
|
+
${formatOpenAsksSummary(open.snapshot, open.url)}`,
|
|
34745
34816
|
display: false,
|
|
34746
34817
|
attribution: "agent"
|
|
34747
34818
|
}
|
|
@@ -34758,15 +34829,13 @@ ${formatOpenAsksSummary(snapshot, currentDispatchConfig().url)}`,
|
|
|
34758
34829
|
};
|
|
34759
34830
|
}
|
|
34760
34831
|
});
|
|
34761
|
-
const
|
|
34832
|
+
const announceFollow = createFollowAnnouncer((text) => {
|
|
34833
|
+
pi.sendMessage({ customType: "envoy-message", content: text, display: true }, { deliverAs: "steer", triggerTurn: false });
|
|
34834
|
+
});
|
|
34762
34835
|
pi.on("tool_result", async (event) => {
|
|
34763
34836
|
if (event.isError)
|
|
34764
34837
|
return;
|
|
34765
|
-
|
|
34766
|
-
if (notice === null || announcedFollows.has(notice.ask))
|
|
34767
|
-
return;
|
|
34768
|
-
announcedFollows.add(notice.ask);
|
|
34769
|
-
pi.sendMessage({ customType: "envoy-message", content: notice.text, display: true }, { deliverAs: "steer", triggerTurn: false });
|
|
34838
|
+
announceFollow(event.details);
|
|
34770
34839
|
});
|
|
34771
34840
|
async function execute(operation, rawParameters) {
|
|
34772
34841
|
try {
|
|
@@ -34816,14 +34885,7 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
34816
34885
|
}
|
|
34817
34886
|
case EnvoyToolOperation.listInterests: {
|
|
34818
34887
|
const registry = await client.getInterest(sessionID);
|
|
34819
|
-
const interests =
|
|
34820
|
-
for (const topic of registry.topics) {
|
|
34821
|
-
interests.set(topic, subscriptions.has(topic) ? "both" : "registry");
|
|
34822
|
-
}
|
|
34823
|
-
for (const topic of subscriptions.keys()) {
|
|
34824
|
-
if (!interests.has(topic))
|
|
34825
|
-
interests.set(topic, "live");
|
|
34826
|
-
}
|
|
34888
|
+
const interests = mergeInterestSources(registry.topics, [...subscriptions.keys()]);
|
|
34827
34889
|
return toolSuccess(JSON.stringify({ ...registry, topics: [...interests.keys()] }, null, 2), {
|
|
34828
34890
|
interests: [...interests].map(([topic, source]) => ({ topic, source }))
|
|
34829
34891
|
});
|
|
@@ -34838,8 +34900,7 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
34838
34900
|
message: stringFor(parameters, "message"),
|
|
34839
34901
|
...toMessageMetadata(parameters)
|
|
34840
34902
|
});
|
|
34841
|
-
|
|
34842
|
-
return toolSuccess(`sent ${result.envelope.event_id} to ${result.recipient}${confirmation}`, {
|
|
34903
|
+
return toolSuccess(sendConfirmationText(result), {
|
|
34843
34904
|
event_id: result.envelope.event_id,
|
|
34844
34905
|
recipient: result.recipient,
|
|
34845
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,14 @@ 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
|
+
};
|
|
29140
29139
|
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
29141
29140
|
function componentsArgument(z) {
|
|
29142
29141
|
return z.object({
|
|
@@ -29158,6 +29157,7 @@ var SPEC_SECTIONS = [
|
|
|
29158
29157
|
];
|
|
29159
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.";
|
|
29160
29159
|
var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
29160
|
+
var ASK_QUESTION_MAX = 800;
|
|
29161
29161
|
var ISSUE_STATUSES = [
|
|
29162
29162
|
"triage",
|
|
29163
29163
|
"icebox",
|
|
@@ -29169,6 +29169,9 @@ var ISSUE_STATUSES = [
|
|
|
29169
29169
|
"retro",
|
|
29170
29170
|
"done"
|
|
29171
29171
|
];
|
|
29172
|
+
function isIssueStatus(value) {
|
|
29173
|
+
return ISSUE_STATUSES.includes(value);
|
|
29174
|
+
}
|
|
29172
29175
|
var DOC_EDIT_OPS = ["replace", "delete", "insert", "retype", "move"];
|
|
29173
29176
|
var dispatchToolSpecs = [
|
|
29174
29177
|
{
|
|
@@ -29211,13 +29214,13 @@ var dispatchToolSpecs = [
|
|
|
29211
29214
|
},
|
|
29212
29215
|
{
|
|
29213
29216
|
name: "dispatch_ask",
|
|
29214
|
-
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}`,
|
|
29215
29218
|
arguments: (z) => ({
|
|
29216
29219
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
29217
29220
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
29218
29221
|
artifact: z.string().describe("Project document artifact id, slug, or filename.").optional(),
|
|
29219
29222
|
ref: z.string().describe("Optional dispatch:// reference (issue, document, message, or ask); appended to the question and rendered as a link.").optional(),
|
|
29220
|
-
question: z.string({ max:
|
|
29223
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Decision question, at most ${ASK_QUESTION_MAX} characters.`),
|
|
29221
29224
|
options: z.array(z.object({
|
|
29222
29225
|
label: z.string().describe("Selectable option label."),
|
|
29223
29226
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -29237,7 +29240,7 @@ var dispatchToolSpecs = [
|
|
|
29237
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.",
|
|
29238
29241
|
arguments: (z) => ({
|
|
29239
29242
|
ask: z.string().describe("Ask id to edit."),
|
|
29240
|
-
question: z.string({ max:
|
|
29243
|
+
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
|
|
29241
29244
|
options: z.array(z.object({
|
|
29242
29245
|
label: z.string().describe("Selectable option label."),
|
|
29243
29246
|
description: z.string().describe("Optional option context.").optional()
|
|
@@ -29990,11 +29993,7 @@ function generation(env) {
|
|
|
29990
29993
|
}
|
|
29991
29994
|
|
|
29992
29995
|
// src/legion/daemon-client.ts
|
|
29993
|
-
|
|
29994
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
29995
|
-
setTimeout(resolve, ms);
|
|
29996
|
-
return promise;
|
|
29997
|
-
}
|
|
29996
|
+
import { setTimeout as delay } from "timers/promises";
|
|
29998
29997
|
var SPAWN_WORKER_RETRY_DELAYS_MS = [2000, 5000];
|
|
29999
29998
|
var SPAWN_WORKER_ATTEMPTS = SPAWN_WORKER_RETRY_DELAYS_MS.length + 1;
|
|
30000
29999
|
|
|
@@ -30053,7 +30052,7 @@ function sessionCapability(body) {
|
|
|
30053
30052
|
return { sessionId: candidate.sessionId, secret: candidate.secret };
|
|
30054
30053
|
}
|
|
30055
30054
|
var WORKER_SESSION_PATH = "/legion/v1/worker-session";
|
|
30056
|
-
function createLegionDaemonClient(baseUrl, fetchFn = fetch, recovery, sleep =
|
|
30055
|
+
function createLegionDaemonClient(baseUrl, fetchFn = fetch, recovery, sleep = (ms) => delay(ms)) {
|
|
30057
30056
|
const endpoint = baseUrl.replace(/\/+$/, "");
|
|
30058
30057
|
const postOnce = async (path, body, schema, fetchImpl = fetchFn, onResponseReadError) => {
|
|
30059
30058
|
const response = await fetchImpl(`${endpoint}${path}`, {
|
|
@@ -30362,9 +30361,6 @@ function toolFailure(error) {
|
|
|
30362
30361
|
|
|
30363
30362
|
// src/legion/tools.ts
|
|
30364
30363
|
var jsonSuccess = (details) => toolSuccess(JSON.stringify(details), details);
|
|
30365
|
-
function isIssueStatus(value) {
|
|
30366
|
-
return ISSUE_STATUSES.includes(value);
|
|
30367
|
-
}
|
|
30368
30364
|
function isDispatchArtifactId(value) {
|
|
30369
30365
|
return LegionDaemonApi.GatesRegister.request.shape.artifactId.safeParse(value).success;
|
|
30370
30366
|
}
|
|
@@ -30623,7 +30619,7 @@ var JJ_LOG_REWRITE_WORDS = ["undo", "abandon"];
|
|
|
30623
30619
|
var JJ_OP_WORDS = ["op", "operation"];
|
|
30624
30620
|
var JJ_OP_LOG_REWRITE_WORDS = ["restore", "revert"];
|
|
30625
30621
|
var JJ_MENTION = /\bjj\b/;
|
|
30626
|
-
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`);
|
|
30627
30623
|
function jjLogRewriteMention(text) {
|
|
30628
30624
|
if (!JJ_MENTION.test(text))
|
|
30629
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
|
|
|
@@ -267,6 +269,8 @@ It returns the imported commit, or the recorded error when the model was rejecte
|
|
|
267
269
|
|
|
268
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.
|
|
269
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
|
+
|
|
270
274
|
**Anything that needs the human is an ask, or it does not exist.** An approval, a credential,
|
|
271
275
|
a setting only they can change, a review click, a conflict between two of their own rules - if
|
|
272
276
|
your work waits on it, open a `dispatch_ask` the moment you know, the action as the question.
|