@sjawhar/pi-legion-envoy 1.42.2 → 1.43.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/envoy.js +11 -1
- package/dist/legion.js +11 -1
- package/dist/skills/dispatch/SKILL.md +48 -0
- package/dist/skills/legion-controller/SKILL.md +20 -0
- package/package.json +2 -2
package/dist/envoy.js
CHANGED
|
@@ -30345,6 +30345,12 @@ var stateTreeLocator = discriminatedUnion("runtime", [
|
|
|
30345
30345
|
stateTmuxLocator.extend({ ompSessionFile: nonEmptyString.optional() }),
|
|
30346
30346
|
stateK8sLocator.extend({ ompSessionFile: nonEmptyString.optional() })
|
|
30347
30347
|
]);
|
|
30348
|
+
var stateExternalControllerLocator = strictObject({
|
|
30349
|
+
runtime: literal("kubernetes"),
|
|
30350
|
+
external: literal(true),
|
|
30351
|
+
sessionId: nonEmptyString,
|
|
30352
|
+
registeredAt: number2().int().nonnegative()
|
|
30353
|
+
});
|
|
30348
30354
|
var stateIssue = strictObject({
|
|
30349
30355
|
key: nonEmptyString,
|
|
30350
30356
|
title: string2(),
|
|
@@ -30400,7 +30406,7 @@ var LegionDaemonApi = {
|
|
|
30400
30406
|
queue: array(nonEmptyString)
|
|
30401
30407
|
}),
|
|
30402
30408
|
gates: record(string2(), stateGate),
|
|
30403
|
-
controllerLocator: stateTreeLocator.optional(),
|
|
30409
|
+
controllerLocator: union([stateTreeLocator, stateExternalControllerLocator]).optional(),
|
|
30404
30410
|
roles: record(string2(), stateRole),
|
|
30405
30411
|
controllerPendingNotices: number2().int().nonnegative(),
|
|
30406
30412
|
pendingStatusWrites: array(nonEmptyString),
|
|
@@ -30415,6 +30421,10 @@ var LegionDaemonApi = {
|
|
|
30415
30421
|
}),
|
|
30416
30422
|
response: object({})
|
|
30417
30423
|
},
|
|
30424
|
+
ControllerSecret: {
|
|
30425
|
+
request: strictObject({}),
|
|
30426
|
+
response: object({ secret: nonEmptyString })
|
|
30427
|
+
},
|
|
30418
30428
|
ProcessStarted: {
|
|
30419
30429
|
request: strictObject({
|
|
30420
30430
|
tree: nonEmptyString,
|
package/dist/legion.js
CHANGED
|
@@ -29619,6 +29619,12 @@ var stateTreeLocator = discriminatedUnion("runtime", [
|
|
|
29619
29619
|
stateTmuxLocator.extend({ ompSessionFile: nonEmptyString.optional() }),
|
|
29620
29620
|
stateK8sLocator.extend({ ompSessionFile: nonEmptyString.optional() })
|
|
29621
29621
|
]);
|
|
29622
|
+
var stateExternalControllerLocator = strictObject({
|
|
29623
|
+
runtime: literal("kubernetes"),
|
|
29624
|
+
external: literal(true),
|
|
29625
|
+
sessionId: nonEmptyString,
|
|
29626
|
+
registeredAt: number2().int().nonnegative()
|
|
29627
|
+
});
|
|
29622
29628
|
var stateIssue = strictObject({
|
|
29623
29629
|
key: nonEmptyString,
|
|
29624
29630
|
title: string2(),
|
|
@@ -29674,7 +29680,7 @@ var LegionDaemonApi = {
|
|
|
29674
29680
|
queue: array(nonEmptyString)
|
|
29675
29681
|
}),
|
|
29676
29682
|
gates: record(string2(), stateGate),
|
|
29677
|
-
controllerLocator: stateTreeLocator.optional(),
|
|
29683
|
+
controllerLocator: union([stateTreeLocator, stateExternalControllerLocator]).optional(),
|
|
29678
29684
|
roles: record(string2(), stateRole),
|
|
29679
29685
|
controllerPendingNotices: number2().int().nonnegative(),
|
|
29680
29686
|
pendingStatusWrites: array(nonEmptyString),
|
|
@@ -29689,6 +29695,10 @@ var LegionDaemonApi = {
|
|
|
29689
29695
|
}),
|
|
29690
29696
|
response: object({})
|
|
29691
29697
|
},
|
|
29698
|
+
ControllerSecret: {
|
|
29699
|
+
request: strictObject({}),
|
|
29700
|
+
response: object({ secret: nonEmptyString })
|
|
29701
|
+
},
|
|
29692
29702
|
ProcessStarted: {
|
|
29693
29703
|
request: strictObject({
|
|
29694
29704
|
tree: nonEmptyString,
|
|
@@ -183,6 +183,10 @@ passage with `anchor`. Follow up on an ask or comment with `dispatch_comment`; c
|
|
|
183
183
|
with a `dispatch://` reference (see [References](#references)). Never write "see above", "the
|
|
184
184
|
message above", or "as attached".
|
|
185
185
|
|
|
186
|
+
**A decision about an uploaded artifact links it.** If the human must read an artifact to answer,
|
|
187
|
+
the question carries `dispatch://KEY/artifact/<slug>` (or `ref`), never just its filename. Text
|
|
188
|
+
they must read to decide belongs in the spec in the first place — see [Artifacts](#artifacts).
|
|
189
|
+
|
|
186
190
|
Before saying you are waiting for human input, call `dispatch_open_asks`. It lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply.
|
|
187
191
|
|
|
188
192
|
**Anything that needs the human is an ask, or it does not exist.** An approval, a credential,
|
|
@@ -431,6 +435,15 @@ with your text. Never do that: the spec is edited in place with `dispatch_doc_ed
|
|
|
431
435
|
artifact by the slug shown in the upload result or by its filename, and a project document by its artifact id, slug, or filename; the
|
|
432
436
|
slug also arrives on `artifact.created` events.
|
|
433
437
|
|
|
438
|
+
**Where a deliverable goes.** Text the human must read to decide — a draft message, a proposal,
|
|
439
|
+
a summary — goes in the spec as a section: the spec is the one document they open. A separate
|
|
440
|
+
artifact is for a real file: something sent as-is, a long report, a binary, a screenshot.
|
|
441
|
+
|
|
442
|
+
When you do upload one, the spec links it as `dispatch://KEY/artifact/<slug>` (the `slug` from the
|
|
443
|
+
upload result; it renders as a link) at the place the reader needs it, and the ask that needs the
|
|
444
|
+
decision carries the same reference. A heading or a sentence naming the filename is not a
|
|
445
|
+
reference.
|
|
446
|
+
|
|
434
447
|
Documents are CommonMark. A bare `<https://example.com|text>` is a CommonMark autolink and is normalised: the angle brackets are
|
|
435
448
|
dropped and the URL keeps `|text`. A backslash-escaped `\<https://example.com|text>` displays as `<https://example.com|text>` in the
|
|
436
449
|
document but comes back re-escaped (`\<`) from `dispatch_doc_read`. A Slack mrkdwn draft, or any other payload that is not Markdown,
|
|
@@ -607,3 +620,38 @@ dispatch_message({
|
|
|
607
620
|
body: "Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.",
|
|
608
621
|
})
|
|
609
622
|
```
|
|
623
|
+
|
|
624
|
+
Before — a draft the human must read is uploaded as a separate file, the spec only names it, and
|
|
625
|
+
the ask does not point at it, so the reader has to go looking:
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
dispatch_artifact({ issue: "OPS-52", name: "cu-update-2026-09-15.md", content: "Hi team, ..." })
|
|
629
|
+
dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
|
|
630
|
+
{ op: "insert", after: "## Context", markdown: "## Draft (artifact cu-update-2026-09-15.md)" },
|
|
631
|
+
]})
|
|
632
|
+
dispatch_ask({ issue: "OPS-52", question: "Send the customer update as drafted?", options: [...] })
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
After — the draft is a section of the spec, and the ask anchors there. If it really must be a
|
|
636
|
+
file (something to send as-is), the spec and the ask both link the slug from the upload result:
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
|
|
640
|
+
{ op: "insert", after: "## Context", markdown: "## Draft\n\nHi team, ..." },
|
|
641
|
+
]})
|
|
642
|
+
dispatch_ask({
|
|
643
|
+
issue: "OPS-52",
|
|
644
|
+
question: "Send the customer update as drafted?",
|
|
645
|
+
options: [...],
|
|
646
|
+
anchor: { artifact: "spec", quote: "Hi team," },
|
|
647
|
+
})
|
|
648
|
+
// or, for a real file — the spec links it where the reader needs it, and so does the ask:
|
|
649
|
+
dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
|
|
650
|
+
{ op: "insert", after: "## Context", markdown: "## Draft\n\nThe update to send as-is: dispatch://OPS-52/artifact/cu-update-2026-09-15-md" },
|
|
651
|
+
]})
|
|
652
|
+
dispatch_ask({
|
|
653
|
+
issue: "OPS-52",
|
|
654
|
+
question: "Send this customer update as-is? dispatch://OPS-52/artifact/cu-update-2026-09-15-md",
|
|
655
|
+
options: [...],
|
|
656
|
+
})
|
|
657
|
+
```
|
|
@@ -64,6 +64,26 @@ command run `/legion-claim-controller` again.
|
|
|
64
64
|
This handshake lets the daemon redeliver held controller work. It does not turn the controller
|
|
65
65
|
into a state holder: daemon state and the Dispatch project remain authoritative.
|
|
66
66
|
|
|
67
|
+
### Started by the operator (runtime: kubernetes)
|
|
68
|
+
|
|
69
|
+
When the daemon runs inside a Kubernetes cluster it cannot open a terminal anywhere, so nobody
|
|
70
|
+
launched your pane: the operator ran `legion controller start --config controller.yaml
|
|
71
|
+
[--daemon-url <port-forward>]` on their own machine, and you are that foreground OMP session.
|
|
72
|
+
The command fetched a fresh controller secret from the daemon with the operator's token, wrote it
|
|
73
|
+
to a 0600 file under `LEGION_STATE_DIR` (`~/.local/state/legion/<project>-controller` by default)
|
|
74
|
+
beside the `gh` shim and the `legion` launcher, and started you with `LEGION_CONTROLLER=1` and
|
|
75
|
+
the same environment a tmux controller pane carries — so the extension claims the role and calls
|
|
76
|
+
`/controller/ready` exactly as under tmux, and nothing changes in how you handle wakes. The
|
|
77
|
+
daemon records you as `controllerLocator: {runtime: "kubernetes", external: true, sessionId,
|
|
78
|
+
registeredAt}` and reads your liveness from the Envoy role registry (the holder of
|
|
79
|
+
`legion-<project>-controller` and its `last_seen`), not from a pane: keep the session running.
|
|
80
|
+
Exiting it leaves the project without a controller until the operator runs the command again —
|
|
81
|
+
the daemon logs `controller not registered; run legion controller start` once per boot-timeout
|
|
82
|
+
interval and launches nothing itself. `legion state`, `legion gh -- <args>`, and
|
|
83
|
+
`legion status <KEY> <status>` work here over `LEGION_DAEMON_URL` (the port-forward). A second
|
|
84
|
+
`legion controller start` replaces you: it mints a new secret, so your grants stop working and
|
|
85
|
+
the role moves to the new session.
|
|
86
|
+
|
|
67
87
|
## Deployment instructions
|
|
68
88
|
|
|
69
89
|
Deployment instructions, when present, are the operator's standing rules for this repository —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjawhar/pi-legion-envoy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"omp": {
|
|
6
6
|
"extensions": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
"legion": {
|
|
15
|
-
"daemonApiVersion":
|
|
15
|
+
"daemonApiVersion": 6
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|