@sjawhar/opencode-legion-envoy 3.2.1 → 3.2.3
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 +6 -3
- package/package.json +1 -1
- package/skills/envoy/SKILL.md +19 -0
- package/skills/legion-controller/SKILL.md +21 -3
package/dist/src/server.js
CHANGED
|
@@ -17267,7 +17267,7 @@ var envoyToolSpecs = [
|
|
|
17267
17267
|
},
|
|
17268
17268
|
{
|
|
17269
17269
|
name: "envoy_whoami",
|
|
17270
|
-
description: "Returns this session's Envoy identity: session ID, machine ID, port, and directory.",
|
|
17270
|
+
description: "Returns this session's Envoy identity: session ID, machine ID, port, and directory. session_id is the address a reply reaches. Where a host runs a task subagent inside its parent's process, such a subagent registers no Envoy session of its own, so its session_id is the parent session that spawned it and the result says so.",
|
|
17271
17271
|
arguments: () => ({}),
|
|
17272
17272
|
operation: EnvoyToolOperation.whoami,
|
|
17273
17273
|
requiresSubscriptionCapability: false
|
|
@@ -17435,7 +17435,7 @@ function createEnvoyClient(config2) {
|
|
|
17435
17435
|
const idempotencyKey = input.idempotencyKey ?? crypto.randomUUID();
|
|
17436
17436
|
const response = EnvelopeResponseSchema.parse(JSON.parse(await post("/v1/messages/send", {
|
|
17437
17437
|
source: input.source ?? "agent",
|
|
17438
|
-
...input.sourceSessionID
|
|
17438
|
+
...sourceSession(input.sourceSessionID),
|
|
17439
17439
|
target_session: input.targetSessionID,
|
|
17440
17440
|
message: input.message,
|
|
17441
17441
|
idempotency_key: idempotencyKey,
|
|
@@ -17451,7 +17451,7 @@ function createEnvoyClient(config2) {
|
|
|
17451
17451
|
const idempotencyKey = input.idempotencyKey ?? crypto.randomUUID();
|
|
17452
17452
|
const response = EnvelopeResponseSchema.parse(JSON.parse(await post("/v1/messages/publish", {
|
|
17453
17453
|
source: input.source ?? "agent",
|
|
17454
|
-
...input.sourceSessionID
|
|
17454
|
+
...sourceSession(input.sourceSessionID),
|
|
17455
17455
|
topic: input.topic,
|
|
17456
17456
|
message: input.message,
|
|
17457
17457
|
...input.payload === undefined ? {} : { payload: input.payload },
|
|
@@ -17514,6 +17514,9 @@ function messageMetadata(input) {
|
|
|
17514
17514
|
...input.expiresAt === undefined ? {} : { expires_at: input.expiresAt }
|
|
17515
17515
|
};
|
|
17516
17516
|
}
|
|
17517
|
+
function sourceSession(sessionID) {
|
|
17518
|
+
return sessionID === undefined || sessionID === "" ? {} : { source_session: sessionID };
|
|
17519
|
+
}
|
|
17517
17520
|
|
|
17518
17521
|
// src/server.ts
|
|
17519
17522
|
import { tool } from "@opencode-ai/plugin/tool";
|
package/package.json
CHANGED
package/skills/envoy/SKILL.md
CHANGED
|
@@ -106,6 +106,25 @@ envoy_send(
|
|
|
106
106
|
)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
### Your own address, and a subagent's
|
|
110
|
+
|
|
111
|
+
`envoy_whoami`'s `session_id` is the address a reply to you reaches. Inside a `task` subagent it
|
|
112
|
+
is the session that spawned you: a subagent registers no Envoy session of its own, so a peer
|
|
113
|
+
answering it reaches that session, which relays to you over hub. The `subagent` field in that
|
|
114
|
+
output carries your own host session id — it is not an address, so never hand it to a peer.
|
|
115
|
+
|
|
116
|
+
`session_id` is empty when nothing can be reached: a process that took no Envoy identity, or a
|
|
117
|
+
subagent whose spawning session this process can no longer place (it forked away, and other
|
|
118
|
+
top-level sessions are running). Then your messages carry no sender either, so say who you are
|
|
119
|
+
in the message body. An empty address is deliberate — being handed an unrelated live session
|
|
120
|
+
would send your peers to an agent that never spawned you.
|
|
121
|
+
|
|
122
|
+
Your own `envoy_publish` never reaches the agent that spawned you. The listener delivers nothing
|
|
123
|
+
to the session a message names as its source, and inside a subagent that source is your parent,
|
|
124
|
+
so a publish to a role it holds — or to any topic it subscribes to — is accepted and delivered to
|
|
125
|
+
nobody. Use hub for that one hop. `envoy_send` to any other session, including a reply, is
|
|
126
|
+
unaffected.
|
|
127
|
+
|
|
109
128
|
### Delivery capabilities
|
|
110
129
|
|
|
111
130
|
Each session row from `envoy_sessions` carries `capabilities`, the targeted-delivery modes that
|
|
@@ -71,9 +71,11 @@ launcher, and started you with `LEGION_CONTROLLER=1` and the same environment a
|
|
|
71
71
|
carries, so nothing changes in how you handle wakes. Under the TypeScript daemon the extension
|
|
72
72
|
claims the role and calls `/controller/ready` exactly as under tmux; under the Go daemon
|
|
73
73
|
(`LEGION_DAEMON_API=go` in your environment) it registers on `/legion/v1/claims/register` with the
|
|
74
|
-
secret,
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
secret, claims the role, then subscribes to `notifications.legion.<project>.controller`, where the
|
|
75
|
+
Go daemon publishes the two Go rows of the wake routing table. The daemon records you as
|
|
76
|
+
`controllerLocator: {runtime, external: true, sessionId, registeredAt}`, `runtime` being the
|
|
77
|
+
daemon's own (`kubernetes`, or `tmux` under the Go daemon). The TypeScript daemon reads your
|
|
78
|
+
liveness from the Envoy role registry (the holder of
|
|
77
79
|
`legion-<project>-controller` and its `last_seen`), not from a pane: keep the session running.
|
|
78
80
|
Exiting it leaves the project without a controller until the operator runs the command again —
|
|
79
81
|
the TypeScript daemon logs `controller not registered; run legion controller start` once per
|
|
@@ -81,6 +83,20 @@ boot-timeout interval and launches nothing itself. `legion state` and `legion st
|
|
|
81
83
|
<status>` work here over `LEGION_DAEMON_URL`. A second `legion controller start` replaces you: it
|
|
82
84
|
mints a new secret, so your grants stop working and the role moves to the new session.
|
|
83
85
|
|
|
86
|
+
### What happened before you started (Go daemon)
|
|
87
|
+
|
|
88
|
+
The Go daemon's controller topic is a wake for a session that is running when it is published.
|
|
89
|
+
Envoy hands an Oh My Pi session no retained copy of a notice published before it subscribed, so a
|
|
90
|
+
hold or a tree architect's failed claim from while no controller ran never arrives as a wake. At
|
|
91
|
+
every start, before anything else, read `legion state --json` and handle each issue whose
|
|
92
|
+
`issues.<KEY>.phase` is `held` (its `issues.<KEY>.holdReason` is `escalated` when its architect
|
|
93
|
+
sent it to you, and absent while the architect is still deciding or while its tree lingers or is
|
|
94
|
+
closed, where the hold waits for the tree's re-admission and needs nothing from you), and each
|
|
95
|
+
tree root whose `issues.<KEY>.architect.state` is `failed` and whose `issues.<KEY>.phase` is not
|
|
96
|
+
`done`, exactly as the matching wake below. A parked tree (root phase `done`: it lingers or is
|
|
97
|
+
closed) needs nothing from you: a failed architect ignores the park and reads `failed` until the
|
|
98
|
+
tree closes. The issue record is the truth; the topic is the wake.
|
|
99
|
+
|
|
84
100
|
## Deployment instructions
|
|
85
101
|
|
|
86
102
|
Deployment instructions, when present, are the operator's standing rules for this repository —
|
|
@@ -116,6 +132,8 @@ quoted here.
|
|
|
116
132
|
| Mention | Slack/GitHub PR @mention text | Answer, or route to the owning issue's architect role |
|
|
117
133
|
| READY packet seen on a Dispatch issue (via issue subscription) | READY line + gate facts | No action: a human merges; the merger has already notified the queue role if the project has one |
|
|
118
134
|
| `worker-recovered` (role `architect`) from the daemon | issue, fromRef | A root architect's tree volume was lost; it restarted as a new session. Verify the tree is active in `legion state` and that the architect posts its next step on the issue within one resync interval; otherwise treat it as an anomaly. |
|
|
135
|
+
| `held on <KEY>` from the Go daemon (payload `{kind: "held", phase, role?, reason?}`) | the held issue, the phase it left, and the role whose claim failed, or `reason: "escalated"` | Verify the hold in `legion state` (the issue's phase is `held`). Without `reason`, a phase worker's launches or prompts ran out and the tree's architect decides retry or escalate: no action. With `reason: "escalated"` (on the record, `issues.<KEY>.holdReason` is `escalated`), the architect sent it to you: handle it as an architect escalation below. Parking the tree is `legion status <root> backlog`; setting the root back to `todo` later re-admits it as a new generation, which starts again from its architect |
|
|
136
|
+
| `worker-died on <KEY>` from the Go daemon with `role: "architect"` | the tree root whose architect's claim failed, and the phase the root was in | The tree's architect ran out of launches or prompts and the daemon relaunches nothing; every other notice of the tree goes to that architect, so nobody inside the tree can act. Verify in `legion state` (`issues.<KEY>.architect.state` is `failed`); if the root's phase is `done`, the tree is already parked: no action. Otherwise re-admit the tree (`legion status <root> backlog`, then `todo`: a new generation, whose architect starts again with fresh budgets) or leave it parked and say why on the issue |
|
|
119
137
|
| Closed-tree activity (comment, review, CI on a closed tree) | issue, root, event summary | Read the artifact; if work should resume, `legion status <root> todo`; otherwise no action — the event is not held or redelivered |
|
|
120
138
|
| Direct user message | — | Always first |
|
|
121
139
|
|