@zhuxixi/pi-agent-board 0.5.1 → 0.6.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/CHANGELOG.md +32 -0
- package/README.md +41 -3
- package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +284 -0
- package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +722 -0
- package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +551 -0
- package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +146 -0
- package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +209 -0
- package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +796 -0
- package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +114 -0
- package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +73 -0
- package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +143 -0
- package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +226 -0
- package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +87 -0
- package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +53 -0
- package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +731 -0
- package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +1 -1
- package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +78 -0
- package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +120 -0
- package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +92 -0
- package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +99 -0
- package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +50 -0
- package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +106 -0
- package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +64 -0
- package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +48 -0
- package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +64 -0
- package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +71 -0
- package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +49 -0
- package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +76 -0
- package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +510 -0
- package/package.json +83 -81
- package/runner/job-runner.mjs +2 -2
- package/runner/pty-runner.mjs +585 -4
- package/runner/state-runner.mjs +3 -0
- package/runner/title-runner.mjs +1 -1
- package/scripts/release_helper.mjs +277 -0
- package/src/commands/agent-board.ts +38 -35
- package/src/commands/attach-decision.mjs +66 -0
- package/src/commands/attach-flow.ts +45 -39
- package/src/core/code-refs.mjs +85 -33
- package/src/core/editor-state-reporter.mjs +102 -0
- package/src/core/evidence.mjs +2 -2
- package/src/core/heuristics.mjs +40 -2
- package/src/core/host-coordination.mjs +159 -0
- package/src/core/host-crash.mjs +43 -3
- package/src/core/host-probe.mjs +196 -0
- package/src/core/launch.mjs +3 -1
- package/src/core/locks.mjs +196 -1
- package/src/core/paths.mjs +24 -0
- package/src/core/pty-input.mjs +32 -0
- package/src/core/store.mjs +164 -5
- package/src/core/types.mjs +17 -1
- package/src/core/warm-host-sweeper.mjs +150 -0
- package/src/index.ts +41 -2
- package/src/runtime/service.mjs +967 -109
- package/src/ui/dashboard-decisions.mjs +55 -0
- package/src/ui/dashboard.ts +26 -12
- package/src/ui/pty-attach.ts +55 -7
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Detach-Gate Tier-2 Glyph Fallback Tightening 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:** Tighten the attach-surface `←` detach gate's tier-2 glyph fallback so content glyph lines (markdown table rows / quotes / drafts) no longer trap the user when `editor_state` is unavailable (issue #69).
|
|
6
|
+
|
|
7
|
+
**Architecture:** One-line condition change in `PtyAttachComponent.childInputLooksEmpty()` (src/ui/pty-attach.ts): the tier-2 loop now only treats an EMPTY prompt-glyph line as proof of an empty editor (`isProbablyPiInputLine(line) && isProbablyEmptyPiInputLine(line)`), skipping content glyph lines; loop-end escape stays authoritative. Tier-1 (inverse fake-cursor anchor), the `editor_state` authoritative path (#71), and all protocol/public API surfaces are untouched. Behavior is pinned by two new detach-gate smoke scenarios (K1/K2) asserted inside the existing `test/pty-attach-detach-gate.test.mjs` case.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript (Node `--experimental-transform-types`), node:test, @xterm/headless in-memory smoke harness.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md` (accepted 2026-09-03, Option B).
|
|
12
|
+
|
|
13
|
+
**Acceptance traceability:** Task 1 → A1, A2, A3, A4 (automated). Task 2 → U1, U2 (manual, human-executed after push, before merge). No orphan tasks, no orphan acceptance IDs.
|
|
14
|
+
|
|
15
|
+
## Global Constraints
|
|
16
|
+
|
|
17
|
+
- Work only inside this worktree: `/home/elling/git-repo/github/pi-agent-board/.pi/worktrees/issue-69-detach-gate-glyph-fallback`. Never edit the main checkout at `/home/elling/git-repo/github/pi-agent-board` (`node_modules` there is only the read-only symlink target).
|
|
18
|
+
- Commit messages: English, conventional commits, suffix `(issue #69)`.
|
|
19
|
+
- Stage files explicitly (`git add <file> ...`); never `git add -A`.
|
|
20
|
+
- Do NOT touch: tier-1 inverse anchor, `editor_state` path (`resolveEditorEmpty`, reporter, runner), README.md, or the pure helpers in `src/core/pty-input.mjs` (spec non-goals).
|
|
21
|
+
- Do NOT extract new pure functions — the tightened tier-2 is constant-true by design; the discriminating tests live at smoke level (spec §4).
|
|
22
|
+
- Every new online-gate smoke scenario MUST pin `connected=true` explicitly before `handleInput` (B-section convention; otherwise the disconnected-escape semantics fire instead of the online gate).
|
|
23
|
+
- `node --test` case count stays 437: the new smoke keys are assertions inside the existing test case in `test/pty-attach-detach-gate.test.mjs`, not new `test()` blocks.
|
|
24
|
+
- Verification commands: `npm test` (expect 437 pass, 0 fail) and `npm run typecheck` (expect 0 errors). Faster smoke-only loop: `node --experimental-transform-types test-support/detach-gate-smoke.ts` (prints a JSON of scenario keys).
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
### Task 1: Tighten tier-2 glyph fallback (TDD) — covers A1, A2, A3, A4
|
|
29
|
+
|
|
30
|
+
**Files:**
|
|
31
|
+
- Modify: `src/ui/pty-attach.ts` (the tier-2 loop inside `childInputLooksEmpty()`, right after the `// Fallback: Pi variants that render no fake cursor` comment, line 359)
|
|
32
|
+
- Test: `test-support/detach-gate-smoke.ts` (insert two scenarios after the J-scenario block, before the `// E2.` comment)
|
|
33
|
+
- Test: `test/pty-attach-detach-gate.test.mjs` (two assertions after the `leftHelloNullResetsStaleEditorState` assertion)
|
|
34
|
+
|
|
35
|
+
**Interfaces:**
|
|
36
|
+
- Consumes: `isProbablyPiInputLine(line)`, `isProbablyEmptyPiInputLine(line)` from `src/core/pty-input.mjs` (both already imported in `pty-attach.ts`); smoke harness helpers `makeAttach()`, `writeToTerm(attach, data)` (already defined in the smoke file).
|
|
37
|
+
- Produces: smoke output keys `leftDetachesOnTableRowsWithoutFakeCursor` (A1) and `leftDetachesOnContentGlyphFallback` (A2) in the JSON printed by `test-support/detach-gate-smoke.ts`.
|
|
38
|
+
|
|
39
|
+
- [ ] **Step 1: Write the failing smoke scenarios (A1, A2)**
|
|
40
|
+
|
|
41
|
+
In `test-support/detach-gate-smoke.ts`, insert these two blocks immediately BEFORE the line starting with `// E2. A terminal at the minimum supported size`:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// K1. Issue #69 real-world shape: zero inverse cells anywhere in the buffer,
|
|
45
|
+
// the chat area carries a markdown table row (`│ … │`) and a quote line
|
|
46
|
+
// (`> …`) that isProbablyPiInputLine misreads as a draft-bearing input line,
|
|
47
|
+
// and editor_state never arrives (editorEmpty stays null — child without the
|
|
48
|
+
// reporter). tier-2 must skip content glyph lines and ← must detach: the
|
|
49
|
+
// gate philosophy is "never trap the user" (issues #42/#48).
|
|
50
|
+
{
|
|
51
|
+
const { attach, sent, didDetach } = makeAttach();
|
|
52
|
+
await writeToTerm(attach, "chat content\r\n│ Issue #778 │ open │\r\n> quote line\r\n");
|
|
53
|
+
(attach as unknown as { connected: boolean }).connected = true;
|
|
54
|
+
attach.handleInput("\x1b[D");
|
|
55
|
+
out.leftDetachesOnTableRowsWithoutFakeCursor = didDetach() && sent.length === 1 && sent[0].type === "detach";
|
|
56
|
+
attach.dispose();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// K2. The deliberate flip side of K1 — pair with scenario B: the SAME draft
|
|
60
|
+
// shape (`> draft`) is gated when the fake cursor is present (tier-1, scenario
|
|
61
|
+
// B) but detaches when the buffer carries no inverse cells (tier-2 fallback
|
|
62
|
+
// cannot tell a real draft from a table row; a spurious detach beats a trapped
|
|
63
|
+
// user, and detach never loses the draft — the child session keeps running).
|
|
64
|
+
// This pins the intentional loss of fallback draft protection (issue #69);
|
|
65
|
+
// restoring it needs the mid-term dock-structure anchor, not a revert.
|
|
66
|
+
{
|
|
67
|
+
const { attach, sent, didDetach } = makeAttach();
|
|
68
|
+
await writeToTerm(attach, "chat content\r\n> draft\r\n");
|
|
69
|
+
(attach as unknown as { connected: boolean }).connected = true;
|
|
70
|
+
attach.handleInput("\x1b[D");
|
|
71
|
+
out.leftDetachesOnContentGlyphFallback = didDetach() && sent.length === 1 && sent[0].type === "detach";
|
|
72
|
+
attach.dispose();
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
In `test/pty-attach-detach-gate.test.mjs`, insert these two assertions immediately AFTER the `assert.equal(parsed.leftHelloNullResetsStaleEditorState, true, ...)` line:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
assert.equal(parsed.leftDetachesOnTableRowsWithoutFakeCursor, true, "← must detach when a zero-inverse buffer holds only table/quote glyph lines and editor_state is unknown (issue #69)");
|
|
80
|
+
assert.equal(parsed.leftDetachesOnContentGlyphFallback, true, "← must detach on a content glyph line in the no-fake-cursor fallback — spurious detach beats trapping (issue #69)");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- [ ] **Step 2: Run to verify both fail (A1, A2 red)**
|
|
84
|
+
|
|
85
|
+
Run: `node --experimental-transform-types test-support/detach-gate-smoke.ts`
|
|
86
|
+
Expected: JSON contains `"leftDetachesOnTableRowsWithoutFakeCursor":false` and `"leftDetachesOnContentGlyphFallback":false` (all pre-existing keys stay `true`).
|
|
87
|
+
|
|
88
|
+
- [ ] **Step 3: Implement the tier-2 tightening (minimal change)**
|
|
89
|
+
|
|
90
|
+
In `src/ui/pty-attach.ts`, inside `childInputLooksEmpty()`, replace this exact block:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// Fallback: Pi variants that render no fake cursor — look for a
|
|
94
|
+
// prompt-glyph line.
|
|
95
|
+
for (let y = active.baseY + active.length - 1; y >= active.baseY; y--) {
|
|
96
|
+
const line = active.getLine(y)?.translateToString(true) ?? "";
|
|
97
|
+
if (isProbablyPiInputLine(line)) return isProbablyEmptyPiInputLine(line);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
with:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// Fallback: Pi variants that render no fake cursor — look for an EMPTY
|
|
105
|
+
// prompt-glyph line. Only an empty glyph line proves an empty editor:
|
|
106
|
+
// content glyph lines (markdown table rows `│ … │`, quotes `> …`, or a
|
|
107
|
+
// real draft in a no-fake-cursor Pi variant) cannot be told apart, and
|
|
108
|
+
// trapping the user is worse than a spurious detach (issue #69) — skip
|
|
109
|
+
// them and keep scanning; the loop-end escape below stays authoritative.
|
|
110
|
+
for (let y = active.baseY + active.length - 1; y >= active.baseY; y--) {
|
|
111
|
+
const line = active.getLine(y)?.translateToString(true) ?? "";
|
|
112
|
+
if (isProbablyPiInputLine(line) && isProbablyEmptyPiInputLine(line)) return true;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- [ ] **Step 4: Run smoke to verify the fix (A1, A2 green)**
|
|
117
|
+
|
|
118
|
+
Run: `node --experimental-transform-types test-support/detach-gate-smoke.ts`
|
|
119
|
+
Expected: EVERY key in the JSON is `true`, including the two new ones.
|
|
120
|
+
|
|
121
|
+
- [ ] **Step 5: Full automated acceptance (A3, A4)**
|
|
122
|
+
|
|
123
|
+
Run: `npm test`
|
|
124
|
+
Expected: `pass 437`, `fail 0` (count unchanged — the new keys are assertions inside the existing test case).
|
|
125
|
+
|
|
126
|
+
Run: `npm run typecheck`
|
|
127
|
+
Expected: exit 0, no output errors.
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 6: Commit**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
git add src/ui/pty-attach.ts test-support/detach-gate-smoke.ts test/pty-attach-detach-gate.test.mjs
|
|
133
|
+
git commit -m "fix: tighten ← detach tier-2 glyph fallback to empty-only lines (issue #69)"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Task 2: Manual verification U1/U2 (human-executed; after PR branch is pushed, before merge) — covers U1, U2
|
|
137
|
+
|
|
138
|
+
> Not a subagent task. The controller surfaces this checklist to the human partner at the pre-merge gate.
|
|
139
|
+
|
|
140
|
+
**Environment:** the running extension copy is the git-source clone at `~/.pi/agent/git/github.com/zhuxixi/pi-agent-board` (currently at 0.5.1 / fac9e91). Steps:
|
|
141
|
+
|
|
142
|
+
- [ ] **Step 1:** `git -C ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board fetch origin && git -C ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board checkout issue-69-detach-gate-glyph-fallback`
|
|
143
|
+
- [ ] **Step 2:** Restart pi IN ANOTHER TERMINAL (restarting kills the session running this agent — do not run inside the controlling session's pi instance).
|
|
144
|
+
- [ ] **Step 3 (U1):** Open dashboard → attach a warm session whose chat area contains markdown table output (`│ … │` rows) → with an empty input box press `←`. Pass = returns to dashboard, not trapped.
|
|
145
|
+
- [ ] **Step 4 (U2):** In the same or another reporter-active hosted session, type a draft → press `←`. Pass = cursor moves left inside the draft, no detach (same as #67 U3).
|
|
146
|
+
- [ ] **Step 5:** `git -C ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board checkout main` and restart pi to restore the running copy.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Evidence outputPreview Extraction 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 `evidence.json` `commands[].outputPreview` storing `[object Object]` for bash tool results by extracting text from pi's `AgentToolResult.content` blocks (issue #41).
|
|
6
|
+
|
|
7
|
+
**Architecture:** New pure function `toolResultText(result)` in `src/core/heuristics.mjs` (symmetric with existing `assistantText`); `reduceEvidence` bash branch swaps `String(event.result ?? "")` for it. No data-structure changes, no migration.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js ESM, `node:test`, JSDoc types checked by `tsc --noEmit`.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md` (acceptance IDs A1/A2/A3/U1 referenced below).
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- All edits under worktree `$WT=/home/elling/git-repo/github/pi-agent-board/.pi/worktrees/issue-41-evidence-outputpreview-object`; git via `git -C $WT` — never touch main checkout.
|
|
16
|
+
- `toolResultText` must stay a pure function with zero side effects; do NOT inline it into `reduceEvidence` (spec §3 hard constraint).
|
|
17
|
+
- Unknown-shaped objects must yield `""`, never `"[object Object]"` (spec D2, #40's 宁可不显示也不错显示 principle).
|
|
18
|
+
- Legacy string `result` must keep passing through unchanged (spec D5).
|
|
19
|
+
- Do NOT change the 500-char truncate, `EvidenceCommand` shape, other tools' branches, or powershell handling (spec §5 non-goals).
|
|
20
|
+
- Stage files individually with `git -C $WT add <file>`; never `git add -A`.
|
|
21
|
+
- Full gate per task end: `npm run verify` (typecheck + test + coverage + pack:dry). Never `npm test -- <file>` (glob expands to full suite anyway).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### Task 1: `toolResultText` pure function (acceptance: A1)
|
|
26
|
+
|
|
27
|
+
**Files:**
|
|
28
|
+
- Modify: `src/core/heuristics.mjs` (after `assistantText`, line ~26)
|
|
29
|
+
- Test: `test/heuristics.test.mjs`
|
|
30
|
+
|
|
31
|
+
**Interfaces:**
|
|
32
|
+
- Produces: `toolResultText(result: any): string` — exported from `src/core/heuristics.mjs`. Task 2 imports it in `src/core/evidence.mjs`.
|
|
33
|
+
- Behavior table (spec D2): `null/undefined → ""`; `string → passthrough`; object with `content` array → text blocks `join("\n")` trimmed; object without content/text → `""`; number/bool → `String()`.
|
|
34
|
+
|
|
35
|
+
- [ ] **Step 1: Write the failing tests**
|
|
36
|
+
|
|
37
|
+
Append to `test/heuristics.test.mjs`, and add `toolResultText` to the existing import block from `../src/core/heuristics.mjs`:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
test("toolResultText extracts text blocks from AgentToolResult", () => {
|
|
41
|
+
const result = {
|
|
42
|
+
content: [
|
|
43
|
+
{ type: "text", text: "line1" },
|
|
44
|
+
{ type: "image", data: "AAAA", mimeType: "image/png" },
|
|
45
|
+
{ type: "text", text: "line2" },
|
|
46
|
+
],
|
|
47
|
+
details: { fullOutputPath: "/tmp/pi-bash-x.log" },
|
|
48
|
+
};
|
|
49
|
+
assert.equal(toolResultText(result), "line1\nline2");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("toolResultText returns empty string for image-only content", () => {
|
|
53
|
+
assert.equal(toolResultText({ content: [{ type: "image", data: "AAAA", mimeType: "image/png" }] }), "");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("toolResultText returns empty string for object without content array", () => {
|
|
57
|
+
assert.equal(toolResultText({ details: {} }), "");
|
|
58
|
+
assert.equal(toolResultText({}), "");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("toolResultText passes through plain strings (legacy events)", () => {
|
|
62
|
+
assert.equal(toolResultText("ok"), "ok");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("toolResultText handles nullish and scalar inputs", () => {
|
|
66
|
+
assert.equal(toolResultText(null), "");
|
|
67
|
+
assert.equal(toolResultText(undefined), "");
|
|
68
|
+
assert.equal(toolResultText(42), "42");
|
|
69
|
+
assert.equal(toolResultText(true), "true");
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
74
|
+
|
|
75
|
+
Run: `cd $WT && node --test test/heuristics.test.mjs`
|
|
76
|
+
Expected: FAIL — `toolResultText is not defined` / import error.
|
|
77
|
+
|
|
78
|
+
- [ ] **Step 3: Implement `toolResultText` in `src/core/heuristics.mjs`**
|
|
79
|
+
|
|
80
|
+
Insert after the existing `assistantText` function:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
/**
|
|
84
|
+
* Extract text from a pi AgentToolResult object
|
|
85
|
+
* ({ content: [{ type: "text", text }, ...], details, ... }), or, defensively,
|
|
86
|
+
* a plain string. Unknown-shaped objects yield "" — never "[object Object]"
|
|
87
|
+
* (issue #41; mirrors pi's own convertToolResultOutput join("\n") semantics).
|
|
88
|
+
* @param {any} result
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
export function toolResultText(result) {
|
|
92
|
+
if (result == null) return "";
|
|
93
|
+
if (typeof result === "string") return result;
|
|
94
|
+
if (typeof result !== "object") return String(result);
|
|
95
|
+
const content = result.content;
|
|
96
|
+
if (!Array.isArray(content)) return "";
|
|
97
|
+
return content
|
|
98
|
+
.filter((b) => b && b.type === "text" && typeof b.text === "string")
|
|
99
|
+
.map((b) => b.text)
|
|
100
|
+
.join("\n")
|
|
101
|
+
.trim();
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
106
|
+
|
|
107
|
+
Run: `cd $WT && node --test test/heuristics.test.mjs`
|
|
108
|
+
Expected: PASS (all 5 new tests + pre-existing suite).
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 5: Commit**
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git -C $WT add test/heuristics.test.mjs src/core/heuristics.mjs
|
|
114
|
+
git -C $WT commit -m "feat: add toolResultText extractor for AgentToolResult content (issue #41)"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Task 2: `reduceEvidence` bash branch uses `toolResultText` (acceptance: A2, A3)
|
|
120
|
+
|
|
121
|
+
**Files:**
|
|
122
|
+
- Modify: `src/core/evidence.mjs` (import line 2; bash branch line ~196)
|
|
123
|
+
- Test: `test/evidence.test.mjs`
|
|
124
|
+
|
|
125
|
+
**Interfaces:**
|
|
126
|
+
- Consumes: `toolResultText(result: any): string` from Task 1.
|
|
127
|
+
- Produces: unchanged `EvidenceCommand` shape; `outputPreview` now carries extracted text.
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 1: Write the failing test (real AgentToolResult shape)**
|
|
130
|
+
|
|
131
|
+
In `test/evidence.test.mjs`, inside the existing top-level test that builds `snap` (after the current `b1` end event at line ~24), append:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
reduceEvidence(snap, { type: "tool_execution_end", toolCallId: "b2", toolName: "bash", isError: false, result: { content: [{ type: "text", text: "https://github.com/zhuxixi/pi-agent-board/pull/45" }] } }, 31);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
and alongside the existing assertions:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
assert.equal(snap.commands.find((c) => c.id === "b2").outputPreview, "https://github.com/zhuxixi/pi-agent-board/pull/45");
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
(The pre-existing `b1` fixture with `result: "ok"` stays untouched — its `outputPreview` passthrough is the D5 regression guard.)
|
|
144
|
+
|
|
145
|
+
**Assertion ripple:** the new `b2` command raises the command count. In the same test, update the tail assertion:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
assert.equal(summary.commandCount, 2);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
(was `assert.equal(summary.commandCount, 1);`)
|
|
152
|
+
|
|
153
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
154
|
+
|
|
155
|
+
Run: `cd $WT && node --test test/evidence.test.mjs`
|
|
156
|
+
Expected: FAIL — `outputPreview` is `"[object Object]"` instead of the URL.
|
|
157
|
+
|
|
158
|
+
- [ ] **Step 3: Modify `src/core/evidence.mjs`**
|
|
159
|
+
|
|
160
|
+
Import line (currently):
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
import { assistantText, classifyCommand, toolFileOperation, truncate } from "./heuristics.mjs";
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
becomes:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
import { assistantText, classifyCommand, toolFileOperation, toolResultText, truncate } from "./heuristics.mjs";
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
In `reduceEvidence`, `tool_execution_end` → `name === "bash"` branch, replace exactly this line:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
outputPreview: truncate(String(event.result ?? ""), 500),
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
with:
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
outputPreview: truncate(toolResultText(event.result), 500),
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
185
|
+
|
|
186
|
+
Run: `cd $WT && node --test test/evidence.test.mjs`
|
|
187
|
+
Expected: PASS — new assertion green AND legacy `"ok"` fixture still green (D5).
|
|
188
|
+
|
|
189
|
+
- [ ] **Step 5: Full verify gate**
|
|
190
|
+
|
|
191
|
+
Run: `cd $WT && npm run verify`
|
|
192
|
+
Expected: PASS — typecheck, full test suite, coverage, pack:dry all green.
|
|
193
|
+
|
|
194
|
+
- [ ] **Step 6: Commit**
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git -C $WT add src/core/evidence.mjs test/evidence.test.mjs
|
|
198
|
+
git -C $WT commit -m "fix: extract evidence outputPreview from AgentToolResult content (issue #41)"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Task 3: Post-merge manual verification (acceptance: U1, user-verified)
|
|
204
|
+
|
|
205
|
+
**Trigger:** after PR merge + board reloads the new extension on next pi session start.
|
|
206
|
+
|
|
207
|
+
- [ ] **Step 1:** In a real pi session, run a few bash commands (e.g. `gh pr create`, `git log`).
|
|
208
|
+
- [ ] **Step 2:** Inspect `~/.pi/agent/agent-board/views/<view>/evidence.json`.
|
|
209
|
+
- [ ] **Step 3:** Pass criteria: newly recorded bash commands show real output text in `outputPreview` (e.g. the `gh pr create` URL); no new `[object Object]` entries. Record observation result back on issue #41.
|