@sjawhar/opencode-legion-envoy 1.20.1 → 1.21.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
CHANGED
|
@@ -13649,7 +13649,7 @@ var MessageEventPayloadSchema = object({
|
|
|
13649
13649
|
});
|
|
13650
13650
|
var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
|
|
13651
13651
|
id: string2(),
|
|
13652
|
-
issue_key: string2(),
|
|
13652
|
+
issue_key: string2().nullable(),
|
|
13653
13653
|
author: object({ kind: string2(), id: string2() }),
|
|
13654
13654
|
body: string2(),
|
|
13655
13655
|
target: string2(),
|
|
@@ -13662,6 +13662,7 @@ var MessageDeliveryEventPayloadSchema = object({
|
|
|
13662
13662
|
attempt: number2().int().positive().optional(),
|
|
13663
13663
|
delivery: _enum2(["btw", "aside", "steer"]).optional(),
|
|
13664
13664
|
session_id: string2().optional(),
|
|
13665
|
+
target: string2().optional(),
|
|
13665
13666
|
title: string2().optional(),
|
|
13666
13667
|
state: _enum2(["sent", "failed"]).optional(),
|
|
13667
13668
|
error: string2().optional()
|
|
@@ -15422,12 +15423,23 @@ async function executeDispatchTool(input) {
|
|
|
15422
15423
|
if (!input.config.enabled || !configUrl || !configToken) {
|
|
15423
15424
|
throw new Error("Dispatch is disabled; resolve both DISPATCH_URL and DISPATCH_TOKEN");
|
|
15424
15425
|
}
|
|
15426
|
+
const baseFetch = input.fetchImpl ?? fetch;
|
|
15427
|
+
const fetchImpl = Object.assign(async (...args) => {
|
|
15428
|
+
try {
|
|
15429
|
+
return await baseFetch(...args);
|
|
15430
|
+
} catch (error) {
|
|
15431
|
+
if (error instanceof TypeError) {
|
|
15432
|
+
throw new Error(`Dispatch at ${configUrl} is unreachable: ${error.message}. If the Dispatch URL changed, restart this agent process so it picks up the new configuration.`);
|
|
15433
|
+
}
|
|
15434
|
+
throw error;
|
|
15435
|
+
}
|
|
15436
|
+
}, { preconnect: baseFetch.preconnect });
|
|
15425
15437
|
if (input.tool === "dispatch_open_asks") {
|
|
15426
15438
|
const sessionId = input.sessionId?.trim();
|
|
15427
15439
|
if (!sessionId)
|
|
15428
15440
|
throw new Error("host session id is required for dispatch_open_asks");
|
|
15429
15441
|
toolSchema(input.tool).parse(input.args);
|
|
15430
|
-
const client = new DispatchClient(configUrl, configToken,
|
|
15442
|
+
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
15431
15443
|
const response = await client.openAsks(sessionId);
|
|
15432
15444
|
return { text: formatOpenAsksSummary(response, configUrl), details: { ...response } };
|
|
15433
15445
|
}
|
|
@@ -15436,7 +15448,7 @@ async function executeDispatchTool(input) {
|
|
|
15436
15448
|
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
|
|
15437
15449
|
const args = toolSchema(input.tool).parse(ownerArguments.args);
|
|
15438
15450
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
15439
|
-
const client = new DispatchClient(configUrl, configToken,
|
|
15451
|
+
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
15440
15452
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
15441
15453
|
kind: "issue",
|
|
15442
15454
|
issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
|
|
@@ -15623,17 +15635,18 @@ async function executeDispatchTool(input) {
|
|
|
15623
15635
|
}
|
|
15624
15636
|
case "dispatch_message": {
|
|
15625
15637
|
const inReplyTo = messageInReplyTo(args);
|
|
15626
|
-
const
|
|
15638
|
+
const issueKey = issue();
|
|
15639
|
+
const message = await client.message(issueKey, {
|
|
15627
15640
|
body: stringArg(args, "body"),
|
|
15628
15641
|
...inReplyTo === undefined ? {} : { in_reply_to: inReplyTo },
|
|
15629
15642
|
actor
|
|
15630
15643
|
});
|
|
15631
|
-
const messageRef = `dispatch://${
|
|
15644
|
+
const messageRef = `dispatch://${issueKey}/message/${message.id}`;
|
|
15632
15645
|
return {
|
|
15633
15646
|
text: `Posted message ${message.id} (${messageRef})`,
|
|
15634
15647
|
details: {
|
|
15635
|
-
issue:
|
|
15636
|
-
topic: dispatchIssueSubject(
|
|
15648
|
+
issue: issueKey,
|
|
15649
|
+
topic: dispatchIssueSubject(issueKey, ">"),
|
|
15637
15650
|
message: message.id
|
|
15638
15651
|
}
|
|
15639
15652
|
};
|
package/package.json
CHANGED
|
@@ -45,7 +45,7 @@ before step 3. The design gate is not a substitute for review and retro.
|
|
|
45
45
|
|
|
46
46
|
1. Re-read the issue, its acceptance criteria, the PR, test evidence, and review evidence.
|
|
47
47
|
Confirm the PR carries both proofs: the implementer's own `E2E (implementer)` line and the tester's `E2E (tester)` line,
|
|
48
|
-
each naming a production-like surface (
|
|
48
|
+
each naming a production-like surface (the daemon's test harness (`packages/daemon/src/daemon/__tests__/`) and real-process fixtures; a live check at the operator's next daemon restart, recorded on the PR; a sandbox repository, a devN
|
|
49
49
|
stack, staging, or a local stack with real migrations), a command or run id, an observation, a head
|
|
50
50
|
SHA, and a negative control. If either is missing, or links only a unit suite, the retro's first
|
|
51
51
|
durable learning is that gap and the issue goes back — to the implementer for its own proof, to the
|
|
@@ -251,7 +251,7 @@ PR opens, and every later phase keeps it current rather than replacing it:
|
|
|
251
251
|
```
|
|
252
252
|
## Verification
|
|
253
253
|
|
|
254
|
-
**CI:** `Tests` run <run-id> — jobs lint,
|
|
254
|
+
**CI:** `Tests` run <run-id> — jobs lint, typecheck, test all success at <head-sha>; `PR Title` run <run-id> — job pr-title success at <head-sha>.
|
|
255
255
|
|
|
256
256
|
**Threads:** <n> resolved, 0 unresolved. Each disposed individually, never in bulk:
|
|
257
257
|
- Thread <id>: fixed in <commit-sha> — <one line>.
|
|
@@ -276,6 +276,7 @@ Verified the implementer's proof by <re-running its command | driving the same s
|
|
|
276
276
|
**Fast-follow:** <one named cleanup item and where it will land>, or "none".
|
|
277
277
|
|
|
278
278
|
**Chain:** stacked on <base bookmark> frozen at <sha> / not stacked.
|
|
279
|
+
**Retarget:** Retargeting a pull request to a new base does not re-run Tests; after a retarget, rebase onto the new base and push — the new head runs Tests against the new merge result — and cite that run in the PR body.
|
|
279
280
|
```
|
|
280
281
|
|
|
281
282
|
- **Threads are dispositioned individually, never resolved in bulk.** Every open review
|
|
@@ -301,11 +302,11 @@ Verified the implementer's proof by <re-running its command | driving the same s
|
|
|
301
302
|
changes behavior, hides an error, or breaks a gate is fixed here — never deferred.
|
|
302
303
|
Findings about naming, duplication, or wording are batched into the single `Fast-follow`
|
|
303
304
|
line instead of iterating per push.
|
|
304
|
-
- **Rebase only on a real conflict.** Sami, 2026-09-11, verbatim:
|
|
305
|
+
- **Rebase only on a real conflict, except after a base retarget.** Sami, 2026-09-11, verbatim:
|
|
305
306
|
"Please don't do unnecessary rebases (i.e. unless there are merge conflicts). The CI queue is too long and slow."
|
|
306
|
-
The implementer rebases the issue branch only when GitHub reports it `CONFLICTING
|
|
307
|
-
|
|
308
|
-
failed CI job is re-run on its own with `legion gh -- run rerun <run-id> --failed`, never by
|
|
307
|
+
The implementer rebases the issue branch only when GitHub reports it `CONFLICTING`, the controller asks
|
|
308
|
+
because of a conflict, or after the pull request is retargeted to a new base. Otherwise, never rebase to
|
|
309
|
+
pick up `main` or refresh CI. A single failed CI job is re-run on its own with `legion gh -- run rerun <run-id> --failed`, never by
|
|
309
310
|
pushing a new commit. A conflict-forced rebase that leaves the branch's diff unchanged is a
|
|
310
311
|
confirmation, not a new round (see *The unchanged-diff check* below). Before rebasing, record
|
|
311
312
|
the fingerprint at the current tip; after pushing the rebased branch, record it at the new
|
|
@@ -317,9 +318,10 @@ Verified the implementer's proof by <re-running its command | driving the same s
|
|
|
317
318
|
field names naming, duplication, or wording cleanup only; anything that changes behaviour,
|
|
318
319
|
hides an error, or breaks a gate lands in this PR.
|
|
319
320
|
- **The implementer proves the change before its phase completes, and writes the `E2E (implementer)` line when the pull request opens.**
|
|
320
|
-
The proof is the changed behaviour exercised on the surface a user reaches it through —
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
The proof is the changed behaviour exercised on the surface a user reaches it through — the daemon's
|
|
322
|
+
test harness (`packages/daemon/src/daemon/__tests__/`) and real-process fixtures; a live check at the
|
|
323
|
+
operator's next daemon restart, recorded on the PR; a sandbox repository, a real browser, a devN stack,
|
|
324
|
+
or a local stack with real migrations — with the exact command or run id, what was observed, the head SHA,
|
|
323
325
|
one negative control. The same proof goes into `.legion/implement.json` as its required `proof`
|
|
324
326
|
array (`legion handoff write --phase implement` refuses a payload without one and names the
|
|
325
327
|
field), and into the PR body, because the reviewer and the merger verify facts on GitHub and
|