@sjawhar/opencode-legion-envoy 6.15.2 → 6.17.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
CHANGED
|
@@ -17297,22 +17297,31 @@ async function refuseOpenDecisionBlocks(client, tool, resolved) {
|
|
|
17297
17297
|
const asks = new Map(documentAsks.map((ask) => [ask.block_id, ask]));
|
|
17298
17298
|
const lines = version2.markdown.split(`
|
|
17299
17299
|
`);
|
|
17300
|
+
const stillOpen = (named, state) => {
|
|
17301
|
+
const next = state === "answered" ? "fold the answer into the text" : "write the decision into the text";
|
|
17302
|
+
return `${named}, ${state} but still open in version ${latest}: ${next} with dispatch doc-edit, which writes a version that carries it`;
|
|
17303
|
+
};
|
|
17300
17304
|
const open = blocks.flatMap((block) => {
|
|
17301
17305
|
const ask = asks.get(block.id);
|
|
17306
|
+
const openers = lines.filter((line) => line.includes(`ask{#${block.id} `) || line.includes(`ask{#${block.id}}`));
|
|
17307
|
+
const states = openers.map((line) => /\bstate="(\w+)"/.exec(line)?.[1]);
|
|
17302
17308
|
const named = ask === undefined ? `block ${block.id}` : `${JSON.stringify(ask.question)} (block ${block.id}, ask ${ask.id})`;
|
|
17303
|
-
const states = lines.filter((line) => line.includes(`ask{#${block.id} `) || line.includes(`ask{#${block.id}}`)).map((line) => /\bstate="(\w+)"/.exec(line)?.[1]);
|
|
17304
17309
|
if (states.length === 0)
|
|
17305
17310
|
return [`${named}, which version ${latest} does not hold yet`];
|
|
17306
17311
|
if (!states.includes("open") && states.some((state) => state !== undefined))
|
|
17307
17312
|
return [];
|
|
17308
|
-
if (ask
|
|
17313
|
+
if (ask !== undefined)
|
|
17314
|
+
return ask.state === "open" ? [named] : [stillOpen(named, ask.state)];
|
|
17315
|
+
const attribute = (name) => openers.map((line) => new RegExp(`\\b${name}="([^"]+)"`).exec(line)?.[1]).find((value) => value !== undefined);
|
|
17316
|
+
const sourceId = attribute("copied_from");
|
|
17317
|
+
const document = attribute("copied_from_document");
|
|
17318
|
+
if (sourceId === undefined || document === undefined) {
|
|
17309
17319
|
return [`${named}, whose ask Dispatch has not opened yet`];
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
const
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
];
|
|
17320
|
+
}
|
|
17321
|
+
const source = parseDispatchRef(document);
|
|
17322
|
+
const where = source === null ? document : source.owner.kind === "project" ? documentLabel(source.owner.project, source.id) : `${source.owner.issue} ${source.id}`;
|
|
17323
|
+
const ref = source?.owner.kind === "issue" ? dispatchChildRef(dispatchIssueRef(source.owner.issue), "ask", sourceId) : dispatchChildRef(document, "ask", sourceId);
|
|
17324
|
+
return [`${named}, a copy of ask ${ref}, which is open on ${where}: answer it there`];
|
|
17316
17325
|
});
|
|
17317
17326
|
if (open.length === 0)
|
|
17318
17327
|
return;
|
package/package.json
CHANGED
|
@@ -148,6 +148,20 @@ artifact by the slug the upload printed or by its filename, and a project docume
|
|
|
148
148
|
slug also arrives on `artifact.created` events. Dispatch suffixes a slug two documents would share, so one document's
|
|
149
149
|
filename can be another's slug (`plan v2` takes `plan-v2`, then a document named `plan-v2` takes `plan-v2-2`): a bare
|
|
150
150
|
`--artifact` that names both is refused with each one's id, while a `dispatch://` reference's document part is always the slug.
|
|
151
|
+
A document you upload holding a copy of another document's `:::ask` block, under the same `#id` and with the same
|
|
152
|
+
question, options, `multiple` and urgency, on the same issue (or the same project for a project document), opens no
|
|
153
|
+
ask: the copy shows the state and answer of the ask it was copied from, names it and its document in `copied_from` and
|
|
154
|
+
`copied_from_document`, and cannot be answered there. Answer the original: its answer or resolution shows on the copy a
|
|
155
|
+
few seconds later. A copy may hold the original's earlier wording, from before someone reworded it. It stays a copy
|
|
156
|
+
while the original is open, and shows an answer or resolution given while the original still asked that wording; an
|
|
157
|
+
answer or resolution given after the rewording decides a question the copy does not ask, so the copy opens its own ask
|
|
158
|
+
instead. A copy whose text you change opens a new ask. Once the original's block leaves its document, its copies share
|
|
159
|
+
one new ask: the first copy to settle opens it and the others name it.
|
|
160
|
+
|
|
161
|
+
An answered or resolved ask's question is fixed. Editing its block's question, options, `multiple` or urgency in the
|
|
162
|
+
document, or uploading a version that changes them, does not change the ask: a few seconds later the block is written
|
|
163
|
+
back to the question the answer was given on, beside its answer. To ask something new, write a new ask block with its
|
|
164
|
+
own `#id`. An open ask's block is its text: edit it and the ask changes with it.
|
|
151
165
|
|
|
152
166
|
A picture meant to be seen inline in a message, comment or ask is not uploaded with `dispatch artifact`: pass its local
|
|
153
167
|
path with that command's `--image`, once per picture (PNG, JPEG, GIF or WebP by its bytes, at most 25 MiB each). Each is uploaded to the issue (a
|
|
@@ -193,12 +193,17 @@ unsent:
|
|
|
193
193
|
`controller_wake_interval_seconds` (an hour by default), whatever the slots. An earlier walk
|
|
194
194
|
that found nothing, a day with no event, and [a tree waiting on a root
|
|
195
195
|
claim](#trees-waiting-on-a-root-claim-go-daemon) all get a turn from it. Its payload's
|
|
196
|
-
`openCapabilities`, when present, names the
|
|
197
|
-
fallback, every role's CPU and memory limits) the deployment's own
|
|
198
|
-
no decision recorded
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
196
|
+
`openCapabilities`, when present, names the open capability rows: the deployment capabilities
|
|
197
|
+
(the secrets broker, model fallback, every role's CPU and memory limits) the deployment's own
|
|
198
|
+
configuration leaves open with no decision recorded, and the live capabilities (`subagents`,
|
|
199
|
+
`web-search`, `mcp`, `repository-extensions`, `dispatch-envoy-tools`, `github`) a running
|
|
200
|
+
session's own check at its start failed. `read_state` lists every row under `capabilities`, each
|
|
201
|
+
open one with its `detail`: on a deployment row, with the `configLine` to write into
|
|
202
|
+
`legion.yaml` (`capabilities.decided.<name>: "<reason>"`); on a live row, naming each failing
|
|
203
|
+
session (its pod or pane, its role, its issue) and the fact its check found, with no
|
|
204
|
+
`configLine`, since nothing in `legion.yaml` decides a live row. Name them in the day's report.
|
|
205
|
+
A gap is the operator's to close or to decide — a live one the operator's or the issue's
|
|
206
|
+
architect's to fix in the session's environment — never yours, and it never stops the walk.
|
|
202
207
|
|
|
203
208
|
**Scope first.** The scope the deployment instructions state decides which issues are candidates
|
|
204
209
|
at all, before anything below. When they say you hand Legion no issue yourself, or that Legion
|
|
@@ -350,9 +355,12 @@ every pull request by its URL.
|
|
|
350
355
|
when it made one: what it took, and how many candidates each row of the table skipped, so a
|
|
351
356
|
reader can see why a free slot stays empty.
|
|
352
357
|
- **Open capabilities.** Each row of `capabilities` in `read_state` whose `status` is
|
|
353
|
-
`open` (the `tick` payload's `openCapabilities`): its name, its `detail`, and
|
|
354
|
-
the `legion.yaml` line that records a decision.
|
|
355
|
-
|
|
358
|
+
`open` (the `tick` payload's `openCapabilities`): its name, its `detail`, and, on a deployment
|
|
359
|
+
row, its `configLine`, the `legion.yaml` line that records a decision. A live row a running
|
|
360
|
+
session failed has no `configLine`: its `detail` names the session (pod or pane), the role, the
|
|
361
|
+
issue and the fact, and nothing in `legion.yaml` decides it. The operator closes or decides a
|
|
362
|
+
deployment row, and the operator or the issue's architect fixes a live row's session
|
|
363
|
+
environment; you only report either, and a report with none says nothing of them.
|
|
356
364
|
|
|
357
365
|
A day with nothing finished says so in one sentence. When the lists do not fit, keep the counts
|
|
358
366
|
and the highest-priority issues.
|
|
@@ -394,7 +402,7 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
|
|
|
394
402
|
| New issue created in the Dispatch project, status `triage`: `triage on <KEY>` (payload `{kind: "triage"}`) on the controller topic, for an unrecorded root carrying the `legion` label only ("Issues handed to Legion" above); the start procedure above catches one sent while no controller ran | issue key + triage context (incl. pre-existing children) | Triage: `set_status` to `todo` to admit, or to `backlog`/`icebox` to park |
|
|
395
403
|
| `slot-free on <KEY>` from the Go daemon (payload `{kind: "slot-free"}`) | the root whose slot the daemon released with no waiting root to take it | Verify a free slot in `read_state`, then fill it ([Keeping the slots full](#keeping-the-slots-full-go-daemon)) |
|
|
396
404
|
| `todo on <KEY>` from the Go daemon (payload `{kind: "todo"}`) | an issue not handed to Legion that changed while in `todo` and a slot stood free, sent half a minute later | Verify a free slot, then walk the whole `todo` list ([Keeping the slots full](#keeping-the-slots-full-go-daemon)) |
|
|
397
|
-
| `tick on <PROJECT>` from the Go daemon (payload `{kind: "tick", openCapabilities?: [<name>, …]}`) | the project key; the daemon's periodic wake, whatever the slots; `openCapabilities` names the deployment capabilities with no decision, each with its detail and `capabilities.decided.<name>` line under `capabilities` in `read_state` | Recheck the trees waiting on a claim, then walk if a slot is free — a gap never stops the walk; post the day's report if this is the day's first turn, naming each open capability in it (the operator closes or decides
|
|
405
|
+
| `tick on <PROJECT>` from the Go daemon (payload `{kind: "tick", openCapabilities?: [<name>, …]}`) | the project key; the daemon's periodic wake, whatever the slots; `openCapabilities` names the deployment capabilities with no decision, each with its detail and `capabilities.decided.<name>` line under `capabilities` in `read_state`, and the live capabilities a running session's check failed, each with the detail naming the session (pod or pane), role, issue and fact, and no `configLine` | Recheck the trees waiting on a claim, then walk if a slot is free — a gap never stops the walk; post the day's report if this is the day's first turn, naming each open capability in it (the operator closes or decides a deployment row; the operator or the issue's architect fixes a live row's session; you take no other action) |
|
|
398
406
|
| Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; the owning architect writes an issue-design decision as a decision block and opens `dispatch ask` only for a human to-do |
|
|
399
407
|
| Mention | Slack/GitHub PR @mention text | Answer, or route to the owning issue's architect role |
|
|
400
408
|
| `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 `read_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 `set_status` of the root to `backlog`; setting the root back to `todo` later re-admits it as a new generation, which starts again from its architect |
|