@zhuxixi/pi-agent-board 0.5.0 → 0.5.2

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.
Files changed (31) hide show
  1. package/PROGRESS.md +18 -3
  2. package/README.md +298 -76
  3. package/VERIFY.md +3 -3
  4. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +3 -3
  5. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +308 -0
  6. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +311 -0
  7. package/docs/superpowers/plans/2026-08-30-readme-v2.md +294 -0
  8. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +284 -0
  9. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +722 -0
  10. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +146 -0
  11. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +4 -0
  12. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +4 -0
  13. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +47 -0
  14. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +105 -0
  15. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +115 -0
  16. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +78 -0
  17. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +120 -0
  18. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +99 -0
  19. package/package.json +1 -1
  20. package/runner/pty-runner.mjs +64 -17
  21. package/src/core/code-refs-store.mjs +3 -0
  22. package/src/core/editor-state-reporter.mjs +102 -0
  23. package/src/core/launch.mjs +6 -0
  24. package/src/core/pty-attach-jiggle-controller.mjs +71 -17
  25. package/src/core/pty-input.mjs +32 -0
  26. package/src/core/pty-scroll.mjs +4 -3
  27. package/src/core/repo.mjs +3 -0
  28. package/src/core/worktree.mjs +1 -0
  29. package/src/index.ts +12 -1
  30. package/src/ui/dashboard.ts +6 -2
  31. package/src/ui/pty-attach.ts +148 -32
