@sjawhar/pi-envoy 10.25.0 → 10.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatch.js +144 -28
- package/dist/envoy.js +215 -32
- package/dist/skills/dispatch/SKILL.md +1 -1
- package/dist/skills/dispatch/references/issues.md +1 -1
- package/dist/skills/envoy/SKILL.md +47 -286
- package/dist/skills/envoy/references/check-settlement.md +62 -0
- package/dist/skills/envoy/references/deliveries.md +62 -0
- package/dist/skills/envoy/references/legion.md +12 -0
- package/dist/skills/envoy/references/roles.md +10 -0
- package/dist/skills/envoy/references/sources.md +41 -0
- package/package.json +1 -1
package/dist/dispatch.js
CHANGED
|
@@ -14026,6 +14026,7 @@ var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passag
|
|
|
14026
14026
|
var SEARCH_KIND_DEPTH = 100;
|
|
14027
14027
|
var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
|
|
14028
14028
|
var PROJECT_KEY_RULE = "an uppercase letter, then 1 to 9 uppercase letters or digits, such as CORE";
|
|
14029
|
+
var ARCHITECTURE_SOURCE_REPO_PATTERN = /^[A-Za-z0-9-]+\/(?!\.\.?$)[A-Za-z0-9._-]+$/;
|
|
14029
14030
|
var ISSUE_STATUSES = [
|
|
14030
14031
|
"triage",
|
|
14031
14032
|
"icebox",
|
|
@@ -14391,7 +14392,7 @@ var dispatchToolSpecs = [
|
|
|
14391
14392
|
{
|
|
14392
14393
|
name: "dispatch_issues",
|
|
14393
14394
|
example: { project: "PROJ", limit: 250, offset: 250 },
|
|
14394
|
-
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, progress (its spec's task-list items and " + "its direct children, each done of total, as `tasks 3/7 \xB7 children 2/5`, each only when it has " + "any), and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never.",
|
|
14395
|
+
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, progress (its spec's task-list items and " + "its direct children, each done of total, as `tasks 3/7 \xB7 children 2/5`, each only when it has " + "any), and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never. " + "all takes the whole filtered list in one read instead, for a caller that must not miss or " + "repeat a row while other writers change the list, and especially for one whose own writes " + "move issues out of what it filtered on.",
|
|
14395
14396
|
arguments: (z2) => ({
|
|
14396
14397
|
project: z2.string().describe("Project key to list issues from."),
|
|
14397
14398
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -14401,7 +14402,8 @@ var dispatchToolSpecs = [
|
|
|
14401
14402
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
14402
14403
|
route_status: z2.enum(ISSUE_ROUTE_STATUSES).describe("Optional: only open issues whose route is in this state. live: a running session holds " + "the role or is the routed session. no_holder: nobody running holds the role, or the " + "session is not running, right now. unknown: the Envoy listener did not answer.").optional(),
|
|
14403
14404
|
limit: z2.number({ int: true, min: 1, max: MAX_ISSUE_PAGE_LIMIT }).describe(`Maximum rows, 1-${MAX_ISSUE_PAGE_LIMIT}; default ${DEFAULT_ISSUE_PAGE_LIMIT}.`).optional(),
|
|
14404
|
-
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional()
|
|
14405
|
+
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional(),
|
|
14406
|
+
all: z2.boolean().describe("Take every matching issue in one unpaged read, so no row is missed or repeated while " + "the list changes. Refused with limit or offset.").optional()
|
|
14405
14407
|
})
|
|
14406
14408
|
},
|
|
14407
14409
|
{
|
|
@@ -14423,11 +14425,19 @@ var dispatchToolSpecs = [
|
|
|
14423
14425
|
},
|
|
14424
14426
|
{
|
|
14425
14427
|
name: "dispatch_architecture_sync",
|
|
14426
|
-
example: { project: "CORE" },
|
|
14427
|
-
description: "Import a project's architecture model from its
|
|
14428
|
+
example: { project: "CORE", repo: "acme/infra" },
|
|
14429
|
+
description: "Import a project's architecture model from its source repositories now, instead of waiting " + "for the server's five-minute schedule. A project may have several sources, one per " + "repository, and its model is every source's components together. Without repo every " + "source imports and each answers on its own line; with repo only that source does. Each " + "line names the imported commit, or the recorded error when that source's model was " + "rejected (its previous components stay up, and every other source is untouched). Sources " + "are saved in Settings or with PUT /api/v1/projects/{key}/architecture-sources/{owner}/{repo}; " + "404 SOURCE_NOT_FOUND when the project has none, or none for that repository.",
|
|
14428
14430
|
arguments: (z2) => ({
|
|
14429
|
-
project: z2.string().describe("Project key whose architecture
|
|
14431
|
+
project: z2.string().describe("Project key whose architecture sources to sync, such as CORE."),
|
|
14432
|
+
repo: z2.string().describe("One source repository of the project to sync alone, owner/name; omit it to sync every source.").optional()
|
|
14430
14433
|
}),
|
|
14434
|
+
validation: {
|
|
14435
|
+
check: (value) => {
|
|
14436
|
+
const { repo } = value;
|
|
14437
|
+
return typeof repo !== "string" || ARCHITECTURE_SOURCE_REPO_PATTERN.test(repo);
|
|
14438
|
+
},
|
|
14439
|
+
message: "repo is not a repository: give it as owner/name, such as acme/infra."
|
|
14440
|
+
},
|
|
14431
14441
|
strict: true
|
|
14432
14442
|
},
|
|
14433
14443
|
{
|
|
@@ -14805,9 +14815,8 @@ function parseCommand(argv, io) {
|
|
|
14805
14815
|
const token = rest[index];
|
|
14806
14816
|
if (!token.startsWith("--")) {
|
|
14807
14817
|
if (ISSUE_KEY.test(token) && surface.flags.has("--issue") && !setBy.has("issue")) {
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
problems.push(`unexpected argument ${token}: use --issue <KEY>, as in: dispatch ${corrected.map(quoteWord).join(" ")}`);
|
|
14818
|
+
if (claim("issue", "--issue", false))
|
|
14819
|
+
args.issue = token;
|
|
14811
14820
|
continue;
|
|
14812
14821
|
}
|
|
14813
14822
|
problems.push(`unexpected argument ${quoteWord(token)}: every value follows its flag`);
|
|
@@ -15164,6 +15173,9 @@ function asErrorShape(value) {
|
|
|
15164
15173
|
function isJson(response) {
|
|
15165
15174
|
return response.headers.get("content-type")?.includes("application/json") ?? false;
|
|
15166
15175
|
}
|
|
15176
|
+
function isNoRoute(error48) {
|
|
15177
|
+
return error48 instanceof DispatchServiceError && error48.status === 404 && error48.code === "NOT_FOUND" && error48.message.startsWith("no route for ");
|
|
15178
|
+
}
|
|
15167
15179
|
var ISSUE_EVENTS_PAGE_MAX = 200;
|
|
15168
15180
|
var DISPATCH_TOOL_DEADLINE_MS = 60000;
|
|
15169
15181
|
function requestSignal(signal) {
|
|
@@ -15191,6 +15203,18 @@ function whyNotAPage(answer) {
|
|
|
15191
15203
|
}
|
|
15192
15204
|
return `${what}. Retrying will not help: the same request gets the same answer until that ` + "Dispatch is upgraded or fixed.";
|
|
15193
15205
|
}
|
|
15206
|
+
function whyNotAnArray(answer) {
|
|
15207
|
+
if (typeof answer === "string")
|
|
15208
|
+
return `text that is not JSON, ${GATEWAY_PAGE}, so a retry may succeed`;
|
|
15209
|
+
if (answer === null)
|
|
15210
|
+
return "null";
|
|
15211
|
+
if (typeof answer !== "object")
|
|
15212
|
+
return `a ${typeof answer}`;
|
|
15213
|
+
if ("issues" in answer && Array.isArray(answer.issues)) {
|
|
15214
|
+
return "a page, so this Dispatch pages a request that carries no limit and no offset";
|
|
15215
|
+
}
|
|
15216
|
+
return "an object that is not the listing";
|
|
15217
|
+
}
|
|
15194
15218
|
var GATEWAY_PAGE = "which looks like a proxy or gateway page rather than Dispatch's own answer";
|
|
15195
15219
|
function gatewayAdvice(method, status) {
|
|
15196
15220
|
if (!transientStatus(status)) {
|
|
@@ -15266,6 +15290,14 @@ class DispatchClient {
|
|
|
15266
15290
|
return answer;
|
|
15267
15291
|
throw new Error(`GET ${this.#url(path, query)} asked for a page ({issues, total, limit, offset}) and got ` + refusal);
|
|
15268
15292
|
}
|
|
15293
|
+
async listAllIssues(options) {
|
|
15294
|
+
const path = ["api", "v1", "issues"];
|
|
15295
|
+
const query = { ...options };
|
|
15296
|
+
const answer = await this.#json("GET", path, undefined, query);
|
|
15297
|
+
if (Array.isArray(answer))
|
|
15298
|
+
return answer;
|
|
15299
|
+
throw new Error(`GET ${this.#url(path, query)} asked for the unpaged listing and got ${whyNotAnArray(answer)}.`);
|
|
15300
|
+
}
|
|
15269
15301
|
async listProjectArtifacts(project, unlinked = false) {
|
|
15270
15302
|
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
15271
15303
|
}
|
|
@@ -15354,19 +15386,69 @@ class DispatchClient {
|
|
|
15354
15386
|
async listProjects() {
|
|
15355
15387
|
return this.#json("GET", ["api", "v1", "projects"]);
|
|
15356
15388
|
}
|
|
15357
|
-
async
|
|
15358
|
-
return this.#json("POST", ["api", "v1", "projects", project, "architecture-source", "sync"]);
|
|
15359
|
-
}
|
|
15360
|
-
async getArchitectureSource(project) {
|
|
15389
|
+
async listArchitectureSources(project) {
|
|
15361
15390
|
try {
|
|
15362
|
-
return await this.#json("GET", ["api", "v1", "projects", project, "architecture-
|
|
15391
|
+
return await this.#json("GET", ["api", "v1", "projects", project, "architecture-sources"]);
|
|
15392
|
+
} catch (error48) {
|
|
15393
|
+
if (!isNoRoute(error48))
|
|
15394
|
+
throw error48;
|
|
15395
|
+
}
|
|
15396
|
+
try {
|
|
15397
|
+
const source = await this.#json("GET", [
|
|
15398
|
+
"api",
|
|
15399
|
+
"v1",
|
|
15400
|
+
"projects",
|
|
15401
|
+
project,
|
|
15402
|
+
"architecture-source"
|
|
15403
|
+
]);
|
|
15404
|
+
return source === null ? [] : [source];
|
|
15363
15405
|
} catch (error48) {
|
|
15364
15406
|
if (error48 instanceof DispatchServiceError && error48.status === 404 && error48.code === "SOURCE_NOT_FOUND") {
|
|
15365
|
-
return
|
|
15407
|
+
return [];
|
|
15366
15408
|
}
|
|
15367
15409
|
throw error48;
|
|
15368
15410
|
}
|
|
15369
15411
|
}
|
|
15412
|
+
async syncArchitectureSources(project) {
|
|
15413
|
+
try {
|
|
15414
|
+
return await this.#json("POST", [
|
|
15415
|
+
"api",
|
|
15416
|
+
"v1",
|
|
15417
|
+
"projects",
|
|
15418
|
+
project,
|
|
15419
|
+
"architecture-sources",
|
|
15420
|
+
"sync"
|
|
15421
|
+
]);
|
|
15422
|
+
} catch (error48) {
|
|
15423
|
+
if (!isNoRoute(error48))
|
|
15424
|
+
throw error48;
|
|
15425
|
+
}
|
|
15426
|
+
const source = await this.#json("POST", [
|
|
15427
|
+
"api",
|
|
15428
|
+
"v1",
|
|
15429
|
+
"projects",
|
|
15430
|
+
project,
|
|
15431
|
+
"architecture-source",
|
|
15432
|
+
"sync"
|
|
15433
|
+
]);
|
|
15434
|
+
return [source];
|
|
15435
|
+
}
|
|
15436
|
+
async syncArchitectureSource(project, repo2) {
|
|
15437
|
+
const [owner, name, ...rest] = repo2.split("/");
|
|
15438
|
+
if (!owner || !name || rest.length > 0) {
|
|
15439
|
+
throw new Error(`repository must be owner/name, got ${JSON.stringify(repo2)}`);
|
|
15440
|
+
}
|
|
15441
|
+
return this.#json("POST", [
|
|
15442
|
+
"api",
|
|
15443
|
+
"v1",
|
|
15444
|
+
"projects",
|
|
15445
|
+
project,
|
|
15446
|
+
"architecture-sources",
|
|
15447
|
+
owner,
|
|
15448
|
+
name,
|
|
15449
|
+
"sync"
|
|
15450
|
+
]);
|
|
15451
|
+
}
|
|
15370
15452
|
async resolveAsk(id, input) {
|
|
15371
15453
|
return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
|
|
15372
15454
|
}
|
|
@@ -16445,14 +16527,31 @@ async function architectureGuidance(client, project, components) {
|
|
|
16445
16527
|
if (components.mode === "none" || components.ids.length > 0)
|
|
16446
16528
|
return;
|
|
16447
16529
|
try {
|
|
16448
|
-
if (await client.
|
|
16530
|
+
if ((await client.listArchitectureSources(project)).length === 0)
|
|
16449
16531
|
return;
|
|
16450
|
-
}
|
|
16451
16532
|
return `Project ${project} has an architecture model, but this issue is not linked to any of its current components. ${architectureComponentsAction}`;
|
|
16452
16533
|
} catch (error48) {
|
|
16453
16534
|
return `Could not check whether project ${project} has an architecture model: ${messageFor(error48)}. ${architectureComponentsAction}`;
|
|
16454
16535
|
}
|
|
16455
16536
|
}
|
|
16537
|
+
function architectureSyncLines(project, source) {
|
|
16538
|
+
if (source.last_error === null) {
|
|
16539
|
+
return `Synced ${project} architecture from ${source.repo}@${source.branch}: commit ${source.last_commit ?? "unknown"} (${source.last_sync_at ?? "unknown time"}).`;
|
|
16540
|
+
}
|
|
16541
|
+
let kept;
|
|
16542
|
+
if (source.last_commit !== null) {
|
|
16543
|
+
kept = `The previous model of ${source.repo} stays up (commit ${source.last_commit}).`;
|
|
16544
|
+
} else if (source.components > 0) {
|
|
16545
|
+
kept = `The previous model of ${source.repo} stays up (${source.components} ${source.components === 1 ? "component" : "components"}).`;
|
|
16546
|
+
} else {
|
|
16547
|
+
kept = `No model has ever imported from ${source.repo}.`;
|
|
16548
|
+
}
|
|
16549
|
+
return [
|
|
16550
|
+
`Sync failed for ${project} (${source.repo}@${source.branch}): ${source.last_error}`,
|
|
16551
|
+
kept
|
|
16552
|
+
].join(`
|
|
16553
|
+
`);
|
|
16554
|
+
}
|
|
16456
16555
|
function isDuplicateCandidate(value) {
|
|
16457
16556
|
if (typeof value !== "object" || value === null)
|
|
16458
16557
|
return false;
|
|
@@ -16696,6 +16795,18 @@ function argumentProblems(tool, args) {
|
|
|
16696
16795
|
}
|
|
16697
16796
|
break;
|
|
16698
16797
|
}
|
|
16798
|
+
case "dispatch_issues": {
|
|
16799
|
+
if (optionalBoolean(args, "all") === true) {
|
|
16800
|
+
const paging = [
|
|
16801
|
+
...optionalNumber(args, "limit") === undefined ? [] : ["--limit"],
|
|
16802
|
+
...optionalNumber(args, "offset") === undefined ? [] : ["--offset"]
|
|
16803
|
+
];
|
|
16804
|
+
if (paging.length > 0) {
|
|
16805
|
+
problems.push(`--all takes every matching issue in one unpaged read, so it cannot be combined with ${paging.join(" or ")}: name --all on its own, or page with --limit and --offset`);
|
|
16806
|
+
}
|
|
16807
|
+
}
|
|
16808
|
+
break;
|
|
16809
|
+
}
|
|
16699
16810
|
}
|
|
16700
16811
|
return problems;
|
|
16701
16812
|
}
|
|
@@ -18013,7 +18124,7 @@ async function executeDispatchTool(input) {
|
|
|
18013
18124
|
const priority = optionalPriorityFilter(args, "priority");
|
|
18014
18125
|
const updatedSince = optionalString(args, "updated_since");
|
|
18015
18126
|
const routeStatus = optionalString(args, "route_status");
|
|
18016
|
-
const
|
|
18127
|
+
const filters = {
|
|
18017
18128
|
project,
|
|
18018
18129
|
...status === undefined ? {} : { status },
|
|
18019
18130
|
...parent === undefined ? {} : { parent },
|
|
@@ -18021,7 +18132,13 @@ async function executeDispatchTool(input) {
|
|
|
18021
18132
|
...priority === undefined ? {} : { priority },
|
|
18022
18133
|
...updatedSince === undefined ? {} : { updated_since: updatedSince },
|
|
18023
18134
|
...routeStatus === undefined ? {} : { route_status: routeStatus }
|
|
18024
|
-
}
|
|
18135
|
+
};
|
|
18136
|
+
const page = optionalBoolean(args, "all") === true ? await client.listAllIssues(filters).then((issues) => ({
|
|
18137
|
+
issues,
|
|
18138
|
+
total: issues.length,
|
|
18139
|
+
limit: issues.length,
|
|
18140
|
+
offset: 0
|
|
18141
|
+
})) : await client.listIssuePage(filters, {
|
|
18025
18142
|
limit: optionalNumber(args, "limit") ?? DEFAULT_ISSUE_PAGE_LIMIT,
|
|
18026
18143
|
offset: optionalNumber(args, "offset") ?? 0
|
|
18027
18144
|
});
|
|
@@ -18083,20 +18200,19 @@ async function executeDispatchTool(input) {
|
|
|
18083
18200
|
}
|
|
18084
18201
|
case "dispatch_architecture_sync": {
|
|
18085
18202
|
const project = stringArg(args, "project");
|
|
18086
|
-
const
|
|
18087
|
-
const
|
|
18203
|
+
const repo2 = optionalString(args, "repo");
|
|
18204
|
+
const sources = repo2 === undefined ? await client.syncArchitectureSources(project) : [await client.syncArchitectureSource(project, repo2)];
|
|
18088
18205
|
return {
|
|
18089
|
-
text: source
|
|
18090
|
-
`Sync failed for ${project} (${source.repo}@${source.branch}): ${source.last_error}`,
|
|
18091
|
-
source.last_commit === null ? "No model has ever imported for this project." : `The previous model stays up (commit ${source.last_commit}).`
|
|
18092
|
-
].join(`
|
|
18206
|
+
text: sources.map((source) => architectureSyncLines(project, source)).join(`
|
|
18093
18207
|
`),
|
|
18094
18208
|
details: {
|
|
18095
18209
|
project,
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
|
|
18099
|
-
|
|
18210
|
+
sources: sources.map((source) => ({
|
|
18211
|
+
repo: source.repo,
|
|
18212
|
+
branch: source.branch,
|
|
18213
|
+
commit: source.last_commit,
|
|
18214
|
+
error: source.last_error
|
|
18215
|
+
}))
|
|
18100
18216
|
}
|
|
18101
18217
|
};
|
|
18102
18218
|
}
|