diff --git a/agents/darkwing/work/queue-55/BUILD.md b/agents/darkwing/work/queue-55/BUILD.md new file mode 100644 index 000000000..6e44225ab --- /dev/null +++ b/agents/darkwing/work/queue-55/BUILD.md @@ -0,0 +1,203 @@ +# Row 55 (#1544): the full-screen terminal app, candidate packet, round 1 + +Author: Darkwing. Reviewers: Dewey and Filbert. Brief: +`docs/plans/2026-10-10_design-implementation.md`, section "Full-screen +terminal app", with `docs/design/IMPLEMENTING.md` "The full-screen +terminal app" and "Strings to keep exactly". Base: `fe5c68fa` (row 41, +`base.txt`). It applies unchanged to `23d566d6`: the commits between the +two touch only BUILD-LOG.md, docs/SESSIONS.md and the queue files, none of +them in this candidate. The feature is uncommitted; only this packet is +committed. I pushed nothing. + +## Files + +`files.txt` lists the nine repository paths. `candidate-manifest.sha256` +has their sha256. `build.patch` is `git diff --cached --binary fe5c68fa` +over them: 9 files, +1363/−16. + +| File | Change | +|---|---| +| `packages/cli/src/app.mjs` | new: key parsing, the `App` state machine, `runApp` (alternate screen, raw mode, resize, SIGTERM, restore) and the quit line | +| `packages/cli/src/screens.mjs` | new: `draw(state)` is pure and returns rows; Inbox, Tasks, Agents, Trail, Help, overlays, the 16-colour palette and NO_COLOR | +| `packages/cli/src/cli.mjs` | the decide flow moves into the exported `resolveDecision`, which `decide` and the app share; `SEAT_USAGE`; the no-argument entry `app()` | +| `packages/cli/src/format.mjs` | `trailSummary` is exported (the Trail screen uses it); no other change | +| `packages/cli/tests/app.test.mjs` | new: 14 tests, listed below | +| `packages/cli/README.md` | the intro names row 55; new section "The app" | +| `docs/TOOLS.md` | the `mosaic` entry: the no-argument line and a four-line paragraph | +| `scripts/mosaic` | a decided scope addition (Sage, 2026-10-11); see below | +| `packages/queue/tests/dispatch.test.mjs` | follows from `scripts/mosaic`; my addition, for Sage to confirm; see below | + +`resolveDecision` writes the same lines in the same order and asks the +same question as before. `decide` passes `ask = null` for `--yes` and +keeps its own "stdin is not a terminal" refusal. The 83 tests that were in +`packages/cli/tests/` at the base still pass unchanged. + +### Scope addition: scripts/mosaic + +`scripts/mosaic` is not in the brief's files. Sage approved it on +2026-10-11 as a decided scope addition. Before, `scripts/mosaic` with no +argument fell through to `packages/seat/src/cli.mjs`, which printed +`refused: ` and exited 4. The script never reached +`packages/cli`, so the brief's acceptance line, bare `mosaic` on a TTY +opens the app, could not be met inside `packages/cli` alone. The change is +one case that sends no argument to `packages/cli/src/cli.mjs`. Nothing +else in the script changed. + +The entry checks in Sage's order: + +1. Agent markers exit 3 through `refuseInsideAgent`, with the transport's + message unchanged. +2. stdin or stdout not a TTY exits 4 with the usage. +3. Otherwise the app opens. + +Test 11 below covers each branch through `main([])`. Test 12 spawns +`scripts/mosaic` itself for exits 4 and 3. + +### The queue dispatch test + +My first gate run had test-queue red, 26 passed and 1 failed +(`out/round1-first-gate/test-queue.log`). `packages/queue/tests/dispatch.test.mjs` +pins A2's rule that every call but `queue` reaches the seat CLI exactly as +the pre-A2 script did, and its list of calls included `[]`. Sage's approved +change sends exactly that call somewhere else, so the pin can't hold as +written. This file is outside the brief and outside what Sage approved. I +changed it because the gate needs every suite green and the change follows +directly from the approved one. Sage, please confirm or rule it out. + +The change, 16 lines in and 5 out: + +- `[]` leaves the list of seat calls. `[""]` stays: it is one argument, so + it still goes to the seat CLI. +- The fake root gains a fake `packages/cli/src/cli.mjs`. +- A new test: no argument reaches `packages/cli/src/cli.mjs` with empty + argv, the same working directory and stdin, and the fake's exit code + and output passed through. The same call on the pre-A2 fixture still + reaches the seat CLI. +- The header comment names row 55. + +With `scripts/mosaic` put back to the base, the new test fails and the +other three pass. With the candidate, all four pass. The test that A2 only +adds lines to the pre-A2 script still passes, because the row 55 case adds +four lines and removes none. + +## How the brief maps to the candidate + +| Brief | Where | Test | +|---|---|---| +| no argument on a TTY opens the app | `cli.mjs` `app()`, `app.mjs` `runApp` | 11, 12; `out/smoke.txt` | +| not a TTY: usage, exit 4 | `cli.mjs` `app()` | 11, 12 | +| agent run: exit 3 | `refuseInsideAgent` in `transport.mjs` | 11, 12 | +| Inbox, Tasks, Agents, Trail, Help; keys c i t a r j k : ? q | `app.mjs` `#key`, `screens.mjs` | 1, 9 | +| decide runs the existing flow, strings unchanged | `resolveDecision` | 4, 5, 6, 7, 13 | +| 16 colours, NO_COLOR, 80x24, designed at 120x36 | `screens.mjs` | 1, 3 | +| no new dependency | `package.json` untouched | | +| every screen fits 120 columns and 80x24 | | 1 | +| NO_COLOR output has no escape codes | | 3 (see deviation 1) | +| the string grep test | | 13 | + +The tests in `packages/cli/tests/app.test.mjs`: + +1. Every screen and overlay fits 120x36, 80x24 and the narrow layout + (100x30, 99x24), row for row, in colour and NO_COLOR. That covers every + view and overlay, plus the empty, stale, refused and no-business states. +2. Broker text is shown, never interpreted. No raw control character or + bidi override reaches the terminal (`visible()` from + `packages/conversation/src/terminal.mjs`). +3. NO_COLOR: no escape codes except reverse video (7/27) on the selected + row. A frame with nothing selected has none. +4. 1-9 shows the decide flow's own lines and question. Only y resolves, + and it goes through the test broker. +5. The app's decide text equals `mosaic decide` stdout for the same + decision. +6. n, N, Esc, Enter and q at the question cancel. Other keys wait. + Nothing is sent. +7. Outcome unknown shows the CLI's sentence and offers no retry. Then v + opens the trail, or r reads again. +8. Refused and failed reads keep the last list, marked stale. A refused + decide says refused. +9. Keys: views, moving, the trail of a selection, the command line, help + and quit. +10. No business: the app says so, and `:business ` picks one. +11. `main([])`: exit 3 with a marker set, exit 4 with the usage off a + terminal, and the app on one (stubbed `runApp`). +12. `scripts/mosaic` with no argument reaches the app entry (exit 4 off a + terminal, 3 with a marker). `SEAT_USAGE` still equals what the seat + CLI prints, so the copy can't drift. +13. Strings to keep exactly appear once, in `cli.mjs` and `transport.mjs`. + `app.mjs` and `screens.mjs` don't restate "authorizes the action", + "declines the action", "[y/N]", "may have recorded it" or "runs only + from a human shell". +14. `draw` is pure: the same state gives the same frame. + +## Deviations, stated + +1. **NO_COLOR keeps reverse video on the selected row.** The brief's test + line says NO_COLOR output has no escape codes. `IMPLEMENTING.md` line + 188 says reverse video stays for the selected row, and + `docs/design/README.md` line 148 makes reverse video the selection + mark. I followed the design. Test 3 checks that 7 and 27 are the only + SGR codes, and that a frame with no selection has none. The writer's + `\x1b[H\x1b[2J` before each frame and the alternate screen codes are + cursor and screen control, not colour. +2. **The non-TTY message gains one line.** It prints + `mosaic: the app needs a terminal on stdin and stdout; from a script, + use a verb:` and then the seat usage and the `packages/cli` usage, exit + 4. Before, it printed `refused: `. `IMPLEMENTING.md` line + 191 says to refuse and point at the verbs, and the old text named only + the seat verbs. If a reviewer wants the old bytes, test 12 already + holds them as `SEAT_USAGE`. +3. **The Terminal class is not the base.** The brief says the app is built + on the conversation `Terminal` class. Only `visible()` is imported. + `Terminal` is a line editor for Talk, and Talk is out of scope here. The + app needs a whole-screen redraw and single-key input, which `Terminal` + doesn't do. When Talk joins, the Talk screen embeds `Terminal`. +4. **Reads block redraw.** `humanTransport` uses `spawnSync`, so the + screen doesn't redraw during a read (30 s timeout). That is the existing + transport, unchanged. The README says so. + +## Evidence + +All runs in a detached worktree of `23d566d6` with `build.patch` applied +and `node_modules` linked (`out/gate/`). + +- `packages/cli`: 97/97 (83 before, plus the 14 new), `out/gate/cli-tests.log`. +- Every `scripts/test-*.sh`, run in sequence on the final candidate, all + exit 0 (`out/gate/summary.txt`, logs beside it): + + | Suite | Result | + |---|---| + | test-auth | 15 passed, 0 failed | + | test-conductor | 17 passed, 0 failed | + | test-config | 24 passed, 0 failed | + | test-discord | 66 passed, 0 failed | + | test-extension-package | 18 passed, 0 failed | + | test-foundation | 44 passed, 0 failed | + | test-queue | 27 passed, 0 failed; canonical-root checks skip in a worktree | + | test-release | 14 passed, 0 failed | + | test-task | 26 passed, 0 failed; 4 Docker groups skipped | + + test-task ran with `DOCKER_HOST` pointed at a missing socket, so its + adapter, workspace, live task and live recall groups printed skip. The + live ones call a real model. Nothing this candidate changes runs in + those groups. Sage's gate rerun covers them with Docker. +- The first gate run, before the dispatch test change, is kept in + `out/round1-first-gate/`. `out/dispatch-mutant.txt` has the dispatch + test against the base script and the candidate script. +- Smoke, `smoke.sh`: the app in a pty at 80x24 against a fixture config, + never the real bus. Keys ? Esc t q. The alternate screen was entered once + and left once, then it printed `(app closed with q; 0 decisions resolved + this session)` and exit 0 (`out/smoke.txt`). +- `out/apply.txt`: `build.patch` applies to `23d566d6` in a fresh + worktree and the manifest matches. + +## Follow-ups (not in this row) + +- The transport's "did not finish" `CliError` (`transport.mjs` line 60) + has no `.code`, so the outcome-unknown branch in `resolveDecision` does + not see it. `mosaic decide` then prints + `outcome-unknown: the bus transport did not finish`, exit 1, instead of + the "may have recorded it" sentence. The app matches the message text + and shows its outcome-unknown overlay, so only the verb has the gap. It + predates this row. +- Talk and stop in the app, Decision Seen, and the web console stay out of + scope, as the brief says. diff --git a/agents/darkwing/work/queue-55/base.txt b/agents/darkwing/work/queue-55/base.txt new file mode 100644 index 000000000..9dfa3f0e9 --- /dev/null +++ b/agents/darkwing/work/queue-55/base.txt @@ -0,0 +1 @@ +fe5c68fa031923ff79cbd8aeebb4c8925eb5af23 diff --git a/agents/darkwing/work/queue-55/build.patch b/agents/darkwing/work/queue-55/build.patch new file mode 100644 index 000000000..b78d8a634 --- /dev/null +++ b/agents/darkwing/work/queue-55/build.patch @@ -0,0 +1,1541 @@ +diff --git a/docs/TOOLS.md b/docs/TOOLS.md +index b96e9012..5a675611 100644 +--- a/docs/TOOLS.md ++++ b/docs/TOOLS.md +@@ -193,6 +193,7 @@ config problem · `4` usage or a required file missing. Details: + ## Inbox, decisions and the bus host (`scripts/mosaic`) + + ```bash ++scripts/mosaic # no argument: the full-screen app + scripts/mosaic inbox | tasks | agents [--business ] [--json] + scripts/mosaic decide