@@ -0,0 +1,294 @@
1
+ # README v2 Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Rewrite `README.md` into an accurate, task-oriented English user guide for the current Pi Agent Board package, and correct the stale install command in `VERIFY.md`.
6
+
7
+ **Architecture:** This is a documentation-only change. `README.md` becomes the primary user-facing guide, organized around installation, first use, dashboard actions, reference behavior, configuration, safety, troubleshooting, and maintainer entry points. `VERIFY.md` receives one supporting-document correction so the linked verification path uses the scoped package name. Source code and `package.json` remain the behavior authority.
8
+
9
+ **Tech Stack:** Markdown, shell command examples, GitHub/Pi package links, existing Node.js verification scripts.
10
+
11
+ ## Global Constraints
12
+
13
+ - Keep all user-facing documentation in English; discuss implementation progress in Chinese.
14
+ - Work only in `/home/elling/git-repo/github/pi-agent-board/.pi/worktrees/issue-51-readme`; do not touch the main checkout.
15
+ - Use `package.json`, `src/index.ts`, `src/commands/*`, `src/ui/dashboard.ts`, `src/ui/pty-attach.ts`, `src/runtime/service.mjs`, and `src/core/*` as the source of truth.
16
+ - Do not advertise worktree isolation, plan-approval UI, provider-stall detection, or other planned/disabled behavior as shipped.
17
+ - State explicitly that worktree isolation is currently disabled and same-repository concurrent writes require user-managed isolation.
18
+ - Do not list internal child markers such as `AGENT_BOARD_CHILD`, `AGENT_BOARD_VIEW_ID`, or `AGENT_BOARD_HOSTED` as user settings.
19
+ - Do not advertise `AGENT_BOARD_ALLOW_PIPE_FALLBACK` as a normal ambient user toggle because the current service does not pass that ambient variable into the PTY runner configuration.
20
+ - Do not hard-code an unverified test count as a durable README claim; use CI and `npm run verify` as the authority.
21
+ - Do not modify runtime code, PRD/history documents, or the main checkout.
22
+ - Do not commit, push, open a PR, or merge without explicit user permission.
23
+
24
+ ## File Map
25
+
26
+ - Modify: `README.md` — complete user-first guide and reference.
27
+ - Modify: `VERIFY.md` — one stale scoped-package install command.
28
+ - Create: `docs/superpowers/specs/2026-08-30-readme-v2-design.md` — approved design copied into the worktree.
29
+ - Create: `docs/superpowers/plans/2026-08-30-readme-v2.md` — this implementation plan.
30
+ - Inspect only: `package.json`, `src/index.ts`, `src/commands/agent-board.ts`, `src/commands/bg.ts`, `src/ui/dashboard.ts`, `src/ui/pty-attach.ts`, `src/runtime/service.mjs`, `src/core/rows.mjs`, `src/core/auto-state.mjs`, `src/core/launch-options.mjs`, `src/core/code-refs*.mjs`, `.github/workflows/ci.yml`, and `VERIFY.md`.
31
+
32
+ ---
33
+
34
+ ### Task 1: Rewrite README structure and first-use path
35
+
36
+ **Files:**
37
+ - Modify: `README.md`
38
+ - Inspect: `package.json`, `src/index.ts`, `src/commands/agent-board.ts`, `src/commands/bg.ts`, `src/ui/dashboard.ts`
39
+
40
+ **Interfaces:**
41
+ - Consumes: package identity and scripts from `package.json`; registered commands/flag from `src/index.ts` and `src/commands/*`; dashboard behavior from `src/ui/dashboard.ts`.
42
+ - Produces: an English README whose first-use path is Requirements → Install → Quick start → Entry points → Dashboard workflow.
43
+
44
+ - [ ] **Step 1: Replace the product introduction and requirements sections**
45
+
46
+ Write the opening around the current value proposition: a full-screen TUI for durable background Pi sessions, global cross-project visibility, dashboard triage, inline reply/evidence, and PTY/JSON fallback. Keep the existing banner, demo, package gallery, and npm links if they remain valid.
47
+
48
+ Add requirements for Pi, Node.js 20+, working Pi provider authentication, and PTY support for live attach/start-and-attach. State that provider authentication is a Pi prerequisite, not an Agent Board credential setup.
49
+
50
+ Use this package command exactly:
51
+
52
+ ```bash
53
+ pi install npm:@zhuxixi/pi-agent-board
54
+ ```
55
+
56
+ Do not use the unscoped `pi-agent-board` package name.
57
+
58
+ - [ ] **Step 2: Add installation alternatives and auth sanity check**
59
+
60
+ Keep three clearly separated paths:
61
+
62
+ ```bash
63
+ # Published package
64
+ pi install npm:@zhuxixi/pi-agent-board
65
+
66
+ # Local package checkout
67
+ npm install
68
+ pi install "$(pwd)"
69
+
70
+ # Development auto-discovery
71
+ ln -s "$(pwd)" ~/.pi/agent/extensions/agent-board
72
+ ```
73
+
74
+ Explain that `pi remove "$(pwd)"` applies to the local path installation, while the development symlink must be removed manually. Include the existing one-shot check and explain that it should end with an assistant `message_end`, then `agent_end`, and exit. Link `VERIFY.md` for the complete diagnostic sequence.
75
+
76
+ - [ ] **Step 3: Add Quick start and entry-point differences**
77
+
78
+ Add a five-step first-task flow:
79
+
80
+ 1. Press `i` to enter INSERT mode.
81
+ 2. Type a task.
82
+ 3. Press `Enter` to open **Start session**.
83
+ 4. Review cwd, model, thinking, and action.
84
+ 5. Press `Enter` to launch.
85
+
86
+ Document `/agent-board`, `pi /agent-board`, `pi --agent-board`, and `/bg [prompt]`. State that `pi /agent-board` runs the standalone dashboard path and quitting it shuts down Pi; state that `--agent-board` cannot attach to a managed session and normal `/agent-board` is required for attach. Explain that `/bg` adopts the current interactive session and optionally queues a prompt.
87
+
88
+ - [ ] **Step 4: Verify the first-use section against source**
89
+
90
+ Check every command and behavior statement against `src/index.ts`, `src/commands/bg.ts`, `src/commands/agent-board.ts`, and the dashboard input handlers. Confirm that draft Enter opens the launch dialog, empty Enter attaches/resumes, and `i` is required before typing in Normal mode.
91
+
92
+ Run:
93
+
94
+ ```bash
95
+ grep -nE '/agent-board|/bg|agent-board|INSERT|Start session|Enter|attach' README.md src/index.ts src/commands/*.ts src/ui/dashboard.ts
96
+ ```
97
+
98
+ Expected: all advertised entry points and input transitions have matching source evidence and no unscoped install command appears in the new README.
99
+
100
+ ---
101
+
102
+ ### Task 2: Add dashboard, views, states, filters, and attach reference
103
+
104
+ **Files:**
105
+ - Modify: `README.md`
106
+ - Inspect: `src/ui/dashboard.ts`, `src/ui/pty-attach.ts`, `src/core/rows.mjs`, `src/core/types.mjs`, `src/runtime/service.mjs`, `src/ui/dashboard-evidence.mjs`
107
+
108
+ **Interfaces:**
109
+ - Consumes: dashboard modes/key handlers, row state/filter helpers, service fallback behavior, and PTY attach input handling.
110
+ - Produces: view-scoped reference sections that do not imply a key works in every dashboard mode.
111
+
112
+ - [ ] **Step 1: Document dashboard modes, launch dialog, and destructive actions**
113
+
114
+ Add Normal vs INSERT behavior, including `/` being literal in INSERT mode. Explain the launch dialog fields: cwd picker with favorites/browse and Tab completion, Pi-scoped model choices, supported thinking levels, and background versus start-and-attach. Mention persisted launch preferences and PTY-dependent start-and-attach fallback.
115
+
116
+ Document exact actions:
117
+
118
+ - `d` confirms Done for inactive sessions;
119
+ - manual completion is the default;
120
+ - `Ctrl+X` twice quickly archives/deletes a row;
121
+ - archive removes the row from the board but preserves the Pi session file;
122
+ - `X` removes inactive rows in the selected state;
123
+ - `m` enters batch selection with Space/a/u/d/Ctrl+X.
124
+
125
+ - [ ] **Step 2: Document view-specific shortcuts and capabilities**
126
+
127
+ Provide separate tables or subsections for Main list, Peek, Transcript, Evidence/Diagnostics, and PTY attach. Include:
128
+
129
+ - main-list navigation and actions;
130
+ - `Space` Peek;
131
+ - `r` reply only from Peek/Transcript/Evidence, not the main list; in Peek, it enters reply mode and the user presses Enter again after typing to send;
132
+ - `v` read-only transcript;
133
+ - `e` Evidence/Diagnostics and evidence-preserving diagnostic clear with `x`;
134
+ - attach with Enter/Right/`>`;
135
+ - PTY detach with `Left` when the child input is empty; edited input forwards the key, while a disconnected host can always be exited; `Ctrl+]` is passed through to the child Pi editor;
136
+ - attach scroll keys, mouse selection/copy, link opening, and optional middle-click paste.
137
+
138
+ State that pending Pi question/questionnaire tools require attach and cannot be answered with inline reply.
139
+
140
+ - [ ] **Step 3: Document states, grouping, unread, queue, and filters**
141
+
142
+ Document the exact display labels: Queued, Running, Needs answer, Needs instructions, Done, Failed, and Stopped. Explain semantic state versus process liveness, state/folder grouping, pinned-first stable creation ordering, unread indicators, busy follow-up FIFO queue, `qN`, and `queued:true`.
143
+
144
+ Document this filter syntax:
145
+
146
+ ```text
147
+ s:running
148
+ review:ready
149
+ diag:stalled
150
+ evidence:error
151
+ queued:true
152
+ steer:awaiting-approval
153
+ ```
154
+
155
+ Explain free-text AND matching over name, summary, and cwd, case-insensitive state aliases, and the limitation that `diag:stalled` consumes persisted diagnostics but does not represent a complete current provider-stall detector.
156
+
157
+ - [ ] **Step 4: Document evidence, code references, and persistence**
158
+
159
+ Explain Peek's summary/blocker/latest-output surface, transcript projection, Evidence/Diagnostics contents, durable artifacts, and locally extracted issue/PR badges. Mention optional per-root `providers.json` only as an extension point; do not invent an unverified schema.
160
+
161
+ Explain that busy replies are queued and drained when the session is ready. Explain the high-level store location `~/.pi/agent/agent-board/` and persistence through reload/restart/worker exit.
162
+
163
+ - [ ] **Step 5: Verify all advertised shortcuts and states**
164
+
165
+ Run:
166
+
167
+ ```bash
168
+ grep -nE 'handle(List|Select|Peek|Session|Evidence)Key|renderHelp|renderPtyHelp|Ctrl|ctrl\+|review:ready|diag:stalled|evidence:error|queued:|steer:' src/ui/dashboard.ts src/core/rows.mjs
169
+ ```
170
+
171
+ Compare every README shortcut/filter/state claim with the matching source handler. Remove any claim that only exists in PRD or planning documents.
172
+
173
+ ---
174
+
175
+ ### Task 3: Add configuration, safety, troubleshooting, and maintainer links
176
+
177
+ **Files:**
178
+ - Modify: `README.md`
179
+ - Inspect: `src/core/auto-state.mjs`, `src/core/title.mjs`, `runner/job-runner.mjs`, `runner/title-runner.mjs`, `src/runtime/service.mjs`, `src/ui/pty-attach.ts`, `src/core/pty-support.mjs`, `src/core/paths.mjs`, `.github/workflows/ci.yml`, `package.json`
180
+
181
+ **Interfaces:**
182
+ - Consumes: supported environment-variable reads, default values, PTY diagnosis behavior, package scripts, CI checks, and existing documentation links.
183
+ - Produces: a complete user-facing configuration table and explicit limitations/troubleshooting path.
184
+
185
+ - [ ] **Step 1: Replace the incomplete configuration table**
186
+
187
+ Document these supported user-facing settings with exact defaults and disable values: `AGENT_BOARD_ROOT`, `AGENT_BOARD_AUTO_STATE`, `AGENT_BOARD_AUTO_STATE_MODEL`, `AGENT_BOARD_AUTO_STATE_NO_DONE`, `AGENT_BOARD_SUMMARY_MODEL`, `AGENT_BOARD_TITLE_MODEL`, `AGENT_BOARD_TITLE_THINKING_LEVEL`, `AGENT_BOARD_CODE_REFS`, `AGENT_BOARD_DISABLE_PTY`, `AGENT_BOARD_FORCE_PTY`, `AGENT_BOARD_ATTACH_MOUSE`, `AGENT_BOARD_ENABLE_MOUSE_SCROLL`, `AGENT_BOARD_WHEEL_LINES`, `AGENT_BOARD_MAX_WARM_HOSTS`, `AGENT_BOARD_WARM_HOST_TTL_MS`, `AGENT_BOARD_ATTACH_NATIVE_PASTE`, `AGENT_BOARD_FORWARD_OSC52`, and `AGENT_BOARD_FORWARD_IMAGES`.
188
+
189
+ State the important default correctly: `AGENT_BOARD_AUTO_STATE_NO_DONE` unset means the user marks Done manually; `0`, `false`, `off`, or `no` restores automatic Done classification. Explain heuristic fallback for summary/title/state model failures where applicable.
190
+
191
+ Mention selected `AGENT_VIEW_*` names only as compatibility aliases and prefer `AGENT_BOARD_*` for new setup. Exclude internal child markers and do not present `AGENT_BOARD_ALLOW_PIPE_FALLBACK` as an ambient normal-user setting.
192
+
193
+ - [ ] **Step 2: Add safety, fallback, and troubleshooting sections**
194
+
195
+ Prominently state that worktree isolation is currently disabled and not automatically created. Same-repository concurrent sessions can run at the same time, so users must avoid overlapping writes or provide their own isolation.
196
+
197
+ Explain PTY versus JSON-runner fallback, the start-and-attach degradation, adopted external-session PTY requirement, Windows named-pipe/hidden-console capability, and `!` diagnostics. Add symptom-based troubleshooting for stuck Running/auth, `node-pty unavailable`, slow attach/reconnect, rejected inline reply, and same-repository conflicts.
198
+
199
+ - [ ] **Step 3: Simplify development, publishing, and further reading**
200
+
201
+ Keep maintainer sections concise:
202
+
203
+ ```bash
204
+ npm install
205
+ npm run verify
206
+ ```
207
+
208
+ Explain that verify runs typecheck, tests, coverage, and package dry-run. Keep publishing as verify, `npm version patch` (or minor/major), and `npm publish`. Link `VERIFY.md`, `PRD.md`, `PROGRESS.md`, and relevant deeper design material without embedding historical progress or a stale numeric test count.
209
+
210
+ - [ ] **Step 4: Validate configuration and limitation claims**
211
+
212
+ Run:
213
+
214
+ ```bash
215
+ git grep -nE 'AGENT_BOARD_[A-Z0-9_]+' -- ':!README.md' ':!coverage/**' ':!node_modules/**'
216
+ grep -nE 'DEFAULT_|AUTO_STATE_NO_DONE|AGENT_BOARD_|node-pty|worktree|windows|verify' README.md package.json src/core/*.mjs src/runtime/*.mjs src/ui/*.ts runner/*.mjs .github/workflows/ci.yml
217
+ ```
218
+
219
+ Expected: each public README variable has a source read and an accurate default; internal markers and unsupported worktree claims are absent.
220
+
221
+ ---
222
+
223
+ ### Task 4: Correct VERIFY.md and run documentation validation
224
+
225
+ **Files:**
226
+ - Modify: `VERIFY.md`
227
+ - Inspect: `README.md`, `VERIFY.md`, `package.json`, all README link targets
228
+
229
+ **Interfaces:**
230
+ - Consumes: the README's package/install path and the existing verification checklist.
231
+ - Produces: consistent scoped package installation instructions and validation evidence for the documentation change.
232
+
233
+ - [ ] **Step 1: Correct the stale published-package command**
234
+
235
+ Replace only this command in `VERIFY.md`:
236
+
237
+ ```bash
238
+ pi install npm:pi-agent-board
239
+ ```
240
+
241
+ with:
242
+
243
+ ```bash
244
+ pi install npm:@zhuxixi/pi-agent-board
245
+ ```
246
+
247
+ Do not change the verification procedure or historical notes beyond this scoped package correction.
248
+
249
+ - [ ] **Step 2: Check Markdown links and stale wording**
250
+
251
+ Run:
252
+
253
+ ```bash
254
+ python - <<'PY'
255
+ from pathlib import Path
256
+ import re
257
+
258
+ for path in (Path("README.md"), Path("VERIFY.md")):
259
+ text = path.read_text()
260
+ for line_no, line in enumerate(text.splitlines(), 1):
261
+ for target in re.findall(r"\]\(([^)]+)\)", line):
262
+ if target.startswith(("http://", "https://", "#", "mailto:")):
263
+ continue
264
+ candidate = (path.parent / target.split("#", 1)[0]).resolve()
265
+ if not candidate.exists():
266
+ raise SystemExit(f"broken link: {path}:{line_no}: {target}")
267
+ print("relative Markdown links: OK")
268
+ PY
269
+
270
+ grep -RInE 'pi install npm:pi-agent-board|default: enabled|300\+ tests|r.*reply' README.md VERIFY.md || true
271
+ ```
272
+
273
+ Expected: no broken relative links, no stale unscoped package command, no ambiguous auto-done wording, and no stale test-count claim.
274
+
275
+ - [ ] **Step 3: Run repository verification and inspect the final diff**
276
+
277
+ Run from the issue worktree:
278
+
279
+ ```bash
280
+ npm run typecheck
281
+ npm test
282
+ npm run pack:dry
283
+ git diff --check
284
+ git diff --stat
285
+ git status --short
286
+ ```
287
+
288
+ Expected: typecheck succeeds, the clean worktree baseline remains 416/416 tests with 0 failures, package dry-run succeeds, `git diff --check` is clean, and the diff contains only the approved spec/plan plus `README.md` and the one-line `VERIFY.md` correction.
289
+
290
+ Do not include the main checkout's untracked PTY tests in this evidence. Do not claim the main checkout is green while those unrelated tests remain failing.
291
+
292
+ - [ ] **Step 4: Prepare issue progress and handoff**
293
+
294
+ Record in the Issue #51 progress comment: the isolated worktree path, the documentation files changed, validation commands and results, any residual source-of-truth caveats, and the fact that no push/PR/merge was performed. Stop before pushing or opening a PR and request explicit user permission.
@@ -0,0 +1,284 @@
1
+ # ← Detach Gate Cursor-Anchor Fix Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Fix issue #66 — `←` must detach when the Pi editor line is empty even while the terminal cursor rests on working/output lines (attach / streaming).
6
+
7
+ **Architecture:** Replace the cursor-line anchor in `PtyAttachComponent.childInputLooksEmpty()` with a bottom-up scan for Pi's inverse-video fake-cursor cell (`ESC[7m`, persists in the xterm buffer across differential frames), with a prompt-glyph fallback and a "treat as empty" escape fallback. Add a pure helper `isProbablyPiInputLine` for glyph detection.
8
+
9
+ **Tech Stack:** TypeScript (pty-attach.ts, run via `node --experimental-transform-types` in tests), plain ESM (pty-input.mjs), `node:test` runner, @xterm/headless.
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md`
12
+
13
+ ## Global Constraints
14
+
15
+ - Coverage gates: lines 85 / funcs 80 / branches 70 (c8, `npm run test:coverage`); Node 22/24 both green (CI).
16
+ - `isProbablyPiInputLine` glyph set must stay identical to `isProbablyEmptyPiInputLine`'s trim charset: `›>┃│|┆╎╏:`.
17
+ - Do not change `ctrl+]` semantics (passes through to Pi since v0.5.1) and do not make `←` unconditionally detach (keeps the edit-protection gate — spec §3).
18
+ - Do not modify `src/core/pty-input.mjs`'s existing `isProbablyEmptyPiInputLine` behavior.
19
+ - All edits inside worktree `WT=.pi/worktrees/issue-66-attach-detach-gate-cursor-anchor`; git ops via `git -C $WT`.
20
+
21
+ ---
22
+
23
+ ### Task 1: Add `isProbablyPiInputLine` pure helper + unit tests
24
+
25
+ **Files:**
26
+ - Modify: `src/core/pty-input.mjs` (append glyph const + function after existing helper)
27
+ - Test: `test/pty-input.test.mjs`
28
+
29
+ **Interfaces:**
30
+ - Produces: `export function isProbablyPiInputLine(line: string): boolean` — true iff the line, after trimming leading whitespace, starts with a prompt/continuation glyph.
31
+
32
+ - [ ] **Step 1: Write the failing test**
33
+
34
+ Append to `test/pty-input.test.mjs` (keep existing tests untouched):
35
+
36
+ ```js
37
+ import { isProbablyEmptyPiInputLine, isProbablyPiInputLine } from "../src/core/pty-input.mjs";
38
+
39
+ test("isProbablyPiInputLine recognizes Pi prompt / continuation lines", () => {
40
+ assert.equal(isProbablyPiInputLine("> "), true);
41
+ assert.equal(isProbablyPiInputLine(" ┃ edit me"), true);
42
+ assert.equal(isProbablyPiInputLine(" │ second line"), true);
43
+ assert.equal(isProbablyPiInputLine("› draft"), true);
44
+ });
45
+
46
+ test("isProbablyPiInputLine rejects content lines and empty lines", () => {
47
+ assert.equal(isProbablyPiInputLine("chat content"), false);
48
+ assert.equal(isProbablyPiInputLine("────── ◊◊ ──────"), false);
49
+ assert.equal(isProbablyPiInputLine(""), false);
50
+ assert.equal(isProbablyPiInputLine(" "), false);
51
+ });
52
+ ```
53
+
54
+ - [ ] **Step 2: Run test to verify it fails**
55
+
56
+ Run: `node --test test/pty-input.test.mjs`
57
+ Expected: FAIL — `isProbablyPiInputLine is not a function` (import error).
58
+
59
+ - [ ] **Step 3: Write minimal implementation**
60
+
61
+ Append to `src/core/pty-input.mjs` (before `isProbablyEmptyPiInputLine` or after — any top-level position):
62
+
63
+ ```js
64
+ /** Glyphs Pi uses to render editor prompt / continuation lines (`>` main prompt,
65
+ * `›`/`┃`/`│` and variants in older releases). Must stay in sync with the
66
+ * trim charset of isProbablyEmptyPiInputLine below. */
67
+ const PROMPT_GLYPHS = "›>┃│|┆╎╏:";
68
+
69
+ /**
70
+ * Whether the given terminal line looks like a Pi editor input line: leading
71
+ * whitespace followed by a prompt/continuation glyph. The attach surface uses
72
+ * this to locate the editor line inside the buffer instead of trusting the
73
+ * terminal cursor, which wanders onto output/working lines while Pi streams
74
+ * (issue #66).
75
+ * @param {string} line
76
+ * @returns {boolean}
77
+ */
78
+ export function isProbablyPiInputLine(line) {
79
+ const withoutLeftPadding = String(line || "").replace(/^[\s\u00a0]+/u, "");
80
+ return withoutLeftPadding.length > 0 && PROMPT_GLYPHS.includes(withoutLeftPadding[0]);
81
+ }
82
+ ```
83
+
84
+ - [ ] **Step 4: Run test to verify it passes**
85
+
86
+ Run: `node --test test/pty-input.test.mjs`
87
+ Expected: PASS (4 tests: 2 existing + 2 new).
88
+
89
+ - [ ] **Step 5: Commit**
90
+
91
+ ```bash
92
+ git add test/pty-input.test.mjs src/core/pty-input.mjs
93
+ git commit -m "feat: add isProbablyPiInputLine helper (issue #66)"
94
+ ```
95
+
96
+ ---
97
+
98
+ ### Task 2: Rework `childInputLooksEmpty()` anchor + smoke scenarios
99
+
100
+ **Files:**
101
+ - Modify: `src/ui/pty-attach.ts` (import line ~8; `childInputLooksEmpty()` ~line 319; add private helper near it)
102
+ - Modify: `test-support/detach-gate-smoke.ts` (scenarios B/B1/B2/B3 + new E)
103
+ - Modify: `test/pty-attach-detach-gate.test.mjs` (assertions)
104
+
105
+ **Interfaces:**
106
+ - Consumes: `isProbablyPiInputLine` from Task 1; existing `isProbablyEmptyPiInputLine`.
107
+ - Produces: private `findLastInverseCellLine(active): number | null` — bottom-most line index containing an `isInverse()` cell; `null` when none.
108
+
109
+ - [ ] **Step 1: Write the failing smoke scenarios**
110
+
111
+ In `test-support/detach-gate-smoke.ts`:
112
+
113
+ 1. Change scenario **B** (line ~76) so the draft line carries a fake cursor, then add B3 (cursor off the empty input line) and E (empty input line without fake cursor). Replace the current B block and append:
114
+
115
+ ```ts
116
+ // B. ← must NOT detach while attached with a draft in the editor line (child
117
+ // is mid-draft and ← is also the editor's cursor-left key). The draft line
118
+ // carries Pi's inverse-video fake cursor (ESC[7m).
119
+ {
120
+ const { attach, sent, didDetach } = makeAttach();
121
+ await writeToTerm(attach, "chat content\r\n> \x1b[7m草\x1b[27m稿");
122
+ (attach as unknown as { connected: boolean }).connected = true;
123
+ attach.handleInput("\x1b[D");
124
+ out.leftStaysGatedOnNonEmptyLine = !didDetach() && sent.length === 1 && sent[0].type === "input" && sent[0].data === "\x1b[D";
125
+ attach.dispose();
126
+ }
127
+
128
+ // B3. The streaming case from issue #66: the editor line is empty (bottom of
129
+ // the buffer, with its fake cursor) but the terminal cursor rests on the
130
+ // working line because Pi's differential frames only repaint the changed
131
+ // line. The gate must be judged from the fake-cursor line, not the cursor.
132
+ {
133
+ const { attach, sent, didDetach } = makeAttach();
134
+ await writeToTerm(attach, "chat content\r\n> \x1b[7m \x1b[27m");
135
+ await writeToTerm(attach, "\x1b[2;1H⠙ Working...");
136
+ (attach as unknown as { connected: boolean }).connected = true;
137
+ attach.handleInput("\x1b[D");
138
+ out.leftDetachesWhenCursorOffEmptyInputLine = didDetach() && sent.length === 0;
139
+ attach.dispose();
140
+ }
141
+
142
+ // E. Empty input line rendered WITHOUT a fake cursor: no inverse cell and no
143
+ // glyph anywhere, and the terminal cursor sits on a non-empty output line.
144
+ // Falls through to the escape fallback — treat as empty, detach.
145
+ {
146
+ const { attach, sent, didDetach } = makeAttach();
147
+ await writeToTerm(attach, "chat content\r\n");
148
+ await writeToTerm(attach, "\x1b[1;1H"); // park the cursor on the non-empty line
149
+ (attach as unknown as { connected: boolean }).connected = true;
150
+ attach.handleInput("\x1b[D");
151
+ out.leftDetachesOnEmptyInputWithoutFakeCursor = didDetach() && sent.length === 0;
152
+ attach.dispose();
153
+ }
154
+ ```
155
+
156
+ 2. Update the B1 header comment (scenario text already fits) and ensure B1 (`────── ◊◊ ──────` garbled buffer) stays as-is — it now asserts escape-on-garbled-buffer.
157
+
158
+ - [ ] **Step 2: Run smoke to verify new scenarios fail**
159
+
160
+ Run: `node --experimental-transform-types test-support/detach-gate-smoke.ts`
161
+ Expected: `leftDetachesWhenCursorOffEmptyInputLine` is `false` (old cursor-line anchor reads the Working line) and `leftDetachesOnEmptyInputWithoutFakeCursor` is `false` (old anchor reads last non-empty line `chat content`? — verify output); `leftStaysGatedOnNonEmptyLine` true.
162
+
163
+ - [ ] **Step 3: Write the implementation**
164
+
165
+ In `src/ui/pty-attach.ts`:
166
+
167
+ 1. Update import (line ~8):
168
+
169
+ ```ts
170
+ import { isProbablyEmptyPiInputLine, isProbablyPiInputLine } from "../core/pty-input.mjs";
171
+ ```
172
+
173
+ 2. Replace the `childInputLooksEmpty()` method (currently ~line 319) and add the helper above it:
174
+
175
+ ```ts
176
+ /** Bottom-most line whose cells include an inverse-video cell — Pi renders
177
+ * its editor cursor as an inverse "fake cursor" (`ESC[7m`), and the cell
178
+ * persists in the buffer even while streaming differential frames skip
179
+ * repainting the editor line. */
180
+ private findLastInverseCellLine(active: {
181
+ baseY: number;
182
+ length: number;
183
+ getLine(index: number): BufferLineLike | undefined;
184
+ }): number | null {
185
+ for (let y = active.baseY + active.length - 1; y >= active.baseY; y--) {
186
+ const line = active.getLine(y);
187
+ if (!line) continue;
188
+ for (let x = 0; x < line.length; x++) {
189
+ if (line.getCell(x)?.isInverse()) return y;
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+
195
+ private childInputLooksEmpty(): boolean {
196
+ if (!this.receivedOutput) return true;
197
+ const active = this.term.buffer.active;
198
+ // The terminal cursor is not a reliable anchor for the editor line:
199
+ // while Pi streams output (or right after attach) the cursor rests on
200
+ // working/output lines, never the input line, so a genuinely empty
201
+ // editor was misread as non-empty and ← stopped detaching (issue #66).
202
+ // Pi's editor line always carries an inverse-video fake-cursor cell,
203
+ // so anchor on that instead.
204
+ const fakeCursorLine = this.findLastInverseCellLine(active);
205
+ if (fakeCursorLine !== null) {
206
+ const line = active.getLine(fakeCursorLine)?.translateToString(true) ?? "";
207
+ return isProbablyEmptyPiInputLine(line);
208
+ }
209
+ // Fallback: Pi variants that render no fake cursor — look for a
210
+ // prompt-glyph line.
211
+ for (let y = active.baseY + active.length - 1; y >= active.baseY; y--) {
212
+ const line = active.getLine(y)?.translateToString(true) ?? "";
213
+ if (isProbablyPiInputLine(line)) return isProbablyEmptyPiInputLine(line);
214
+ }
215
+ // No editor line recoverable (e.g. a garbled replay buffer): treat the
216
+ // input as empty — ← is the only detach key left on the attach surface,
217
+ // so it must always escape rather than trap the user.
218
+ return true;
219
+ }
220
+ ```
221
+
222
+ - [ ] **Step 4: Update gate assertions**
223
+
224
+ In `test/pty-attach-detach-gate.test.mjs`, add to the existing test (after `leftDetachesOnEmptyInput`):
225
+
226
+ ```js
227
+ assert.equal(parsed.leftDetachesWhenCursorOffEmptyInputLine, true, "← must detach when the editor line is empty even if the cursor sits on a working line");
228
+ assert.equal(parsed.leftDetachesOnEmptyInputWithoutFakeCursor, true, "← must detach when an empty editor line renders no fake cursor");
229
+ ```
230
+
231
+ - [ ] **Step 5: Run tests to verify pass**
232
+
233
+ Run: `node --test test/pty-attach-detach-gate.test.mjs test/pty-input.test.mjs`
234
+ Expected: PASS — all gate assertions true, including the three new ones.
235
+
236
+ - [ ] **Step 6: Commit**
237
+
238
+ ```bash
239
+ git add src/ui/pty-attach.ts test-support/detach-gate-smoke.ts test/pty-attach-detach-gate.test.mjs
240
+ git commit -m "fix: anchor ← detach gate on Pi's fake-cursor line, not terminal cursor (issue #66)"
241
+ ```
242
+
243
+ ---
244
+
245
+ ### Task 3: Full regression (A6)
246
+
247
+ **Files:** none (verification only).
248
+
249
+ - [ ] **Step 1: Run verify**
250
+
251
+ Run (in worktree): `npm run verify`
252
+ Expected: typecheck clean; all `node --test test/*.test.mjs` pass (325+ tests, none of the pre-existing ones changed semantics); c8 coverage above gates (lines ≥85 / funcs ≥80 / branches ≥70); `npm pack --dry-run` succeeds.
253
+
254
+ - [ ] **Step 2: Commit any incidental fixes**
255
+
256
+ If verify surfaced issues, fix and commit with a message referencing issue #66. Otherwise nothing to commit.
257
+
258
+ ---
259
+
260
+ ### Task 4: Post-implementation manual verification (U1, U2)
261
+
262
+ **Files:** none (user verification; report back into the PR).
263
+
264
+ - [ ] **Step 1: U1 — attach-then-←**
265
+
266
+ 1. Start agent-board, attach into an existing pi session (or create a new one).
267
+ 2. As soon as the attach surface paints, press `←`.
268
+ Expected: returns to the dashboard immediately, no ↑/↓ or type-then-delete needed.
269
+
270
+ - [ ] **Step 2: U2 — ← while Pi is thinking**
271
+
272
+ 1. Attach into a session and send a prompt that triggers Pi streaming (`⠹ Working...` animation visible).
273
+ 2. While the animation is running, press `←`.
274
+ Expected: returns to the dashboard. (Fails before this fix — the exact reported symptom.)
275
+
276
+ - [ ] **Step 3: Sanity — ← does not steal the editor's cursor-left**
277
+
278
+ 1. Attach, type a draft in the input box.
279
+ 2. Press `←`.
280
+ Expected: cursor moves left inside the draft (key forwarded), NOT detach.
281
+
282
+ - [ ] **Step 4: Record results**
283
+
284
+ Write the U1/U2/U3 outcomes into the PR description (or a PR comment) before merge.