@katyella/legio 0.2.2 → 0.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/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.3] - 2026-03-02
11
+
12
+ ### Fixed
13
+ - Coordinator and sling beacons now explain what legio is — previously Claude Code on fresh machines rejected thin beacons as "unrecognized" foreign content (same fix as gateway in v0.2.1)
14
+ - Coordinator beacon startup instructions use `legio status` instead of `bd ready` / `legio group status` (doesn't assume beads is installed)
15
+ - `legio doctor` no longer flags persistent agent identity files (coordinator, gateway, monitor) as stale — they legitimately exist outside the agent manifest
16
+ - Gateway greeting mail ("Gateway is online and ready") is only sent after confirmed beacon delivery — previously it was sent even when the beacon was rejected
17
+ - 2399 tests across 79 test files (up from 2397 across 79)
18
+
10
19
  ## [0.2.2] - 2026-03-02
11
20
 
12
21
  ### Fixed
@@ -201,7 +210,8 @@ Initial public release on npm as [`@katyella/legio`](https://www.npmjs.com/packa
201
210
  - E2E lifecycle tests via Playwright
202
211
  - Vitest test runner with forks pool for CI compatibility
203
212
 
204
- [Unreleased]: https://github.com/katyella/legio/compare/v0.2.2...HEAD
213
+ [Unreleased]: https://github.com/katyella/legio/compare/v0.2.3...HEAD
214
+ [0.2.3]: https://github.com/katyella/legio/compare/v0.2.2...v0.2.3
205
215
  [0.2.2]: https://github.com/katyella/legio/compare/v0.2.1...v0.2.2
206
216
  [0.2.1]: https://github.com/katyella/legio/compare/v0.2.0...v0.2.1
207
217
  [0.2.0]: https://github.com/katyella/legio/compare/v0.1.3...v0.2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@katyella/legio",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Multi-agent orchestration for Claude Code — spawn worker agents in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution",
5
5
  "author": "Matthew Wojtowicz",
6
6
  "license": "MIT",
@@ -518,8 +518,26 @@ describe("startGateway", () => {
518
518
  expect(enterCalls).toHaveLength(2); // follow-up Enter + retry Enter
519
519
  });
520
520
 
521
- test("sends greeting mail to human after beacon delivery", async () => {
522
- const { deps } = makeDeps();
521
+ test("sends greeting mail to human after confirmed beacon delivery", async () => {
522
+ const { tmux } = makeFakeTmux();
523
+ // Simulate hooks transitioning session out of "booting" after a few sleep calls
524
+ let sleepCount = 0;
525
+ const deps: GatewayDeps = {
526
+ _tmux: tmux,
527
+ _sleep: async () => {
528
+ sleepCount++;
529
+ // After 3 sleeps (waitForTuiReady + follow-up Enter delay + first poll),
530
+ // transition session to "working" like hooks would
531
+ if (sleepCount === 3) {
532
+ const { store } = openSessionStore(legioDir);
533
+ try {
534
+ store.updateState("gateway", "working");
535
+ } finally {
536
+ store.close();
537
+ }
538
+ }
539
+ },
540
+ };
523
541
  await captureStdout(() => gatewayCommand(["start", "--no-attach"], deps));
524
542
  // Verify mail.db has the greeting
525
543
  const { createMailStore } = await import("../mail/store.ts");
@@ -535,6 +553,20 @@ describe("startGateway", () => {
535
553
  mailDb.close();
536
554
  }
537
555
  });
556
+
557
+ test("does not send greeting mail when beacon delivery fails", async () => {
558
+ const { deps } = makeDeps();
559
+ await captureStdout(() => gatewayCommand(["start", "--no-attach"], deps));
560
+ // verifyBeaconDelivery returns false (session stays in "booting"), so no greeting mail
561
+ const { createMailStore } = await import("../mail/store.ts");
562
+ const mailDb = createMailStore(join(legioDir, "mail.db"));
563
+ try {
564
+ const msgs = mailDb.getAll({ from: "gateway", to: "human" });
565
+ expect(msgs).toHaveLength(0);
566
+ } finally {
567
+ mailDb.close();
568
+ }
569
+ });
538
570
  });
539
571
 
540
572
  describe("stopGateway", () => {
@@ -118,7 +118,7 @@ async function verifyBeaconDelivery(
118
118
  tmuxSession: string,
119
119
  beacon: string,
120
120
  sleep: (ms: number) => Promise<void>,
121
- ): Promise<void> {
121
+ ): Promise<boolean> {
122
122
  const MAX_CHECKS = 10;
123
123
  const INTERVAL_MS = 2_000;
124
124
 
@@ -127,7 +127,7 @@ async function verifyBeaconDelivery(
127
127
  const session = store.getByName(GATEWAY_NAME);
128
128
  if (session && session.state !== "booting") {
129
129
  // Beacon confirmed — gateway transitioned out of booting
130
- return;
130
+ return true;
131
131
  }
132
132
  }
133
133
 
@@ -142,7 +142,9 @@ async function verifyBeaconDelivery(
142
142
  const finalSession = store.getByName(GATEWAY_NAME);
143
143
  if (!finalSession || finalSession.state === "booting") {
144
144
  process.stderr.write("[legio] Warning: gateway beacon delivery could not be confirmed\n");
145
+ return false;
145
146
  }
147
+ return true;
146
148
  }
147
149
 
148
150
  /**
@@ -305,25 +307,27 @@ async function startGateway(args: string[], deps: GatewayDeps = {}): Promise<voi
305
307
 
306
308
  // Verify delivery: poll store until state transitions out of booting.
307
309
  // Must complete before the finally block closes the store.
308
- await verifyBeaconDelivery(store, tmux, tmuxSession, beacon, sleep);
309
-
310
- // Send greeting mail to human
311
- const { createMailStore } = await import("../mail/store.ts");
312
- const mailDb = createMailStore(join(legioDir, "mail.db"));
313
- try {
314
- mailDb.insert({
315
- id: "",
316
- from: GATEWAY_NAME,
317
- to: "human",
318
- subject: "Gateway online",
319
- body: "Gateway is online and ready. Send a message to start chatting.",
320
- type: "status",
321
- priority: "normal",
322
- threadId: null,
323
- audience: "human",
324
- });
325
- } finally {
326
- mailDb.close();
310
+ const confirmed = await verifyBeaconDelivery(store, tmux, tmuxSession, beacon, sleep);
311
+
312
+ // Send greeting mail to human only after confirmed beacon delivery
313
+ if (confirmed) {
314
+ const { createMailStore } = await import("../mail/store.ts");
315
+ const mailDb = createMailStore(join(legioDir, "mail.db"));
316
+ try {
317
+ mailDb.insert({
318
+ id: "",
319
+ from: GATEWAY_NAME,
320
+ to: "human",
321
+ subject: "Gateway online",
322
+ body: "Gateway is online and ready. Send a message to start chatting.",
323
+ type: "status",
324
+ priority: "normal",
325
+ threadId: null,
326
+ audience: "human",
327
+ });
328
+ } finally {
329
+ mailDb.close();
330
+ }
327
331
  }
328
332
 
329
333
  if (shouldAttach) {
package/src/index.ts CHANGED
@@ -45,7 +45,7 @@ import { worktreeCommand } from "./commands/worktree.ts";
45
45
  import { LegioError, WorktreeError } from "./errors.ts";
46
46
  import { setQuiet } from "./logging/color.ts";
47
47
 
48
- const VERSION = "0.2.2";
48
+ const VERSION = "0.2.3";
49
49
 
50
50
  const HELP = `legio v${VERSION} — Multi-agent orchestration for Claude Code
51
51