@sjawhar/pi-envoy 10.25.1 → 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
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
|
}
|
package/dist/envoy.js
CHANGED
|
@@ -30013,6 +30013,7 @@ var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passag
|
|
|
30013
30013
|
var SEARCH_KIND_DEPTH = 100;
|
|
30014
30014
|
var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
|
|
30015
30015
|
var PROJECT_KEY_RULE = "an uppercase letter, then 1 to 9 uppercase letters or digits, such as CORE";
|
|
30016
|
+
var ARCHITECTURE_SOURCE_REPO_PATTERN = /^[A-Za-z0-9-]+\/(?!\.\.?$)[A-Za-z0-9._-]+$/;
|
|
30016
30017
|
var ISSUE_STATUSES = [
|
|
30017
30018
|
"triage",
|
|
30018
30019
|
"icebox",
|
|
@@ -30378,7 +30379,7 @@ var dispatchToolSpecs = [
|
|
|
30378
30379
|
{
|
|
30379
30380
|
name: "dispatch_issues",
|
|
30380
30381
|
example: { project: "PROJ", limit: 250, offset: 250 },
|
|
30381
|
-
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.",
|
|
30382
|
+
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.",
|
|
30382
30383
|
arguments: (z2) => ({
|
|
30383
30384
|
project: z2.string().describe("Project key to list issues from."),
|
|
30384
30385
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -30388,7 +30389,8 @@ var dispatchToolSpecs = [
|
|
|
30388
30389
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
30389
30390
|
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(),
|
|
30390
30391
|
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(),
|
|
30391
|
-
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional()
|
|
30392
|
+
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional(),
|
|
30393
|
+
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()
|
|
30392
30394
|
})
|
|
30393
30395
|
},
|
|
30394
30396
|
{
|
|
@@ -30410,11 +30412,19 @@ var dispatchToolSpecs = [
|
|
|
30410
30412
|
},
|
|
30411
30413
|
{
|
|
30412
30414
|
name: "dispatch_architecture_sync",
|
|
30413
|
-
example: { project: "CORE" },
|
|
30414
|
-
description: "Import a project's architecture model from its
|
|
30415
|
+
example: { project: "CORE", repo: "acme/infra" },
|
|
30416
|
+
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.",
|
|
30415
30417
|
arguments: (z2) => ({
|
|
30416
|
-
project: z2.string().describe("Project key whose architecture
|
|
30418
|
+
project: z2.string().describe("Project key whose architecture sources to sync, such as CORE."),
|
|
30419
|
+
repo: z2.string().describe("One source repository of the project to sync alone, owner/name; omit it to sync every source.").optional()
|
|
30417
30420
|
}),
|
|
30421
|
+
validation: {
|
|
30422
|
+
check: (value) => {
|
|
30423
|
+
const { repo } = value;
|
|
30424
|
+
return typeof repo !== "string" || ARCHITECTURE_SOURCE_REPO_PATTERN.test(repo);
|
|
30425
|
+
},
|
|
30426
|
+
message: "repo is not a repository: give it as owner/name, such as acme/infra."
|
|
30427
|
+
},
|
|
30418
30428
|
strict: true
|
|
30419
30429
|
},
|
|
30420
30430
|
{
|
|
@@ -32708,6 +32718,9 @@ function asErrorShape(value) {
|
|
|
32708
32718
|
function isJson(response) {
|
|
32709
32719
|
return response.headers.get("content-type")?.includes("application/json") ?? false;
|
|
32710
32720
|
}
|
|
32721
|
+
function isNoRoute(error48) {
|
|
32722
|
+
return error48 instanceof DispatchServiceError && error48.status === 404 && error48.code === "NOT_FOUND" && error48.message.startsWith("no route for ");
|
|
32723
|
+
}
|
|
32711
32724
|
var ISSUE_EVENTS_PAGE_MAX = 200;
|
|
32712
32725
|
var DISPATCH_TOOL_DEADLINE_MS = 60000;
|
|
32713
32726
|
function requestSignal(signal) {
|
|
@@ -32735,6 +32748,18 @@ function whyNotAPage(answer) {
|
|
|
32735
32748
|
}
|
|
32736
32749
|
return `${what}. Retrying will not help: the same request gets the same answer until that ` + "Dispatch is upgraded or fixed.";
|
|
32737
32750
|
}
|
|
32751
|
+
function whyNotAnArray(answer) {
|
|
32752
|
+
if (typeof answer === "string")
|
|
32753
|
+
return `text that is not JSON, ${GATEWAY_PAGE}, so a retry may succeed`;
|
|
32754
|
+
if (answer === null)
|
|
32755
|
+
return "null";
|
|
32756
|
+
if (typeof answer !== "object")
|
|
32757
|
+
return `a ${typeof answer}`;
|
|
32758
|
+
if ("issues" in answer && Array.isArray(answer.issues)) {
|
|
32759
|
+
return "a page, so this Dispatch pages a request that carries no limit and no offset";
|
|
32760
|
+
}
|
|
32761
|
+
return "an object that is not the listing";
|
|
32762
|
+
}
|
|
32738
32763
|
var GATEWAY_PAGE = "which looks like a proxy or gateway page rather than Dispatch's own answer";
|
|
32739
32764
|
function gatewayAdvice(method, status) {
|
|
32740
32765
|
if (!transientStatus(status)) {
|
|
@@ -32810,6 +32835,14 @@ class DispatchClient {
|
|
|
32810
32835
|
return answer;
|
|
32811
32836
|
throw new Error(`GET ${this.#url(path2, query)} asked for a page ({issues, total, limit, offset}) and got ` + refusal);
|
|
32812
32837
|
}
|
|
32838
|
+
async listAllIssues(options) {
|
|
32839
|
+
const path2 = ["api", "v1", "issues"];
|
|
32840
|
+
const query = { ...options };
|
|
32841
|
+
const answer = await this.#json("GET", path2, undefined, query);
|
|
32842
|
+
if (Array.isArray(answer))
|
|
32843
|
+
return answer;
|
|
32844
|
+
throw new Error(`GET ${this.#url(path2, query)} asked for the unpaged listing and got ${whyNotAnArray(answer)}.`);
|
|
32845
|
+
}
|
|
32813
32846
|
async listProjectArtifacts(project, unlinked = false) {
|
|
32814
32847
|
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
32815
32848
|
}
|
|
@@ -32898,19 +32931,69 @@ class DispatchClient {
|
|
|
32898
32931
|
async listProjects() {
|
|
32899
32932
|
return this.#json("GET", ["api", "v1", "projects"]);
|
|
32900
32933
|
}
|
|
32901
|
-
async
|
|
32902
|
-
return this.#json("POST", ["api", "v1", "projects", project, "architecture-source", "sync"]);
|
|
32903
|
-
}
|
|
32904
|
-
async getArchitectureSource(project) {
|
|
32934
|
+
async listArchitectureSources(project) {
|
|
32905
32935
|
try {
|
|
32906
|
-
return await this.#json("GET", ["api", "v1", "projects", project, "architecture-
|
|
32936
|
+
return await this.#json("GET", ["api", "v1", "projects", project, "architecture-sources"]);
|
|
32937
|
+
} catch (error48) {
|
|
32938
|
+
if (!isNoRoute(error48))
|
|
32939
|
+
throw error48;
|
|
32940
|
+
}
|
|
32941
|
+
try {
|
|
32942
|
+
const source = await this.#json("GET", [
|
|
32943
|
+
"api",
|
|
32944
|
+
"v1",
|
|
32945
|
+
"projects",
|
|
32946
|
+
project,
|
|
32947
|
+
"architecture-source"
|
|
32948
|
+
]);
|
|
32949
|
+
return source === null ? [] : [source];
|
|
32907
32950
|
} catch (error48) {
|
|
32908
32951
|
if (error48 instanceof DispatchServiceError && error48.status === 404 && error48.code === "SOURCE_NOT_FOUND") {
|
|
32909
|
-
return
|
|
32952
|
+
return [];
|
|
32910
32953
|
}
|
|
32911
32954
|
throw error48;
|
|
32912
32955
|
}
|
|
32913
32956
|
}
|
|
32957
|
+
async syncArchitectureSources(project) {
|
|
32958
|
+
try {
|
|
32959
|
+
return await this.#json("POST", [
|
|
32960
|
+
"api",
|
|
32961
|
+
"v1",
|
|
32962
|
+
"projects",
|
|
32963
|
+
project,
|
|
32964
|
+
"architecture-sources",
|
|
32965
|
+
"sync"
|
|
32966
|
+
]);
|
|
32967
|
+
} catch (error48) {
|
|
32968
|
+
if (!isNoRoute(error48))
|
|
32969
|
+
throw error48;
|
|
32970
|
+
}
|
|
32971
|
+
const source = await this.#json("POST", [
|
|
32972
|
+
"api",
|
|
32973
|
+
"v1",
|
|
32974
|
+
"projects",
|
|
32975
|
+
project,
|
|
32976
|
+
"architecture-source",
|
|
32977
|
+
"sync"
|
|
32978
|
+
]);
|
|
32979
|
+
return [source];
|
|
32980
|
+
}
|
|
32981
|
+
async syncArchitectureSource(project, repo2) {
|
|
32982
|
+
const [owner, name, ...rest] = repo2.split("/");
|
|
32983
|
+
if (!owner || !name || rest.length > 0) {
|
|
32984
|
+
throw new Error(`repository must be owner/name, got ${JSON.stringify(repo2)}`);
|
|
32985
|
+
}
|
|
32986
|
+
return this.#json("POST", [
|
|
32987
|
+
"api",
|
|
32988
|
+
"v1",
|
|
32989
|
+
"projects",
|
|
32990
|
+
project,
|
|
32991
|
+
"architecture-sources",
|
|
32992
|
+
owner,
|
|
32993
|
+
name,
|
|
32994
|
+
"sync"
|
|
32995
|
+
]);
|
|
32996
|
+
}
|
|
32914
32997
|
async resolveAsk(id, input) {
|
|
32915
32998
|
return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
|
|
32916
32999
|
}
|
|
@@ -34151,7 +34234,7 @@ var envoyToolSpecs = [
|
|
|
34151
34234
|
},
|
|
34152
34235
|
{
|
|
34153
34236
|
name: "envoy_publish",
|
|
34154
|
-
description: `Publish an Envoy message to a notifications. topic. A role or built-in topic is delivered as its guide says; any other notifications. topic you name (a team topic such as notifications.team.deploy-squad.all) is live only: it reaches every subscribed session that holds its own NATS connection, yours included
|
|
34237
|
+
description: `Publish an Envoy message to a notifications. topic. A role or built-in topic is delivered as its guide says; any other notifications. topic you name (a team topic such as notifications.team.deploy-squad.all) is live only: it reaches every session subscribed to it when you publish, and is not retained. A session that holds its own NATS connection receives it directly, yours included; a session the listener delivers to over a serve port, such as an OpenCode agent, receives it through its machine's listener when that listener subscribes to every topic (ENVOY_SUBSCRIBE_ALL_TOPICS=1; the shared hub's listener does not). ${DELIVERY_CONTRACT}`,
|
|
34155
34238
|
arguments: (schema) => ({ topic: schema.string(), ...messageArguments(schema) }),
|
|
34156
34239
|
operation: EnvoyToolOperation.publish
|
|
34157
34240
|
},
|
|
@@ -34724,12 +34807,17 @@ var REPLAY_ARRAY_BYTES = 2;
|
|
|
34724
34807
|
function jsonBytes(value) {
|
|
34725
34808
|
return encoder.encode(JSON.stringify(value)).length;
|
|
34726
34809
|
}
|
|
34727
|
-
|
|
34728
|
-
|
|
34810
|
+
var SEQ_PER_MS = 1000;
|
|
34811
|
+
function modelReference(provider, id) {
|
|
34812
|
+
if (typeof id !== "string" || id === "")
|
|
34729
34813
|
return;
|
|
34730
|
-
if (typeof
|
|
34814
|
+
if (typeof provider !== "string" || provider === "")
|
|
34731
34815
|
return;
|
|
34732
|
-
return
|
|
34816
|
+
return `${provider}/${id}`;
|
|
34817
|
+
}
|
|
34818
|
+
function readModel(message) {
|
|
34819
|
+
const model = modelReference(message.provider, message.model);
|
|
34820
|
+
return model === undefined ? undefined : capAgentStreamText(model, AGENT_STREAM_LIMITS.modelChars);
|
|
34733
34821
|
}
|
|
34734
34822
|
function readMessage(raw) {
|
|
34735
34823
|
if (typeof raw !== "object" || raw === null)
|
|
@@ -34854,16 +34942,31 @@ class AgentStreamPublisher {
|
|
|
34854
34942
|
#history = new Map;
|
|
34855
34943
|
#settled = new Set;
|
|
34856
34944
|
#publishedAt = new Map;
|
|
34857
|
-
#seq
|
|
34945
|
+
#seq;
|
|
34858
34946
|
#watchedUntil = 0;
|
|
34947
|
+
#listedCommands;
|
|
34859
34948
|
#commands;
|
|
34860
34949
|
#commandsSeq = 0;
|
|
34950
|
+
#settings;
|
|
34951
|
+
#settingsSeq = 0;
|
|
34952
|
+
#settingsJson;
|
|
34861
34953
|
constructor(deps) {
|
|
34862
34954
|
this.#deps = deps;
|
|
34955
|
+
this.#seq = Math.trunc(deps.now()) * SEQ_PER_MS;
|
|
34863
34956
|
}
|
|
34864
34957
|
noteViewer() {
|
|
34865
34958
|
this.#watchedUntil = this.#deps.now() + AGENT_STREAM_WATCH_TTL_MS;
|
|
34866
34959
|
}
|
|
34960
|
+
publishState(subject2) {
|
|
34961
|
+
if (!this.watched)
|
|
34962
|
+
return;
|
|
34963
|
+
if (this.#settings !== undefined) {
|
|
34964
|
+
this.#deps.publish(subject2, JSON.stringify(this.#settingsFrame(this.#settings)));
|
|
34965
|
+
}
|
|
34966
|
+
if (this.#commands !== undefined) {
|
|
34967
|
+
this.#deps.publish(subject2, JSON.stringify(this.#commandsFrame(this.#commands)));
|
|
34968
|
+
}
|
|
34969
|
+
}
|
|
34867
34970
|
get watched() {
|
|
34868
34971
|
return this.#deps.now() < this.#watchedUntil;
|
|
34869
34972
|
}
|
|
@@ -34872,35 +34975,67 @@ class AgentStreamPublisher {
|
|
|
34872
34975
|
this.#settled.clear();
|
|
34873
34976
|
this.#publishedAt.clear();
|
|
34874
34977
|
this.#watchedUntil = 0;
|
|
34978
|
+
this.#listedCommands = undefined;
|
|
34875
34979
|
this.#commands = undefined;
|
|
34980
|
+
this.#settings = undefined;
|
|
34981
|
+
this.#settingsJson = undefined;
|
|
34876
34982
|
}
|
|
34877
34983
|
setCommands(subject2, commands) {
|
|
34878
|
-
const limit = AGENT_STREAM_LIMITS.historyBytes - REPLAY_ARRAY_BYTES;
|
|
34879
|
-
let bytes = jsonBytes(this.#commandsFrame([], this.#seq + 1));
|
|
34880
34984
|
const seen = new Set;
|
|
34881
|
-
const
|
|
34985
|
+
const listed = [];
|
|
34882
34986
|
for (const command of commands) {
|
|
34883
|
-
if (
|
|
34987
|
+
if (listed.length >= AGENT_STREAM_LIMITS.commands)
|
|
34884
34988
|
break;
|
|
34885
34989
|
const name = command.name;
|
|
34886
34990
|
if (name.length > AGENT_STREAM_LIMITS.commandNameChars)
|
|
34887
34991
|
continue;
|
|
34888
34992
|
if (!isTypableCommandName(name) || seen.has(name))
|
|
34889
34993
|
continue;
|
|
34890
|
-
|
|
34994
|
+
seen.add(name);
|
|
34995
|
+
listed.push({
|
|
34891
34996
|
name,
|
|
34892
34997
|
source: command.source,
|
|
34893
34998
|
...command.description !== undefined && {
|
|
34894
34999
|
description: capAgentStreamText(command.description, AGENT_STREAM_LIMITS.commandDescriptionChars)
|
|
34895
35000
|
},
|
|
34896
35001
|
...command.terminalOnly === true && { terminalOnly: true }
|
|
34897
|
-
};
|
|
34898
|
-
|
|
35002
|
+
});
|
|
35003
|
+
}
|
|
35004
|
+
this.#listedCommands = listed;
|
|
35005
|
+
this.#fitCommands(subject2);
|
|
35006
|
+
}
|
|
35007
|
+
setSettings(subject2, settings) {
|
|
35008
|
+
const kept = settings.reported ? {
|
|
35009
|
+
...settings.model !== undefined && { model: settings.model },
|
|
35010
|
+
models: [...settings.models],
|
|
35011
|
+
reported: true,
|
|
35012
|
+
thinking: settings.thinking === null ? null : { level: settings.thinking.level, levels: [...settings.thinking.levels] }
|
|
35013
|
+
} : { reported: false };
|
|
35014
|
+
const json2 = JSON.stringify(kept);
|
|
35015
|
+
if (json2 === this.#settingsJson)
|
|
35016
|
+
return;
|
|
35017
|
+
this.#settings = kept;
|
|
35018
|
+
this.#settingsJson = json2;
|
|
35019
|
+
this.#seq += 1;
|
|
35020
|
+
this.#settingsSeq = this.#seq;
|
|
35021
|
+
if (this.watched)
|
|
35022
|
+
this.#deps.publish(subject2, JSON.stringify(this.#settingsFrame(kept)));
|
|
35023
|
+
this.#fitCommands(subject2);
|
|
35024
|
+
}
|
|
35025
|
+
#fitCommands(subject2) {
|
|
35026
|
+
const listed = this.#listedCommands;
|
|
35027
|
+
if (listed === undefined)
|
|
35028
|
+
return;
|
|
35029
|
+
const settings = this.#settings === undefined ? 0 : jsonBytes(this.#settingsFrame(this.#settings)) + 1;
|
|
35030
|
+
const limit = AGENT_STREAM_LIMITS.historyBytes - REPLAY_ARRAY_BYTES - settings;
|
|
35031
|
+
let bytes = jsonBytes(this.#commandsFrame([], this.#seq + 1));
|
|
35032
|
+
const kept = [];
|
|
35033
|
+
for (const command of listed) {
|
|
35034
|
+
const cost = jsonBytes(command) + (kept.length === 0 ? 0 : 1);
|
|
34899
35035
|
if (bytes + cost > limit)
|
|
34900
35036
|
break;
|
|
34901
35037
|
bytes += cost;
|
|
34902
|
-
|
|
34903
|
-
kept.push(listed);
|
|
35038
|
+
kept.push(command);
|
|
34904
35039
|
}
|
|
34905
35040
|
if (JSON.stringify(kept) === JSON.stringify(this.#commands))
|
|
34906
35041
|
return;
|
|
@@ -34913,6 +35048,9 @@ class AgentStreamPublisher {
|
|
|
34913
35048
|
#commandsFrame(commands, seq = this.#commandsSeq) {
|
|
34914
35049
|
return { commands, kind: "commands", seq, v: AGENT_STREAM_PROTOCOL };
|
|
34915
35050
|
}
|
|
35051
|
+
#settingsFrame(settings) {
|
|
35052
|
+
return { ...settings, kind: "settings", seq: this.#settingsSeq, v: AGENT_STREAM_PROTOCOL };
|
|
35053
|
+
}
|
|
34916
35054
|
record(subject2, raw, streaming, dispatchMessageId) {
|
|
34917
35055
|
const message = readMessage(raw);
|
|
34918
35056
|
if (message === null)
|
|
@@ -34958,23 +35096,26 @@ class AgentStreamPublisher {
|
|
|
34958
35096
|
if (!this.watched)
|
|
34959
35097
|
return { frames: [], session_id: sessionID, v: AGENT_STREAM_PROTOCOL };
|
|
34960
35098
|
const ordered = [...this.#history.values()].sort((left, right) => left.at === right.at ? left.seq - right.seq : left.at - right.at);
|
|
34961
|
-
const
|
|
34962
|
-
|
|
35099
|
+
const tail = [];
|
|
35100
|
+
if (this.#settings !== undefined)
|
|
35101
|
+
tail.push(this.#settingsFrame(this.#settings));
|
|
35102
|
+
if (this.#commands !== undefined)
|
|
35103
|
+
tail.push(this.#commandsFrame(this.#commands));
|
|
35104
|
+
let used = REPLAY_ARRAY_BYTES + tail.reduce((bytes, frame, index) => bytes + jsonBytes(frame) + (index === 0 ? 0 : 1), 0);
|
|
34963
35105
|
const kept = [];
|
|
34964
35106
|
for (let index = ordered.length - 1;index >= 0; index -= 1) {
|
|
34965
35107
|
const entry = ordered[index];
|
|
34966
35108
|
if (entry === undefined)
|
|
34967
35109
|
continue;
|
|
34968
35110
|
const frame = frameFor(entry);
|
|
34969
|
-
const separated = kept.length > 0 ||
|
|
35111
|
+
const separated = kept.length > 0 || tail.length > 0;
|
|
34970
35112
|
const cost = jsonBytes(frame) + (separated ? 1 : 0);
|
|
34971
35113
|
if (used + cost > AGENT_STREAM_LIMITS.historyBytes)
|
|
34972
35114
|
break;
|
|
34973
35115
|
used += cost;
|
|
34974
35116
|
kept.unshift(frame);
|
|
34975
35117
|
}
|
|
34976
|
-
|
|
34977
|
-
kept.push(commands);
|
|
35118
|
+
kept.push(...tail);
|
|
34978
35119
|
return { frames: kept, session_id: sessionID, v: AGENT_STREAM_PROTOCOL };
|
|
34979
35120
|
}
|
|
34980
35121
|
}
|
|
@@ -35334,6 +35475,25 @@ import("@oh-my-pi/pi-coding-agent/extensibility/extensions/send-user-input-handl
|
|
|
35334
35475
|
}, () => {
|
|
35335
35476
|
return;
|
|
35336
35477
|
});
|
|
35478
|
+
function liveAgentSession(session) {
|
|
35479
|
+
return host2.AgentRegistry?.global().list().find((ref) => ref.session?.sessionManager.getSessionId() === session)?.session ?? undefined;
|
|
35480
|
+
}
|
|
35481
|
+
function liveSessionSettings(live) {
|
|
35482
|
+
if (live === undefined || !("model" in live) || typeof live.configuredThinkingLevel !== "function" || typeof live.getAvailableModels !== "function" || typeof live.getAvailableEffortSelectors !== "function") {
|
|
35483
|
+
return { reported: false };
|
|
35484
|
+
}
|
|
35485
|
+
const model = modelReference(live.model?.provider, live.model?.id);
|
|
35486
|
+
const levels = live.getAvailableEffortSelectors();
|
|
35487
|
+
return {
|
|
35488
|
+
...model !== undefined && { model },
|
|
35489
|
+
models: live.getAvailableModels().flatMap((available) => {
|
|
35490
|
+
const listed = modelReference(available.provider, available.id);
|
|
35491
|
+
return listed === undefined ? [] : [listed];
|
|
35492
|
+
}),
|
|
35493
|
+
reported: true,
|
|
35494
|
+
thinking: levels.length === 0 ? null : { level: live.configuredThinkingLevel() ?? null, levels }
|
|
35495
|
+
};
|
|
35496
|
+
}
|
|
35337
35497
|
var codec2 = import_nats2.StringCodec();
|
|
35338
35498
|
var NATS_RETRY_INTERVAL_MS = 15000;
|
|
35339
35499
|
var DELIVERY_PICTURES_TIMEOUT_MS = 20000;
|
|
@@ -35739,6 +35899,7 @@ function envoyExtension(pi) {
|
|
|
35739
35899
|
typedPictures.withdraw(turn.messageId);
|
|
35740
35900
|
return typedInputReply(turn.body, { error: messageFor(error48) });
|
|
35741
35901
|
}).then(async (reply) => {
|
|
35902
|
+
refreshAgentStreamSettings(sessionID);
|
|
35742
35903
|
if (reply !== undefined)
|
|
35743
35904
|
await postDispatchReply(delivery, { body: reply }, replySessionID);
|
|
35744
35905
|
}).catch((error48) => {
|
|
@@ -35960,7 +36121,7 @@ function envoyExtension(pi) {
|
|
|
35960
36121
|
return;
|
|
35961
36122
|
const builtins = hostBuiltinCommands?.() ?? [];
|
|
35962
36123
|
const reserved = new Set(builtins.flatMap((builtin) => [builtin.name, ...builtin.aliases]));
|
|
35963
|
-
const live =
|
|
36124
|
+
const live = liveAgentSession(session);
|
|
35964
36125
|
const commands = builtins.map((builtin) => ({
|
|
35965
36126
|
description: builtin.description,
|
|
35966
36127
|
name: builtin.name,
|
|
@@ -35986,6 +36147,18 @@ function envoyExtension(pi) {
|
|
|
35986
36147
|
}
|
|
35987
36148
|
agentStream.setCommands(agentStreamFramesSubject(session), commands);
|
|
35988
36149
|
};
|
|
36150
|
+
const refreshAgentStreamSettings = (session) => {
|
|
36151
|
+
if (session === "")
|
|
36152
|
+
return;
|
|
36153
|
+
try {
|
|
36154
|
+
agentStream.setSettings(agentStreamFramesSubject(session), liveSessionSettings(liveAgentSession(session)));
|
|
36155
|
+
} catch (error48) {
|
|
36156
|
+
logger3.warn("envoy: could not refresh agent stream settings", {
|
|
36157
|
+
error: messageFor(error48),
|
|
36158
|
+
sessionID: session
|
|
36159
|
+
});
|
|
36160
|
+
}
|
|
36161
|
+
};
|
|
35989
36162
|
const pumpAgentStreamControl = async (subscription, session) => {
|
|
35990
36163
|
try {
|
|
35991
36164
|
for await (const message of subscription) {
|
|
@@ -35996,6 +36169,7 @@ function envoyExtension(pi) {
|
|
|
35996
36169
|
const request = parsed;
|
|
35997
36170
|
if (request.type !== "replay" && request.type !== "watch")
|
|
35998
36171
|
continue;
|
|
36172
|
+
refreshAgentStreamSettings(session);
|
|
35999
36173
|
const attached = agentStreamViewerAttached;
|
|
36000
36174
|
if (request.type === "replay" || !attached || Date.now() - agentStreamCommandsRefreshedOnWatchAt >= AGENT_STREAM_WATCH_INTERVAL_MS) {
|
|
36001
36175
|
try {
|
|
@@ -36012,8 +36186,11 @@ function envoyExtension(pi) {
|
|
|
36012
36186
|
}
|
|
36013
36187
|
const reply = message.reply;
|
|
36014
36188
|
const answer = request.type === "replay" && reply !== undefined ? JSON.stringify(agentStream.replay(session)) : undefined;
|
|
36189
|
+
const arming = !agentStream.watched;
|
|
36015
36190
|
agentStream.noteViewer();
|
|
36016
36191
|
agentStreamViewerAttached = true;
|
|
36192
|
+
if (arming)
|
|
36193
|
+
agentStream.publishState(agentStreamFramesSubject(session));
|
|
36017
36194
|
if (answer === undefined || reply === undefined)
|
|
36018
36195
|
continue;
|
|
36019
36196
|
connection?.publish(reply, codec2.encode(answer));
|
|
@@ -36072,6 +36249,12 @@ function envoyExtension(pi) {
|
|
|
36072
36249
|
pi.on("message_end", async (event) => {
|
|
36073
36250
|
recordAgentStreamMessage(event.message, false);
|
|
36074
36251
|
});
|
|
36252
|
+
pi.on("turn_start", async () => {
|
|
36253
|
+
refreshAgentStreamSettings(sessionID);
|
|
36254
|
+
});
|
|
36255
|
+
pi.on("retry_fallback_applied", async () => {
|
|
36256
|
+
refreshAgentStreamSettings(sessionID);
|
|
36257
|
+
});
|
|
36075
36258
|
const endRole = () => {
|
|
36076
36259
|
claimedRoleTopic = undefined;
|
|
36077
36260
|
claimedRoleSessionID = undefined;
|
|
@@ -136,7 +136,7 @@ EOF
|
|
|
136
136
|
|
|
137
137
|
- Attach every architectural issue to the components it changes. Attach the root before you decompose it: children inherit.
|
|
138
138
|
- `--components-json '{"mode":"explicit","ids":["web"]}'`, `'{"mode":"none","reason":"hiring, not code"}'` (only for non-architectural work, with a concrete reason), or `'{"mode":"inherit"}'`.
|
|
139
|
-
- Component ids: each non-external `components[].id` from `GET /api/v1/projects/{key}/architecture`, or the file names under `.dispatch/architecture/` in
|
|
139
|
+
- Component ids: each non-external `components[].id` from `GET /api/v1/projects/{key}/architecture`, or the file names under `.dispatch/architecture/` in any of the project's source repositories. A project may import from several; ids are project-wide, so one issue can name components of different sources. Others are refused `COMPONENTS_INPUT`. A closed issue can be classified without reopening.
|
|
140
140
|
- Change code and `.dispatch/architecture/<id>.md` in the same review. A stale model: `dispatch architecture-sync` ([issues.md](skill://dispatch/references/issues.md#syncing-a-projects-architecture-model)).
|
|
141
141
|
|
|
142
142
|
## Close what you opened
|
|
@@ -63,4 +63,4 @@ As a surface's owner, run `dispatch issues --project <KEY> --priority 0 --priori
|
|
|
63
63
|
|
|
64
64
|
## Syncing a project's architecture model
|
|
65
65
|
|
|
66
|
-
`dispatch architecture-sync --project CORE` imports
|
|
66
|
+
A project imports its model from one or more source repositories, each on its own branch. `dispatch architecture-sync --project CORE` imports every source now and prints a line for each: `Synced CORE architecture from <repo>@<branch>: commit <sha> (<time>).`, or `Sync failed for CORE (<repo>@<branch>): <error>` followed by what of that source still shows: its commit, its component count when the source was re-saved on another branch and has not imported from it yet, or that none ever imported. A failed source keeps its previous components; every other source is untouched. `--repo <owner/name>` imports that one source alone. No source configured, or no source from that repository, answers `SOURCE_NOT_FOUND`. A credential or branch problem is that source's failed line, or with `--repo` an error, `SOURCE_ACCESS`.
|
|
@@ -66,6 +66,6 @@ Every topic starts with `notifications.`.
|
|
|
66
66
|
|
|
67
67
|
- Send to one session with `envoy_send`, to the one session that holds a named duty with a role, and to a group with a team topic.
|
|
68
68
|
- Publish to a role with `envoy_publish(topic="notifications.role.<role>", ...)`; never subscribe to a role topic, because the listener forwards a role message to its holder only. A publish returns the live `holder`; an unheld role returns an error. `envoy_role_get(role=...)` finds the holder first.
|
|
69
|
-
- For a team, the group subscribes to `notifications.team.<name>.>`, and any member publishes to `notifications.team.<name>.all`. Envoy treats any other `notifications.` topic that no source in the table uses the same way. It reaches only the sessions subscribed when it is published (yours too, when you subscribe), and is not kept for a session that subscribes later. A topic outside `notifications.` is refused.
|
|
69
|
+
- For a team, the group subscribes to `notifications.team.<name>.>`, and any member publishes to `notifications.team.<name>.all`. Envoy treats any other `notifications.` topic that no source in the table uses the same way. It reaches only the sessions subscribed when it is published (yours too, when you subscribe, unless you are a serve-port session, which receives it only through a listener run with `ENVOY_SUBSCRIBE_ALL_TOPICS=1` and never its own publish), and is not kept for a session that subscribes later. A topic outside `notifications.` is refused.
|
|
70
70
|
- How a role claim survives a resume, a fork or a dead process: `skill://envoy/references/roles.md`.
|
|
71
71
|
- In a Legion pane, the plugin claims your role at session start; read `skill://envoy/references/legion.md` before you claim, release or publish to a role, or answer a role delivery failure there.
|
|
@@ -54,7 +54,7 @@ envoy:
|
|
|
54
54
|
It depends on where a session takes its deliveries, not on whether the topic is built in or your own.
|
|
55
55
|
|
|
56
56
|
- A session that holds its own NATS connection (every Oh My Pi session, and a Claude Code one) receives every message published on a subject it subscribes to, whoever sent it: its own publish included, and a subagent's publish to a topic the parent subscribes to. This holds for a topic the stream keeps and for a live team topic alike.
|
|
57
|
-
- A session the listener delivers to by push, over a serve port (such as the OpenCode plugin), does not receive its own publish, and receives
|
|
57
|
+
- A session the listener delivers to by push, over a serve port (such as the OpenCode plugin), does not receive its own publish, and receives a team topic only through the listener that owns it when that listener runs with `ENVOY_SUBSCRIBE_ALL_TOPICS=1` (the shared hub's listener does not).
|
|
58
58
|
- A role is not a subject its holder subscribes to: the listener finds the live holder and forwards the message to it, and drops the message when that holder is the message's source session.
|
|
59
59
|
|
|
60
60
|
## Listener API errors
|