@sjawhar/opencode-legion-envoy 3.12.1 → 3.14.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/src/server.js +41 -3
- package/package.json +1 -1
- package/skills/AGENTS.md +3 -1
- package/skills/dispatch/SKILL.md +83 -659
- package/skills/dispatch/references/api.md +28 -0
- package/skills/dispatch/references/asks.md +109 -0
- package/skills/dispatch/references/document-edits.md +1 -1
- package/skills/dispatch/references/documents.md +148 -0
- package/skills/dispatch/references/examples.md +82 -0
- package/skills/dispatch/references/issues.md +213 -0
- package/skills/dispatch/references/messages.md +65 -0
- package/skills/dispatch/references/reading.md +55 -0
- package/skills/dispatch-first/SKILL.md +49 -0
- package/skills/legion-architect/SKILL.md +2 -2
- package/skills/legion-controller/SKILL.md +183 -34
- package/skills/legion-worker/SKILL.md +86 -383
- package/skills/legion-worker/references/conflicts-and-rewrites.md +129 -0
- package/skills/legion-worker/references/merge-gate.md +101 -0
- package/skills/legion-worker/references/pr-body.md +125 -0
- package/skills/legion-worker/references/review-threads.md +71 -0
- package/src/server.ts +22 -1
- package/skills/legion-worker/references/knowledge-injection.md +0 -98
- /package/skills/legion-worker/{resources/strategies → references}/cleanup-deletion.md +0 -0
- /package/skills/legion-worker/{resources/strategies → references}/systematic-rename.md +0 -0
package/dist/src/server.js
CHANGED
|
@@ -13793,6 +13793,16 @@ var AGENT_STREAM_LIMITS = {
|
|
|
13793
13793
|
historyBytes: 512 * 1024,
|
|
13794
13794
|
snapshotIntervalMs: 100
|
|
13795
13795
|
};
|
|
13796
|
+
// ../contracts/src/claim-holds.ts
|
|
13797
|
+
function claimHolds(claim, titles) {
|
|
13798
|
+
if (claim.actor.kind !== "session") {
|
|
13799
|
+
return "holds";
|
|
13800
|
+
}
|
|
13801
|
+
if (titles === undefined) {
|
|
13802
|
+
return "unknown";
|
|
13803
|
+
}
|
|
13804
|
+
return titles.has(claim.actor.id) ? "holds" : "lapsed";
|
|
13805
|
+
}
|
|
13796
13806
|
// ../contracts/src/dispatch-href.ts
|
|
13797
13807
|
function itemFromSearch(search) {
|
|
13798
13808
|
const params = new URLSearchParams(search);
|
|
@@ -16201,17 +16211,19 @@ function componentsLine(components) {
|
|
|
16201
16211
|
}
|
|
16202
16212
|
}
|
|
16203
16213
|
function claimText(claim, titles) {
|
|
16204
|
-
|
|
16214
|
+
const holding = claimHolds(claim, titles);
|
|
16215
|
+
const marker = holding === "unknown" ? " \xB7 liveness unknown" : holding === "lapsed" ? " \xB7 not running" : "";
|
|
16216
|
+
return `${actorLabel(claim.actor, titles)} since ${claim.at}${marker}`;
|
|
16205
16217
|
}
|
|
16206
16218
|
async function liveSessionTitles(client, needed) {
|
|
16207
16219
|
if (!needed) {
|
|
16208
|
-
return
|
|
16220
|
+
return;
|
|
16209
16221
|
}
|
|
16210
16222
|
try {
|
|
16211
16223
|
const agents = await client.listAgents();
|
|
16212
16224
|
return new Map(agents.map((agent) => [agent.session_id, agent.title]));
|
|
16213
16225
|
} catch {
|
|
16214
|
-
return
|
|
16226
|
+
return;
|
|
16215
16227
|
}
|
|
16216
16228
|
}
|
|
16217
16229
|
function holdsSession(claim) {
|
|
@@ -16243,6 +16255,9 @@ function issueSummary(issue2, events, references, graph, titles) {
|
|
|
16243
16255
|
throw new Error("Dispatch issue is missing components");
|
|
16244
16256
|
if (issue2.claim === undefined)
|
|
16245
16257
|
throw new Error("Dispatch issue is missing claim");
|
|
16258
|
+
if (issue2.external_links === undefined) {
|
|
16259
|
+
throw new Error("Dispatch issue is missing external_links");
|
|
16260
|
+
}
|
|
16246
16261
|
return [
|
|
16247
16262
|
`Title: ${issue2.title}`,
|
|
16248
16263
|
`Key: ${issue2.key}`,
|
|
@@ -16254,6 +16269,8 @@ function issueSummary(issue2, events, references, graph, titles) {
|
|
|
16254
16269
|
componentsLine(issue2.components),
|
|
16255
16270
|
`Route: ${routeText(issue2, titles)}`,
|
|
16256
16271
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
16272
|
+
"External links:",
|
|
16273
|
+
...issue2.external_links.length === 0 ? ["- none"] : issue2.external_links.map((link) => `- ${link.url}${link.kind === undefined ? "" : ` (${link.kind})`}`),
|
|
16257
16274
|
"Open asks:",
|
|
16258
16275
|
...asks.length === 0 ? ["- none"] : asks.map((ask) => `- ${ask.id}: ${ask.question}`),
|
|
16259
16276
|
"References:",
|
|
@@ -17287,6 +17304,12 @@ function asObject(value) {
|
|
|
17287
17304
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
17288
17305
|
}
|
|
17289
17306
|
|
|
17307
|
+
// ../envoy-client/src/dispatch-first.ts
|
|
17308
|
+
import { join as join2 } from "path";
|
|
17309
|
+
function dispatchFirstSkillFile(skillsDirectory) {
|
|
17310
|
+
return join2(skillsDirectory, "dispatch-first", "SKILL.md");
|
|
17311
|
+
}
|
|
17312
|
+
|
|
17290
17313
|
// ../envoy-client/src/tool-contract.ts
|
|
17291
17314
|
var DELIVERY_CONTRACT = "Delivery is at-least-once, possibly out of order across topics; use id for dedupe and at for freshness.";
|
|
17292
17315
|
var TOPIC_GUIDE = "Topic guide (all are under notifications.): agent.<session_id> (subscribe: own inbox); role.<role> " + "(publish-to; holders claim via envoy_role_set). > matches one or more trailing tokens and does not " + "match the base subject. Envoy registers the concrete base when you subscribe to <subject>.>, so " + "the recommended default remains github.<owner>.<repo>.pr.<n>.>. <owner> and <repo> are one token " + "each, with every dot in the name written _ (sjawhar/.github is github.sjawhar._github; " + "acme/site.io is github.acme.site_io), and a topic spelled with the dot receives nothing. " + "Default PR subscription: " + "github.<owner>.<repo>.pr.<n>.> (it receives the quiet PR family): pr.<n> (lifecycle: " + "opened/synchronize/closed; closed carries merged, merge_commit_sha, merged_by, head_sha), " + "pr.<n>.comment, pr.<n>.review, pr.<n>.mention, pr.<n>.checks (one settlement event per commit whose checks settle, the head or not, naming its sha: " + 'passed/failed/cancelled/skipped with failing names and URLs; re-fires with superseded_settlement: "true" ' + "when new runs appear for the same commit). Other GitHub: issue.<n>, issue.<n>.comment, " + "issue.<n>.mention, mention, push.branch.<name>, push.tag.<name>, workflow.<file>.<action> (only " + "runs without an associated PR); slack.<team>.<channel>.message|mention and " + "slack.<team>.<channel>.thread.<ts>.message|mention; ghostwispr.<session>.<kind>; " + "whatsapp.<phone>.<jid>.<kind>; envoy.exceptions.<original-topic>.";
|
|
@@ -17752,6 +17775,16 @@ var server_default = async (input) => {
|
|
|
17752
17775
|
if (!dispatchConfig.enabled) {
|
|
17753
17776
|
logger.warn(`envoy: dispatch tools disabled \u2014 ${dispatchConfig.error ?? "no Dispatch URL configured"}`);
|
|
17754
17777
|
}
|
|
17778
|
+
let dispatchFirst;
|
|
17779
|
+
if (dispatchConfig.enabled) {
|
|
17780
|
+
if (skillsDirectory === undefined) {
|
|
17781
|
+
throw new Error(`envoy: no skills directory beside ${moduleDirectory}`);
|
|
17782
|
+
}
|
|
17783
|
+
dispatchFirst = dispatchFirstSkillFile(skillsDirectory);
|
|
17784
|
+
if (!existsSync(dispatchFirst)) {
|
|
17785
|
+
throw new Error(`envoy: the dispatch-first skill ${dispatchFirst} is missing`);
|
|
17786
|
+
}
|
|
17787
|
+
}
|
|
17755
17788
|
const envoyDefaults = envoyDefaultsFromEnvironment(process.env);
|
|
17756
17789
|
const envoy = createEnvoyClient({ baseUrl: envoyDefaults.envoyUrl, fetch: globalThis.fetch });
|
|
17757
17790
|
let activeSessionID = null;
|
|
@@ -17854,6 +17887,11 @@ var server_default = async (input) => {
|
|
|
17854
17887
|
cfg.skills.paths.push(skillsDirectory);
|
|
17855
17888
|
}
|
|
17856
17889
|
}
|
|
17890
|
+
if (dispatchFirst !== undefined) {
|
|
17891
|
+
cfg.instructions ??= [];
|
|
17892
|
+
if (!cfg.instructions.includes(dispatchFirst))
|
|
17893
|
+
cfg.instructions.push(dispatchFirst);
|
|
17894
|
+
}
|
|
17857
17895
|
},
|
|
17858
17896
|
event: async ({
|
|
17859
17897
|
event
|
package/package.json
CHANGED
package/skills/AGENTS.md
CHANGED
|
@@ -8,9 +8,10 @@ event intake, process lifecycle, credentials, and role delivery.
|
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| `ce-simplify-code/` | the implementer, once per pull request | the behaviour-preserving simplify pass before the reviewer's final pass (Legion's copy of the MIT-licensed Compound Engineering skill; `LICENSE` beside it) |
|
|
10
10
|
| `dispatch/` | every role, and any session writing to Dispatch | specs, asks, comments, artifacts, and messages on native Dispatch |
|
|
11
|
+
| `dispatch-first/` | every session with Dispatch configured, injected by each host plugin on every request (Oh My Pi), on every `SessionStart` (startup, resume, clear, compact, fork) and `SubagentStart` (Claude Code), or as an instruction file (OpenCode) | searching Dispatch before acting, extending the existing issue, citing decisions, closing duplicates; kept under 60 lines and 6,000 characters |
|
|
11
12
|
| `envoy/` | every role | subscriptions, agent-to-agent messages, and topic formats |
|
|
12
13
|
| `legion-architect/` | root and sub-architects | tree ownership, decomposition, waves, gates, integration, sign-off |
|
|
13
|
-
| `legion-controller/` | the controller root process | wake routing,
|
|
14
|
+
| `legion-controller/` | the controller root process | wake routing, keeping the admission slots full from `todo`, the daily report, escalation |
|
|
14
15
|
| `legion-oracle/` | any role doing research | repository-grounded research |
|
|
15
16
|
| `legion-retro/` | the implementer, at retro | the pre-merge retrospective and its Dispatch message |
|
|
16
17
|
| `legion-worker/` | planner, implementer, tester, reviewer, merger | the phase contracts: handoffs, GitHub identity, PR body and READY discipline, the merge-gate order |
|
|
@@ -19,6 +20,7 @@ event intake, process lifecycle, credentials, and role delivery.
|
|
|
19
20
|
|
|
20
21
|
The owning skill above is where each contract is defined; a role prompt that needs a contract from its own seat points there or restates only its own step. This file lists and does not restate.
|
|
21
22
|
A Legion prompt (a skill here, a role prompt, or an agent definition in `packages/pi-envoy/agents/`) names a task agent only as `task(agent="<name>")` and a skill it tells the model to load only as `skill://<name>`. Those are the two forms the Go daemon's boot gate and `legion probe-image` resolve through Oh My Pi, refusing by name one it cannot find; a dispatch or a load written any other way goes unchecked. An agent or skill Legion's prompts name is shipped here or in `packages/pi-envoy/agents/`, unless Oh My Pi bundles it.
|
|
23
|
+
A skill file an agent loads stays under 51,200 bytes, Oh My Pi's spill threshold (a longer one arrives with its middle cut out), and a skill's detail lives in `references/` files linked as `skill://<name>/references/<file>.md`, never by relative path, which a read by path cuts at 300 lines. `packages/pi-envoy/src/skills-guard.test.ts` fails on a file at the threshold, on any `SKILL.md` body of 500 lines or more, on a skill whose frontmatter `name` is not its directory's name, on a `dispatch-first` over its budget, on a `skill://<name>/<path>` link that names a missing file or a missing `#heading`, and on a `legion-worker` reference nothing links.
|
|
22
24
|
The text a worker boots with (its role prompt) lives in `packages/pi-envoy/roles/` and is
|
|
23
25
|
composed per role in `packages/daemon/src/daemon/processes.ts`; `packages/pi-envoy/roles/roles.test.ts`
|
|
24
26
|
holds the structural rules for those parts.
|