@sjawhar/pi-legion-envoy 1.41.0 → 1.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/envoy.js +81 -13
- package/dist/legion.js +37 -28
- package/dist/skills/dispatch/SKILL.md +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ extension files it does not contain.
|
|
|
85
85
|
|
|
86
86
|
## Native Dispatch tools
|
|
87
87
|
|
|
88
|
-
The extension registers
|
|
88
|
+
The extension registers eighteen native Dispatch tools: `dispatch_issue`, `dispatch_issue_update`, `dispatch_ask`, `dispatch_edit_ask`,
|
|
89
89
|
`dispatch_resolve_ask`, `dispatch_resolve_comment`, `dispatch_follow`, `dispatch_comment`, `dispatch_suggest`,
|
|
90
90
|
`dispatch_message`, `dispatch_doc_edit`, `dispatch_doc_read`, `dispatch_request_approval`, `dispatch_artifact`,
|
|
91
91
|
`dispatch_read`, `dispatch_search`, `dispatch_open_asks`, and `dispatch_whoami`, when Dispatch configuration resolves both a base URL and bearer token.
|
|
@@ -112,7 +112,7 @@ whose trimmed contents are the token — how the Legion daemon delivers it to a
|
|
|
112
112
|
pane) wins over every other token source and never falls back when unreadable.
|
|
113
113
|
Omitting `dispatch.serverUrl` while `dispatch.enabled` is true targets
|
|
114
114
|
`http://localhost:8766`, the Go server's listen address. Invalid configuration,
|
|
115
|
-
an invalid URL, or an empty token leaves the
|
|
115
|
+
an invalid URL, or an empty token leaves the eighteen tools unavailable and
|
|
116
116
|
reports the source of the error.
|
|
117
117
|
|
|
118
118
|
Owner-scoped calls use either an issue (a native `KEY` or external `owner/repo#n` reference) or
|
package/dist/envoy.js
CHANGED
|
@@ -29876,6 +29876,17 @@ var SPEC_SECTIONS = [
|
|
|
29876
29876
|
];
|
|
29877
29877
|
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
29878
|
var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
29879
|
+
var ISSUE_STATUSES = [
|
|
29880
|
+
"triage",
|
|
29881
|
+
"icebox",
|
|
29882
|
+
"backlog",
|
|
29883
|
+
"todo",
|
|
29884
|
+
"in_progress",
|
|
29885
|
+
"testing",
|
|
29886
|
+
"needs_review",
|
|
29887
|
+
"retro",
|
|
29888
|
+
"done"
|
|
29889
|
+
];
|
|
29879
29890
|
var DOC_EDIT_OPS = ["replace", "delete", "insert", "retype", "move"];
|
|
29880
29891
|
var dispatchToolSpecs = [
|
|
29881
29892
|
{
|
|
@@ -29893,6 +29904,26 @@ var dispatchToolSpecs = [
|
|
|
29893
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()
|
|
29894
29905
|
})
|
|
29895
29906
|
},
|
|
29907
|
+
{
|
|
29908
|
+
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), or set its route. 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. Priority is the human's and is not settable here. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
29910
|
+
arguments: (z) => ({
|
|
29911
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29912
|
+
status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
29913
|
+
title: z.string({ min: 1 }).describe("Replacement title.").optional(),
|
|
29914
|
+
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
|
+
external_links: z.array(z.string({ min: 1 })).describe("URLs to link; merged into the issue's existing external links by URL.").optional(),
|
|
29916
|
+
route: z.string().describe("Route the issue to role:<name> or session:<id>; an empty string clears it.").optional()
|
|
29917
|
+
}),
|
|
29918
|
+
validation: {
|
|
29919
|
+
check: (value) => {
|
|
29920
|
+
const input = value;
|
|
29921
|
+
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string";
|
|
29922
|
+
},
|
|
29923
|
+
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, or route."
|
|
29924
|
+
},
|
|
29925
|
+
strict: true
|
|
29926
|
+
},
|
|
29896
29927
|
{
|
|
29897
29928
|
name: "dispatch_ask",
|
|
29898
29929
|
description: "Open a durable, answerable decision or human to-do on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. Use kind: action for a to-do a human must complete; it has fixed Done / Can't answers. " + "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 800 ' + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
@@ -30280,17 +30311,6 @@ var legionRole = _enum2(LEGION_ROLES);
|
|
|
30280
30311
|
var requiredUnknown = unknown().refine((value) => value !== undefined, {
|
|
30281
30312
|
message: "Required"
|
|
30282
30313
|
});
|
|
30283
|
-
var LIFECYCLE_STATUSES = [
|
|
30284
|
-
"triage",
|
|
30285
|
-
"icebox",
|
|
30286
|
-
"backlog",
|
|
30287
|
-
"todo",
|
|
30288
|
-
"in_progress",
|
|
30289
|
-
"testing",
|
|
30290
|
-
"needs_review",
|
|
30291
|
-
"retro",
|
|
30292
|
-
"done"
|
|
30293
|
-
];
|
|
30294
30314
|
var architectCapability = strictObject({
|
|
30295
30315
|
tree: nonEmptyString,
|
|
30296
30316
|
sessionId: nonEmptyString,
|
|
@@ -30322,7 +30342,7 @@ var stateTreeLocator = discriminatedUnion("runtime", [
|
|
|
30322
30342
|
var stateIssue = strictObject({
|
|
30323
30343
|
key: nonEmptyString,
|
|
30324
30344
|
title: string2(),
|
|
30325
|
-
status: _enum2(
|
|
30345
|
+
status: _enum2(ISSUE_STATUSES).optional(),
|
|
30326
30346
|
children: array(nonEmptyString),
|
|
30327
30347
|
parent: nonEmptyString.optional(),
|
|
30328
30348
|
lastAppliedSeq: number2().int().nonnegative().optional()
|
|
@@ -30491,7 +30511,7 @@ var LegionDaemonApi = {
|
|
|
30491
30511
|
},
|
|
30492
30512
|
IssueStatus: {
|
|
30493
30513
|
request: controllerIssue.extend({
|
|
30494
|
-
status: _enum2(
|
|
30514
|
+
status: _enum2(ISSUE_STATUSES),
|
|
30495
30515
|
tree: nonEmptyString.optional(),
|
|
30496
30516
|
sessionId: nonEmptyString.optional()
|
|
30497
30517
|
}),
|
|
@@ -31862,6 +31882,9 @@ class DispatchClient {
|
|
|
31862
31882
|
async getIssue(issue) {
|
|
31863
31883
|
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue)]);
|
|
31864
31884
|
}
|
|
31885
|
+
async updateIssue(issue, input) {
|
|
31886
|
+
return this.#json("PATCH", ["api", "v1", "issues", await this.#resolveIssue(issue)], input);
|
|
31887
|
+
}
|
|
31865
31888
|
async getIssueEvents(issue, after = 0, limit = 200) {
|
|
31866
31889
|
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "events"], undefined, { after, limit });
|
|
31867
31890
|
}
|
|
@@ -33043,6 +33066,51 @@ async function executeDispatchTool(input) {
|
|
|
33043
33066
|
};
|
|
33044
33067
|
}
|
|
33045
33068
|
}
|
|
33069
|
+
case "dispatch_issue_update": {
|
|
33070
|
+
const issueKey = issue();
|
|
33071
|
+
const status = optionalString(args, "status");
|
|
33072
|
+
const title = optionalString(args, "title");
|
|
33073
|
+
const route = optionalString(args, "route");
|
|
33074
|
+
const labels = Array.isArray(args.labels) ? args.labels : undefined;
|
|
33075
|
+
const requestedLinks = Array.isArray(args.external_links) ? [...new Set(args.external_links)] : undefined;
|
|
33076
|
+
let newLinks = [];
|
|
33077
|
+
try {
|
|
33078
|
+
const before = await client.getIssue(issueKey);
|
|
33079
|
+
const linked = before.external_links.map((link) => link.url);
|
|
33080
|
+
newLinks = requestedLinks?.filter((url) => !linked.includes(url)) ?? [];
|
|
33081
|
+
const after = await client.updateIssue(issueKey, {
|
|
33082
|
+
...status === undefined ? {} : { status },
|
|
33083
|
+
...title === undefined ? {} : { title },
|
|
33084
|
+
...labels === undefined ? {} : { labels },
|
|
33085
|
+
...route === undefined ? {} : { route },
|
|
33086
|
+
...requestedLinks === undefined ? {} : { external_links: [...before.external_links, ...newLinks.map((url) => ({ url }))] },
|
|
33087
|
+
actor
|
|
33088
|
+
});
|
|
33089
|
+
const linkCount = `(${after.external_links.length} ${after.external_links.length === 1 ? "link" : "links"})`;
|
|
33090
|
+
const changes = [
|
|
33091
|
+
...status === undefined ? [] : [`status ${before.status} -> ${after.status}`],
|
|
33092
|
+
...title === undefined ? [] : [`title "${after.title}"`],
|
|
33093
|
+
...labels === undefined ? [] : [after.labels.length === 0 ? "labels cleared" : `labels ${after.labels.join(", ")}`],
|
|
33094
|
+
...requestedLinks === undefined ? [] : [
|
|
33095
|
+
newLinks.length === 0 ? `already linked ${requestedLinks.join(", ")} ${linkCount}` : `linked ${newLinks.join(", ")} ${linkCount}`
|
|
33096
|
+
],
|
|
33097
|
+
...route === undefined ? [] : [after.route === null ? "route cleared" : `route ${after.route}`]
|
|
33098
|
+
];
|
|
33099
|
+
return {
|
|
33100
|
+
text: `${after.key}: ${changes.join("; ")} ${notSubscribed(issueTopic(after.key))}`,
|
|
33101
|
+
details: {
|
|
33102
|
+
issue: after.key,
|
|
33103
|
+
status: after.status,
|
|
33104
|
+
external_links: after.external_links.map((link) => link.url)
|
|
33105
|
+
}
|
|
33106
|
+
};
|
|
33107
|
+
} catch (error) {
|
|
33108
|
+
if (!(error instanceof DispatchServiceError))
|
|
33109
|
+
throw error;
|
|
33110
|
+
const taken = error.status === 500 && newLinks.length > 0 ? `; one of ${newLinks.join(", ")} may already be linked from another issue (a URL links exactly one issue)` : "";
|
|
33111
|
+
throw new DispatchServiceError(error.code, error.status, `${error.code}: ${error.message}${taken}`, error.candidates);
|
|
33112
|
+
}
|
|
33113
|
+
}
|
|
33046
33114
|
case "dispatch_search": {
|
|
33047
33115
|
const query = stringArg(args, "query");
|
|
33048
33116
|
const project = optionalString(args, "project");
|
package/dist/legion.js
CHANGED
|
@@ -29150,6 +29150,17 @@ var SPEC_SECTIONS = [
|
|
|
29150
29150
|
];
|
|
29151
29151
|
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
29152
|
var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
29153
|
+
var ISSUE_STATUSES = [
|
|
29154
|
+
"triage",
|
|
29155
|
+
"icebox",
|
|
29156
|
+
"backlog",
|
|
29157
|
+
"todo",
|
|
29158
|
+
"in_progress",
|
|
29159
|
+
"testing",
|
|
29160
|
+
"needs_review",
|
|
29161
|
+
"retro",
|
|
29162
|
+
"done"
|
|
29163
|
+
];
|
|
29153
29164
|
var DOC_EDIT_OPS = ["replace", "delete", "insert", "retype", "move"];
|
|
29154
29165
|
var dispatchToolSpecs = [
|
|
29155
29166
|
{
|
|
@@ -29167,6 +29178,26 @@ var dispatchToolSpecs = [
|
|
|
29167
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()
|
|
29168
29179
|
})
|
|
29169
29180
|
},
|
|
29181
|
+
{
|
|
29182
|
+
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), or set its route. 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. Priority is the human's and is not settable here. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
|
|
29184
|
+
arguments: (z) => ({
|
|
29185
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
29186
|
+
status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
|
|
29187
|
+
title: z.string({ min: 1 }).describe("Replacement title.").optional(),
|
|
29188
|
+
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
|
+
external_links: z.array(z.string({ min: 1 })).describe("URLs to link; merged into the issue's existing external links by URL.").optional(),
|
|
29190
|
+
route: z.string().describe("Route the issue to role:<name> or session:<id>; an empty string clears it.").optional()
|
|
29191
|
+
}),
|
|
29192
|
+
validation: {
|
|
29193
|
+
check: (value) => {
|
|
29194
|
+
const input = value;
|
|
29195
|
+
return typeof input.status === "string" || typeof input.title === "string" || Array.isArray(input.labels) || Array.isArray(input.external_links) || typeof input.route === "string";
|
|
29196
|
+
},
|
|
29197
|
+
message: "Issue update requires at least one field besides issue: status, title, labels, external_links, or route."
|
|
29198
|
+
},
|
|
29199
|
+
strict: true
|
|
29200
|
+
},
|
|
29170
29201
|
{
|
|
29171
29202
|
name: "dispatch_ask",
|
|
29172
29203
|
description: "Open a durable, answerable decision or human to-do on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. Use kind: action for a to-do a human must complete; it has fixed Done / Can't answers. " + "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 800 ' + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
@@ -29554,17 +29585,6 @@ var legionRole = _enum2(LEGION_ROLES);
|
|
|
29554
29585
|
var requiredUnknown = unknown().refine((value) => value !== undefined, {
|
|
29555
29586
|
message: "Required"
|
|
29556
29587
|
});
|
|
29557
|
-
var LIFECYCLE_STATUSES = [
|
|
29558
|
-
"triage",
|
|
29559
|
-
"icebox",
|
|
29560
|
-
"backlog",
|
|
29561
|
-
"todo",
|
|
29562
|
-
"in_progress",
|
|
29563
|
-
"testing",
|
|
29564
|
-
"needs_review",
|
|
29565
|
-
"retro",
|
|
29566
|
-
"done"
|
|
29567
|
-
];
|
|
29568
29588
|
var architectCapability = strictObject({
|
|
29569
29589
|
tree: nonEmptyString,
|
|
29570
29590
|
sessionId: nonEmptyString,
|
|
@@ -29596,7 +29616,7 @@ var stateTreeLocator = discriminatedUnion("runtime", [
|
|
|
29596
29616
|
var stateIssue = strictObject({
|
|
29597
29617
|
key: nonEmptyString,
|
|
29598
29618
|
title: string2(),
|
|
29599
|
-
status: _enum2(
|
|
29619
|
+
status: _enum2(ISSUE_STATUSES).optional(),
|
|
29600
29620
|
children: array(nonEmptyString),
|
|
29601
29621
|
parent: nonEmptyString.optional(),
|
|
29602
29622
|
lastAppliedSeq: number2().int().nonnegative().optional()
|
|
@@ -29765,7 +29785,7 @@ var LegionDaemonApi = {
|
|
|
29765
29785
|
},
|
|
29766
29786
|
IssueStatus: {
|
|
29767
29787
|
request: controllerIssue.extend({
|
|
29768
|
-
status: _enum2(
|
|
29788
|
+
status: _enum2(ISSUE_STATUSES),
|
|
29769
29789
|
tree: nonEmptyString.optional(),
|
|
29770
29790
|
sessionId: nonEmptyString.optional()
|
|
29771
29791
|
}),
|
|
@@ -30294,19 +30314,8 @@ function toolFailure(error) {
|
|
|
30294
30314
|
|
|
30295
30315
|
// src/legion/tools.ts
|
|
30296
30316
|
var jsonSuccess = (details) => toolSuccess(JSON.stringify(details), details);
|
|
30297
|
-
|
|
30298
|
-
|
|
30299
|
-
"icebox",
|
|
30300
|
-
"backlog",
|
|
30301
|
-
"todo",
|
|
30302
|
-
"in_progress",
|
|
30303
|
-
"testing",
|
|
30304
|
-
"needs_review",
|
|
30305
|
-
"retro",
|
|
30306
|
-
"done"
|
|
30307
|
-
];
|
|
30308
|
-
function isLifecycleStatus(value) {
|
|
30309
|
-
return LIFECYCLE_STATUSES2.includes(value);
|
|
30317
|
+
function isIssueStatus(value) {
|
|
30318
|
+
return ISSUE_STATUSES.includes(value);
|
|
30310
30319
|
}
|
|
30311
30320
|
function isDispatchArtifactId(value) {
|
|
30312
30321
|
return LegionDaemonApi.GatesRegister.request.shape.artifactId.safeParse(value).success;
|
|
@@ -30323,7 +30332,7 @@ function legionToolSchema(pi) {
|
|
|
30323
30332
|
return z.object({
|
|
30324
30333
|
op: z.enum(["set_status", "register_gate", "release_wave", "escalate", "spawn_worker"]),
|
|
30325
30334
|
issue: z.string().optional(),
|
|
30326
|
-
status: z.enum(
|
|
30335
|
+
status: z.enum(ISSUE_STATUSES).optional(),
|
|
30327
30336
|
artifactId: z.string().optional(),
|
|
30328
30337
|
version: z.number().optional(),
|
|
30329
30338
|
kind: z.enum(["re-file", "capacity", "cross-tree"]).optional(),
|
|
@@ -30365,7 +30374,7 @@ function createLegionTool(deps) {
|
|
|
30365
30374
|
switch (parameters.op) {
|
|
30366
30375
|
case "set_status": {
|
|
30367
30376
|
const status = parameters.status;
|
|
30368
|
-
if (typeof status !== "string" || !
|
|
30377
|
+
if (typeof status !== "string" || !isIssueStatus(status)) {
|
|
30369
30378
|
throw new Error("set_status requires a valid Legion issue status");
|
|
30370
30379
|
}
|
|
30371
30380
|
await daemon.issueStatus({
|
|
@@ -108,6 +108,29 @@ dispatch_issue({ project, title, parent?, external?, spec?, force?, labels?: str
|
|
|
108
108
|
`details` `{ issue }`; creating an issue does not subscribe you to it (see [Following](#following)). Use `dispatch_issue` only to create an issue; never use it to park a question. When `spec` is supplied,
|
|
109
109
|
follow [Writing a spec](#writing-a-spec).
|
|
110
110
|
|
|
111
|
+
## Issue status is yours to move
|
|
112
|
+
|
|
113
|
+
The issue's status is how a human sees delivery without asking a session. Outside Legion (where
|
|
114
|
+
the daemon writes it), the session doing the work moves it, the way a person moves a card:
|
|
115
|
+
`in_progress` when implementation starts, `testing` when the change is being proven on a
|
|
116
|
+
production-like surface, `needs_review` when its pull request is open and waiting on the merge
|
|
117
|
+
queue, `done` when the change has been driven in production (a merge is not `done`). Move child
|
|
118
|
+
issues you own as well as the root. An issue left at `triage` while work is underway is a defect:
|
|
119
|
+
Sami, 2026-09-15, on the roadmap he could not read — "I'm not even sure what their development
|
|
120
|
+
status is." Waiting for the deploy lane is not a status and is never announced. Priority stays the
|
|
121
|
+
human's: set it on creation only when their intent is clear, and change it only on their word.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// PATCH /api/v1/issues/{key} — status, title, labels, external_links (merged by URL), route
|
|
125
|
+
dispatch_issue_update({ issue: "AGENTC-175", status: "testing" })
|
|
126
|
+
dispatch_issue_update({ issue: "AGENTC-175", external_links: ["https://github.com/owner/repo/pull/7"] })
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Link the pull request that delivers the issue in `external_links` when you open it; the issue page
|
|
130
|
+
renders its state and checks from that link. The call is authenticated with the same bearer as every
|
|
131
|
+
other `dispatch_*` tool: a Legion pane reads it from the `DISPATCH_TOKEN_FILE` path the daemon sets on
|
|
132
|
+
the pane; an OMP session outside Legion reads `dispatch.token` from `~/.config/opencode/envoy.json`.
|
|
133
|
+
|
|
111
134
|
## Search first
|
|
112
135
|
|
|
113
136
|
Before you create an issue or start a design document, search:
|