@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/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`.
|