@sjawhar/pi-legion-envoy 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -64,18 +64,18 @@ OMP at extension files it does not contain.
|
|
|
64
64
|
|
|
65
65
|
## Dispatch
|
|
66
66
|
|
|
67
|
-
Legion sessions
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
Every OMP session — Legion sessions included — gets the `dispatch` MCP tool the way
|
|
68
|
+
OpenCode sessions do: the shared `@legion/envoy-client` shim mounts as a stdio MCP server,
|
|
69
|
+
and it serves when `dispatch.enabled` is true in the shared envoy.json
|
|
70
|
+
(`~/.config/opencode/envoy.json`, shallow-merged with `<cwd>/.opencode/envoy.json`) or
|
|
71
|
+
`DISPATCH_MCP_URL` is set explicitly. The server URL comes from `dispatch.serverUrl`
|
|
72
|
+
(default `http://localhost:8766`). Dispatch exists so headless unattended agents —
|
|
73
|
+
Legion architects, planners, phase workers — can raise durable questions to the human.
|
|
74
|
+
Replies route back to the asking session, which is auto-subscribed to the thread's
|
|
75
|
+
GitHub topic; a Legion role's session survives kill/resume because Legion resurrection
|
|
76
|
+
resumes the same OMP session file, so the reply still lands. Lifecycle and scope
|
|
77
|
+
decisions still go through `hub` to the owning architect — Dispatch is for durable
|
|
78
|
+
questions to the human, not for coordination between roles.
|
|
79
79
|
|
|
80
80
|
Mount it in `~/.omp/agent/mcp.json` (user-wide) or `.omp/mcp.json` (per project):
|
|
81
81
|
|
package/dist/envoy.js
CHANGED
|
@@ -29857,21 +29857,6 @@ var LegionDaemonApi = {
|
|
|
29857
29857
|
request: architectCapability.extend({ issue: nonEmptyString, comment: exports_external.string().optional() }),
|
|
29858
29858
|
response: exports_external.object({})
|
|
29859
29859
|
},
|
|
29860
|
-
DispatchThread: {
|
|
29861
|
-
request: exports_external.strictObject({
|
|
29862
|
-
tree: nonEmptyString,
|
|
29863
|
-
issue: nonEmptyString,
|
|
29864
|
-
role: legionRole,
|
|
29865
|
-
sessionId: nonEmptyString,
|
|
29866
|
-
secret: nonEmptyString,
|
|
29867
|
-
parent: nonEmptyString,
|
|
29868
|
-
subject: nonEmptyString,
|
|
29869
|
-
body: nonEmptyString,
|
|
29870
|
-
ask: exports_external.unknown().optional(),
|
|
29871
|
-
urgency: exports_external.enum(["low", "med", "high", "blocking"]).optional()
|
|
29872
|
-
}),
|
|
29873
|
-
response: exports_external.object({ thread: exports_external.number().int(), url: nonEmptyString })
|
|
29874
|
-
},
|
|
29875
29860
|
SpawnToken: {
|
|
29876
29861
|
request: architectCapability.extend({ issue: nonEmptyString, role: legionRole }),
|
|
29877
29862
|
response: exports_external.object({ spawnToken: nonEmptyString })
|
|
@@ -29961,6 +29946,29 @@ function normalizeEnvoyUrl(value) {
|
|
|
29961
29946
|
return value.replace(/\/+$/, "");
|
|
29962
29947
|
}
|
|
29963
29948
|
|
|
29949
|
+
// ../envoy-client/src/dispatch-subscribe.ts
|
|
29950
|
+
var ISSUE_URL_RE = /https?:\/\/github\.com\/([^/\s"]+)\/([^/\s"]+)\/issues\/(\d+)/i;
|
|
29951
|
+
var DISPATCH_TOOL_RE = /(^|[-._])dispatch$/i;
|
|
29952
|
+
function isDispatchTool(tool) {
|
|
29953
|
+
return DISPATCH_TOOL_RE.test(tool);
|
|
29954
|
+
}
|
|
29955
|
+
function dispatchThreadTopic(owner, repo, thread) {
|
|
29956
|
+
return `notifications.github.${owner}.${repo}.issue.${thread}.>`;
|
|
29957
|
+
}
|
|
29958
|
+
function dispatchSubscriptionTopic(tool, output) {
|
|
29959
|
+
if (!isDispatchTool(tool))
|
|
29960
|
+
return null;
|
|
29961
|
+
const match = ISSUE_URL_RE.exec(output);
|
|
29962
|
+
if (!match)
|
|
29963
|
+
return null;
|
|
29964
|
+
const owner = match[1];
|
|
29965
|
+
const repo = match[2];
|
|
29966
|
+
const thread = Number(match[3]);
|
|
29967
|
+
if (!Number.isInteger(thread) || thread <= 0)
|
|
29968
|
+
return null;
|
|
29969
|
+
return dispatchThreadTopic(owner, repo, thread);
|
|
29970
|
+
}
|
|
29971
|
+
|
|
29964
29972
|
// ../envoy-client/src/errors.ts
|
|
29965
29973
|
function messageFor(error48) {
|
|
29966
29974
|
return error48 instanceof Error ? error48.message : String(error48);
|
|
@@ -30897,6 +30905,18 @@ function envoyExtension(pi) {
|
|
|
30897
30905
|
}, defaults.heartbeatMs);
|
|
30898
30906
|
heartbeatRegistered = true;
|
|
30899
30907
|
};
|
|
30908
|
+
const recoverRegisteredInterests = async () => {
|
|
30909
|
+
const registry2 = await client.getInterest(sessionID).catch(() => {
|
|
30910
|
+
return;
|
|
30911
|
+
});
|
|
30912
|
+
if (registry2 === undefined)
|
|
30913
|
+
return;
|
|
30914
|
+
for (const topic of registry2.topics) {
|
|
30915
|
+
if (topic === agentSubject(sessionID) || topic.startsWith(ROLE_TOPIC_PREFIX))
|
|
30916
|
+
continue;
|
|
30917
|
+
await subscribe(topic);
|
|
30918
|
+
}
|
|
30919
|
+
};
|
|
30900
30920
|
const establishSession = async (context) => {
|
|
30901
30921
|
const previousTopic = sessionID === "" ? undefined : agentSubject(sessionID);
|
|
30902
30922
|
sessionDirectory = context.cwd;
|
|
@@ -30908,6 +30928,9 @@ function envoyExtension(pi) {
|
|
|
30908
30928
|
}
|
|
30909
30929
|
await ensureConnection();
|
|
30910
30930
|
await subscribe(currentTopic);
|
|
30931
|
+
if ((context.sessionManager.getBranch?.() ?? []).length > 0) {
|
|
30932
|
+
await recoverRegisteredInterests();
|
|
30933
|
+
}
|
|
30911
30934
|
if (registrationRequired()) {
|
|
30912
30935
|
await registerSession();
|
|
30913
30936
|
ensureHeartbeat(context);
|
|
@@ -31003,6 +31026,19 @@ function envoyExtension(pi) {
|
|
|
31003
31026
|
});
|
|
31004
31027
|
}
|
|
31005
31028
|
registerEnvoyWhoamiCommand(pi, () => sessionID);
|
|
31029
|
+
pi.on("tool_result", async (event) => {
|
|
31030
|
+
if (event.isError)
|
|
31031
|
+
return;
|
|
31032
|
+
const topic = dispatchSubscriptionTopic(event.toolName, JSON.stringify(event.details) ?? "");
|
|
31033
|
+
if (topic === null)
|
|
31034
|
+
return;
|
|
31035
|
+
try {
|
|
31036
|
+
if (await subscribe(topic))
|
|
31037
|
+
await registerSession();
|
|
31038
|
+
} catch (error48) {
|
|
31039
|
+
activeSessionContext?.ui.notify(`envoy: dispatch reply auto-subscribe failed (${messageFor(error48)}); run envoy_subscribe ${topic}`, "warning");
|
|
31040
|
+
}
|
|
31041
|
+
});
|
|
31006
31042
|
async function execute(operation, parameters) {
|
|
31007
31043
|
try {
|
|
31008
31044
|
switch (operation) {
|
package/dist/skills/AGENTS.md
CHANGED
|
@@ -25,9 +25,10 @@ returns that schema to the architect. It writes the same phase-specific payload
|
|
|
25
25
|
`.legion/<phase>.json`, verifies it exists, and commits the handoff before reporting completion.
|
|
26
26
|
The committed predecessor handoff wins after revival or re-creation.
|
|
27
27
|
|
|
28
|
-
Workers do not run a controller loop
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
Workers do not run a controller loop or mutate lifecycle labels. Workers coordinate
|
|
29
|
+
lifecycle, scope, and cross-phase decisions with the owning architect through hub, sending
|
|
30
|
+
the verified observation and decision needed. A worker may use the `dispatch` MCP tool
|
|
31
|
+
directly for a durable human question; replies come back to the worker's own session.
|
|
31
32
|
|
|
32
33
|
## Durable artifacts
|
|
33
34
|
|
|
@@ -15,7 +15,9 @@ separate coordinator to finish necessary work.
|
|
|
15
15
|
- Use the `legion` tool for lifecycle writes. Its issue key format is
|
|
16
16
|
`owner/repo#number`.
|
|
17
17
|
- Use `task` for every Legion role spawn and `hub` to direct or revive a known phase
|
|
18
|
-
worker. Phase workers escalate
|
|
18
|
+
worker. Phase workers escalate lifecycle, scope, and cross-phase matters inward to you
|
|
19
|
+
through hub. Any role may use `dispatch` directly for a standalone human question;
|
|
20
|
+
replies return to the asking session.
|
|
19
21
|
- The runtime, not you, appends a machine `<legion-spawn>` block. Each Legion `task`
|
|
20
22
|
text must start with `Legion-Issue: <owner/repo#n>` on its first line.
|
|
21
23
|
- Use only the live label vocabulary: `needs-approval`, `human-approved`,
|
|
@@ -57,7 +59,7 @@ sub-architect:
|
|
|
57
59
|
```text
|
|
58
60
|
legion({ op: "post_spec", issue: "<root issue>", body: "<root specification>" })
|
|
59
61
|
legion({ op: "label_add", issue: "<root issue>", label: "needs-approval" })
|
|
60
|
-
|
|
62
|
+
dispatch({
|
|
61
63
|
parent: "<root issue>",
|
|
62
64
|
subject: "Legion design approval requested",
|
|
63
65
|
body: "<summary, specification, and requested decision>"
|
|
@@ -186,7 +188,6 @@ corresponding lifecycle procedure.
|
|
|
186
188
|
| `pr-blocked` | Read the failed CI evidence and recovery attempts. Assign a focused implementer or corrective child, then return it through testing and review; do not treat the blocked PR as final. |
|
|
187
189
|
| `pr-closed-unmerged` | Decide from current scope whether to reopen the work, send a fresh implementer, or cancel it with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
|
|
188
190
|
| `issue-comment` | Interpret the comment in the issue's design context. Answer it, adjust the plan, or relay it through `hub` to the responsible worker; scope and product decisions remain with you. |
|
|
189
|
-
| `dispatch-reply` | Resolve the question that opened the thread, record the resulting decision in the tree's work, and direct the affected worker through `hub`. |
|
|
190
191
|
| `catchup-overseer` | Verify its gates, child counts, and PR verdicts against current artifacts, then resume the applicable numbered lifecycle step. It is a current-state snapshot, not a raw-event replay. |
|
|
191
192
|
| `revive-worker` | The extension has revived the backed worker. Do not create a duplicate; direct the restored worker through `hub` if action is needed and rely on its committed handoff over recollection. |
|
|
192
193
|
| `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
|
|
@@ -195,5 +196,6 @@ corresponding lifecycle procedure.
|
|
|
195
196
|
|
|
196
197
|
Controller-actionable matters are exactly re-filing a genuinely independent child,
|
|
197
198
|
capacity, and cross-tree conflict. Use the Legion escalation operation for those. Handle
|
|
198
|
-
everything else in the tree or
|
|
199
|
-
|
|
199
|
+
everything else in the tree, or use `dispatch` for a human question; workers may reach
|
|
200
|
+
Sami directly with `dispatch` the same way. Do not create a wait loop for any wake
|
|
201
|
+
source.
|
|
@@ -46,7 +46,7 @@ into a state holder: daemon state and GitHub artifacts remain authoritative.
|
|
|
46
46
|
|---|---|---|
|
|
47
47
|
| New issue added to the project board (webhook: issue opened / project item added; resync heals misses) | issue ref + triage context (incl. pre-existing children) | Triage: spawn root process via daemon admission, or park in the daemon-state backlog |
|
|
48
48
|
| Backlog eligibility | slot freed / priority change | Reconsider parked items; deliberately-backlogged issues carry a marker so resync doesn't re-flag them |
|
|
49
|
-
| Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; human Q&A
|
|
49
|
+
| Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; issue-scoped human Q&A goes through `dispatch` from the owning architect, not here |
|
|
50
50
|
| Resync report | artifact-driven anomaly list (zero-owner trees, erroring issues) | Verify against fresh state, then dispatch/heal |
|
|
51
51
|
| Mention | Slack/GitHub @mention text | Answer, or route to the owning issue's architect role |
|
|
52
52
|
| Approval interpretation | ambiguous human comment on a gated issue | Decide whether it's an approval; if so, apply `human-approved` via the daemon |
|
|
@@ -82,8 +82,8 @@ is a deliberate controller decision, not a no-op.
|
|
|
82
82
|
## Architect escalation
|
|
83
83
|
|
|
84
84
|
Only decide controller-actionable escalations: re-filing independent work, capacity, and
|
|
85
|
-
cross-tree conflicts.
|
|
86
|
-
|
|
85
|
+
cross-tree conflicts. Issue-scoped human Q&A goes through `dispatch` from the owning
|
|
86
|
+
architect, not the controller.
|
|
87
87
|
|
|
88
88
|
For an independence judgment, verify the child and its parent against GitHub and current
|
|
89
89
|
daemon state. If the work belongs in an independent root:
|
|
@@ -19,9 +19,10 @@ its own role again.
|
|
|
19
19
|
|
|
20
20
|
Read the current issue and its acceptance criteria before changing the workspace. Work only
|
|
21
21
|
on this phase's artifact. You may use ordinary scouts, reviewers, and oracle subagents for
|
|
22
|
-
phase work; never spawn legion-role workers
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
phase work; never spawn legion-role workers. Escalate a product, scope, cross-phase, or
|
|
23
|
+
lifecycle decision to the owning architect through hub, with the verified facts and the
|
|
24
|
+
decision needed. For a durable question that needs Sami directly, you may use the raw
|
|
25
|
+
`dispatch` MCP tool yourself; replies return to your own session.
|
|
25
26
|
|
|
26
27
|
## Workspace and handoff precedence
|
|
27
28
|
|
|
@@ -171,5 +172,6 @@ pipeline labels, run a controller loop, or notify a controller with an invented
|
|
|
171
172
|
protocol. A direct worker delivery belongs to its role; overseers receive only derived
|
|
172
173
|
verdicts.
|
|
173
174
|
|
|
174
|
-
When blocked, send the owning architect a concise
|
|
175
|
-
observation, what you tried, and the decision required.
|
|
175
|
+
When blocked on lifecycle, scope, or cross-phase matters, send the owning architect a concise
|
|
176
|
+
hub message: issue, phase, verified observation, what you tried, and the decision required.
|
|
177
|
+
Reach for `dispatch` yourself only for a standalone human question outside that coordination.
|