pi-thread-engine 0.2.4 β 0.4.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/README.md +168 -61
- package/docs/THREADS.md +116 -0
- package/extensions/index.ts +32 -6
- package/package.json +7 -3
- package/src/core/executor.ts +20 -0
- package/src/core/types.ts +1 -1
- package/src/dashboard.ts +238 -63
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
# π§΅ pi-
|
|
1
|
+
# π§΅ pi-thread-engine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> *"When you start thinking in threads, you stop being a bottleneck and become an orchestrator."*
|
|
4
|
+
> β @IndyDevDan, [Thinking in Threads](https://agenticengineer.com/thinking-in-threads)
|
|
4
5
|
|
|
5
|
-
**
|
|
6
|
+
**Thread-Based Engineering** for [Pi Coding Agent](https://github.com/badlogic/pi-mono).
|
|
7
|
+
7 thread types + stories + fusion + zero-touch + full TUI dashboard.
|
|
8
|
+
Built on @IndyDevDan's framework from [agenticengineer.com](https://agenticengineer.com).
|
|
6
9
|
|
|
7
10
|
## Install
|
|
8
11
|
|
|
@@ -10,99 +13,203 @@ Thread engineering for [pi](https://github.com/badlogic/pi-mono) β all 7 threa
|
|
|
10
13
|
pi install npm:pi-thread-engine
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
## The Thread Framework
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
Everything starts with the base thread:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
PROMPT β TOOL CALLS β REVIEW
|
|
22
|
+
You Agent You
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You show up at two nodes for maximum leverage. Your agent handles everything in between.
|
|
26
|
+
|
|
27
|
+
Then you multiply:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Thread Types
|
|
31
|
+
βββ Base β You β Agent β You (minutes)
|
|
32
|
+
βββ P β Parallel 5+ agents (hours)
|
|
33
|
+
βββ C β Checkpoint chains (high-stakes)
|
|
34
|
+
βββ F β Fusion 9 agents, pick winner
|
|
35
|
+
βββ B β Branch (orchestrator agents)
|
|
36
|
+
βββ L β Long-running 26hrs (days)
|
|
37
|
+
βββ Z β Zero-touch, no review (ultimate)
|
|
17
38
|
```
|
|
18
39
|
|
|
19
40
|
## Commands
|
|
20
41
|
|
|
21
|
-
| Command | Type |
|
|
22
|
-
|
|
23
|
-
| `/
|
|
24
|
-
| `/
|
|
25
|
-
| `/
|
|
26
|
-
| `/
|
|
27
|
-
| `/
|
|
28
|
-
| `/
|
|
29
|
-
| `/
|
|
42
|
+
| Command | Type | What |
|
|
43
|
+
|---------|------|------|
|
|
44
|
+
| `/threads` | Dashboard | TUI: 3-column status view, search, inline reply, keyboard nav |
|
|
45
|
+
| `/pthread` | P-Thread | N independent tasks in parallel |
|
|
46
|
+
| `/cthread` | C-Thread | Sequential phases with checkpoint reviews |
|
|
47
|
+
| `/bthread` | B-Thread | scout β plan β build β review pipeline |
|
|
48
|
+
| `/fthread` | F-Thread | Same prompt β N models β compare, pick winner |
|
|
49
|
+
| `/zthread` | Z-Thread | Autonomous + verification gate (ships only if tests pass) |
|
|
50
|
+
| `/lthread` | L-Thread | Extended autonomous run |
|
|
51
|
+
| `/story` | Stories | Auto-decompose goal into thread phases |
|
|
52
|
+
| `/agents` | Alias | Same as `/threads` β Claude muscle memory |
|
|
30
53
|
|
|
31
|
-
##
|
|
54
|
+
## Dashboard
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
Same prompt sent to multiple models in parallel. Compare results, pick the best.
|
|
56
|
+
Run `/threads` (or `/agents`) to open the TUI dashboard:
|
|
35
57
|
|
|
36
58
|
```
|
|
59
|
+
π§΅ Thread Dashboard
|
|
60
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
61
|
+
|
|
62
|
+
β‘ Needs Input (2)
|
|
63
|
+
βΈ t-001 ββββββββββ needs_input /fix bug in auth β "password has special chars"
|
|
64
|
+
βΈ t-003 βββββββββ needs_input /audit dependencies β "found 3 vulnerable"
|
|
65
|
+
|
|
66
|
+
β Working (3)
|
|
67
|
+
βΈ t-002 ββββββββββ running /migrate database β± 4m 23s
|
|
68
|
+
βΈ t-004 ββββββββββ running /build API endpoints β± 2m 11s
|
|
69
|
+
βΈ s-001 βscoutβplanβbuild running /story add payments
|
|
70
|
+
|
|
71
|
+
β Done (4)
|
|
72
|
+
βΈ t-005 ββββββββββ success /refactor auth module β "cleaned up 12 files"
|
|
73
|
+
βΈ t-006 ββββββββββ success /update docs β "30 pages updated"
|
|
74
|
+
βΈ t-007 ββββββββββ error /deploy to prod β "missing env vars"
|
|
75
|
+
|
|
76
|
+
nav=ββ exp=Enter rep=i srch=/ kill=k rev=r prune=p quit=q
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Keyboard shortcuts:**
|
|
80
|
+
- `ββ` β navigate threads
|
|
81
|
+
- `Enter` β expand/collapse thread details
|
|
82
|
+
- `i` β inline reply (send message to blocked thread)
|
|
83
|
+
- `/` β search/filter threads
|
|
84
|
+
- `k` β kill selected thread
|
|
85
|
+
- `r` β review selected thread results
|
|
86
|
+
- `p` β prune (clear) finished threads
|
|
87
|
+
- `q` β quit dashboard
|
|
88
|
+
|
|
89
|
+
## Thread Types in Depth
|
|
90
|
+
|
|
91
|
+
### P-Thread β Parallel
|
|
92
|
+
Five in the terminal. Five to ten in the browser. Zero apologies.
|
|
93
|
+
β *Boris Cherny, creator of Claude Code*
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
/pthread "Review all PRs" --count 8
|
|
97
|
+
/pthread "Fix bug in auth" --count 3 --each
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### C-Thread β Checkpoint Chains
|
|
101
|
+
High-stakes work needs verification at critical junctures.
|
|
102
|
+
Intentional chunking is a feature, not a bug.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
/cthread "Migrate to new database" --phases scout,plan,build,test,deploy
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### F-Thread β Fusion (pi-thread-engine unique)
|
|
109
|
+
Nine agents. Nine parallel futures. Pick the winner.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
37
112
|
/fthread "Design the caching architecture" --count 5
|
|
38
|
-
/fthread "Refactor the auth module" --models
|
|
113
|
+
/fthread "Refactor the auth module" --models claude,gpt-4o,gemini-pro
|
|
39
114
|
```
|
|
40
115
|
|
|
41
|
-
|
|
42
|
-
|
|
116
|
+
Best-of-N gives you confidence. Cherry-picking gives you quality.
|
|
117
|
+
Each agent runs in complete isolation β no cross-contamination.
|
|
118
|
+
|
|
119
|
+
### B-Thread β Branch/Meta-Agentic
|
|
120
|
+
When agents manage agents. You become an Orchestrator of Intelligence.
|
|
43
121
|
|
|
122
|
+
```bash
|
|
123
|
+
/bthread "Build the checkout flow"
|
|
44
124
|
```
|
|
45
|
-
|
|
46
|
-
|
|
125
|
+
|
|
126
|
+
Auto-runs: plan agent β scout β build β review β deploy
|
|
127
|
+
|
|
128
|
+
### L-Thread β Long-Running
|
|
129
|
+
A single prompt that ran 26 hours.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
/lthread "Audit and fix all security vulnerabilities" --hours 24
|
|
47
133
|
```
|
|
48
134
|
|
|
49
|
-
###
|
|
50
|
-
|
|
135
|
+
### Z-Thread β Zero-Touch (pi-thread-engine unique)
|
|
136
|
+
Maximum earned trust. No review node. Ships only if verification passes.
|
|
51
137
|
|
|
138
|
+
```bash
|
|
139
|
+
/zthread "Fix all ESLint warnings" --verify "npm run lint"
|
|
140
|
+
/zthread "Add dark mode" --verify "npm test && npm run build"
|
|
52
141
|
```
|
|
142
|
+
|
|
143
|
+
This is NOT vibe coding. Z-threads are the opposite:
|
|
144
|
+
maximum earned trust through hundreds of iterations.
|
|
145
|
+
|
|
146
|
+
### Stories β Goal Decomposition (pi-thread-engine unique)
|
|
147
|
+
Auto-decompose a goal into thread phases. pi-thread-engine picks the right type for each.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
53
150
|
/story "Add dark mode to the dashboard" --verify "npm test"
|
|
54
151
|
```
|
|
55
152
|
|
|
56
153
|
Auto-generates phases:
|
|
57
154
|
1. **Scout** (meta) β research the codebase
|
|
58
|
-
2. **Plan** (fusion) β 3 models brainstorm approaches
|
|
59
|
-
3. **Decide** (
|
|
155
|
+
2. **Plan** (fusion) β 3 models brainstorm approaches
|
|
156
|
+
3. **Decide** (checkpoint) β human picks the winner
|
|
60
157
|
4. **Build** (parallel) β implement across files
|
|
61
158
|
5. **Verify** (zero) β run tests
|
|
62
159
|
|
|
63
|
-
##
|
|
160
|
+
## LLM Tools
|
|
64
161
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
162
|
+
Use these inside any Pi prompt:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
thread_spawn({ type: "parallel", prompts: [...] }) // Start any thread type
|
|
166
|
+
thread_status({ id: "t-001" }) // Check progress
|
|
167
|
+
thread_kill({ id: "t-001" }) // Stop a thread
|
|
71
168
|
```
|
|
72
169
|
|
|
73
|
-
##
|
|
170
|
+
## The Core Four
|
|
74
171
|
|
|
75
|
-
|
|
76
|
-
- `thread_status` β Check progress of threads and stories
|
|
77
|
-
- `thread_kill` β Stop a thread
|
|
172
|
+
Every thread runs on these four pillars. Invest in all of them:
|
|
78
173
|
|
|
79
|
-
|
|
174
|
+
| Pillar | Description | pi-thread-engine |
|
|
175
|
+
|--------|-------------|-----------------|
|
|
176
|
+
| **Context** | Right information (no more, no less) | `~/.pi-memory/` integration |
|
|
177
|
+
| **Model** | Capable of sustained reasoning | 324+ models via OpenRouter |
|
|
178
|
+
| **Prompt** | Crystal-clear agentic engineering | Structured commands |
|
|
179
|
+
| **Tools** | Self-verifying, comprehensive | 40+ pi-skills |
|
|
80
180
|
|
|
81
|
-
|
|
82
|
-
Wrapper layer (pi-subagents): Native layer (pi -p):
|
|
83
|
-
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
|
|
84
|
-
β /pthread β /parallel β β /fthread β N models β
|
|
85
|
-
β /cthread β /chain β β /zthread β run+verify β
|
|
86
|
-
β /bthread β scout chain β β /lthread β long run β
|
|
87
|
-
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
|
|
88
|
-
β β
|
|
89
|
-
βββββββββββββββββββββββββββββββββββ
|
|
90
|
-
β ThreadRegistry (state machine) β
|
|
91
|
-
β ThreadExecutor (dispatch) β
|
|
92
|
-
β /threads dashboard β
|
|
93
|
-
β /story orchestrator β
|
|
94
|
-
βββββββββββββββββββββββββββββββββββ
|
|
95
|
-
```
|
|
181
|
+
## Measuring Progress
|
|
96
182
|
|
|
97
|
-
|
|
183
|
+
You know you're improving at agentic coding when:
|
|
184
|
+
|
|
185
|
+
- **Width** β β P-threads = 5x exploration per hour
|
|
186
|
+
- **Time** β β L-threads = 10min β 8hrs autonomous work
|
|
187
|
+
- **Depth** β β B-threads = one prompt β entire teams
|
|
188
|
+
- **Checkpoints** β β Earned trust through evidence
|
|
98
189
|
|
|
99
|
-
|
|
190
|
+
> *The common denominator: increase tool calls per unit of your attention.*
|
|
100
191
|
|
|
101
|
-
|
|
102
|
-
> The metric: **tool calls per unit of your attention**. Maximize this.
|
|
192
|
+
## Architecture
|
|
103
193
|
|
|
104
194
|
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
195
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
196
|
+
β pi-thread-engine β
|
|
197
|
+
βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββββ€
|
|
198
|
+
β Dashboard β Registry β Executor β
|
|
199
|
+
β (TUI v3) β (state) β (dispatch) β
|
|
200
|
+
βββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββββ€
|
|
201
|
+
β Commands: /threads /pthread /cthread /fthread β
|
|
202
|
+
β /bthread /zthread /lthread /story β
|
|
203
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
|
204
|
+
β Backends β
|
|
205
|
+
β ββ pi-subagents (P/C/B threads) β
|
|
206
|
+
β ββ native (F/Z/L threads, Stories) β
|
|
207
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
108
208
|
```
|
|
209
|
+
|
|
210
|
+
## Further Reading
|
|
211
|
+
|
|
212
|
+
- [Thinking in Threads](https://agenticengineer.com/thinking-in-threads) β @IndyDevDan
|
|
213
|
+
- [The Only Claude Code Competitor](https://agenticengineer.com/the-only-claude-code-competitor)
|
|
214
|
+
- [Compute Advantage Equation](https://agenticengineer.com/compute-advantage-equation)
|
|
215
|
+
- [Engineering with Exponentials](https://agenticengineer.com/state-of-ai-coding/engineering-with-exponentials)
|
package/docs/THREADS.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Thread-Based Engineering
|
|
2
|
+
|
|
3
|
+
> *"When you start thinking in threads, you stop being a bottleneck and become an orchestrator."*
|
|
4
|
+
> β @IndyDevDan, [Thinking in Threads](https://agenticengineer.com/thinking-in-threads)
|
|
5
|
+
|
|
6
|
+
This is the mental framework for agentic coding. Adapted from @IndyDevDan's article
|
|
7
|
+
by @IndyDevDan, co-authored with Claude Opus 4.5.
|
|
8
|
+
|
|
9
|
+
## The Core Four
|
|
10
|
+
|
|
11
|
+
Every thread runs on these four pillars:
|
|
12
|
+
|
|
13
|
+
| Pillar | Description | pi-thread-engine |
|
|
14
|
+
|--------|-------------|-----------------|
|
|
15
|
+
| **Context** | Right information (no more, no less) | pi-memory integration |
|
|
16
|
+
| **Model** | Capable of sustained reasoning | Any of 324+ models |
|
|
17
|
+
| **Prompt** | Crystal-clear agentic engineering | Structured thread commands |
|
|
18
|
+
| **Tools** | Self-verifying, comprehensive | pi-skills ecosystem |
|
|
19
|
+
|
|
20
|
+
## The Thread Taxonomy
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Thread Types
|
|
24
|
+
βββ Base β You β Agent β You (minutes)
|
|
25
|
+
βββ P β Parallel 5+ agents (hours)
|
|
26
|
+
βββ C β Checkpoint chains (high-stakes)
|
|
27
|
+
βββ F β Fusion 9 agents, pick winner
|
|
28
|
+
βββ B β Branch (orchestrator agents)
|
|
29
|
+
βββ L β Long-running 26hrs (days)
|
|
30
|
+
βββ Z β Zero-touch, no review (ultimate)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Base Thread
|
|
34
|
+
The atomic unit. Every prompt follows this shape:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
PROMPT β TOOL CALLS β REVIEW
|
|
38
|
+
You Agent You
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You show up at two nodes for maximum leverage: Prompt and Review.
|
|
42
|
+
The middle is your agent executing tool calls.
|
|
43
|
+
|
|
44
|
+
### P-Thread (Parallel)
|
|
45
|
+
Five in the terminal. Five to ten in the browser. Zero apologies.
|
|
46
|
+
β *Boris Cherny, creator of Claude Code*
|
|
47
|
+
|
|
48
|
+
- In-loop: Active collaboration where you guide and redirect
|
|
49
|
+
- Out-of-loop: Fire-and-forget tasks that complete while you focus elsewhere
|
|
50
|
+
- Unlock: code review at scale, rapid exploration, verification through redundancy
|
|
51
|
+
|
|
52
|
+
### C-Thread (Checkpoint/Chained)
|
|
53
|
+
High-stakes work needs verification at critical junctures.
|
|
54
|
+
|
|
55
|
+
- **Context constraints**: work exceeds context window
|
|
56
|
+
- **Production stakes**: human verification prevents catastrophe
|
|
57
|
+
|
|
58
|
+
> *Intentional chunking is a feature, not a bug.*
|
|
59
|
+
|
|
60
|
+
### F-Thread (Fusion)
|
|
61
|
+
Nine agents. Nine parallel futures. Pick the winner.
|
|
62
|
+
|
|
63
|
+
- 3 Claude Code + 3 Gemini + 3 Codex all tackling the same problem
|
|
64
|
+
- Best-of-N gives confidence. Cherry-picking gives quality.
|
|
65
|
+
- Agent sandboxes: complete isolation, no cross-contamination
|
|
66
|
+
|
|
67
|
+
> *"The future of rapid prototyping will be done with fusion threads."*
|
|
68
|
+
|
|
69
|
+
### B-Thread (Branch/Meta-Agentic)
|
|
70
|
+
When agents manage agents. You become an Orchestrator of Intelligence.
|
|
71
|
+
|
|
72
|
+
- Simplest: one agent spawns another for a specific task
|
|
73
|
+
- Full orchestration: plan agent + scout + build + review + deploy
|
|
74
|
+
- Deterministic code orchestrating non-deterministic intelligence
|
|
75
|
+
|
|
76
|
+
> *"This is meta-programming for the agentic age."*
|
|
77
|
+
|
|
78
|
+
### L-Thread (Long-Running)
|
|
79
|
+
A single prompt that ran 26 hours.
|
|
80
|
+
|
|
81
|
+
- Prerequisites are demanding: Core Four must all be exceptional
|
|
82
|
+
- Top agentic engineers kick off a prompt and go to sleep
|
|
83
|
+
- 100x duration = 100x decisions your agent makes without you
|
|
84
|
+
|
|
85
|
+
> *"Living software that works for us while we sleep."*
|
|
86
|
+
|
|
87
|
+
### Z-Thread (Zero-Touch)
|
|
88
|
+
The hidden level beyond review. Maximum earned trust.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
PROMPT β AGENT β SHIP (no review node)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> *"It isn't that we don't look at the code. It's that we know we don't have to."*
|
|
95
|
+
|
|
96
|
+
This is NOT vibe coding. Z-threads are the opposite:
|
|
97
|
+
maximum earned trust through hundreds of iterations of context, prompt, and tools refinement.
|
|
98
|
+
|
|
99
|
+
## Measuring Progress
|
|
100
|
+
|
|
101
|
+
You measure improvement in threads:
|
|
102
|
+
- **Width** (more threads): P-threads = 5x exploration
|
|
103
|
+
- **Time** (longer threads): L-threads = 10min β 8hrs autonomous
|
|
104
|
+
- **Depth** (thicker threads): B-threads = one prompt β entire teams
|
|
105
|
+
- **Attention** (fewer checkpoints): Earn trust through evidence
|
|
106
|
+
|
|
107
|
+
> *"The common denominator: increase tool calls per unit of your attention."*
|
|
108
|
+
|
|
109
|
+
Stack these multipliers. Track them weekly. Watch them trend upward.
|
|
110
|
+
|
|
111
|
+
## Further Reading
|
|
112
|
+
|
|
113
|
+
- [Thinking in Threads](https://agenticengineer.com/thinking-in-threads) β @IndyDevDan
|
|
114
|
+
- [The Only Claude Code Competitor](https://agenticengineer.com/the-only-claude-code-competitor)
|
|
115
|
+
- [Compute Advantage Equation](https://agenticengineer.com/compute-advantage-equation)
|
|
116
|
+
- [Engineering with Exponentials](https://agenticengineer.com/state-of-ai-coding/engineering-with-exponentials)
|
package/extensions/index.ts
CHANGED
|
@@ -178,6 +178,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
// Default: open interactive TUI dashboard
|
|
181
|
+
// ββ /agents alias (Claude-style muscle memory) ββββββββββββββ
|
|
182
|
+
pi.registerCommand("agents", {
|
|
183
|
+
description: "Alias for /threads β Claude-style Agent View dashboard",
|
|
184
|
+
handler: async (a, c) => {
|
|
185
|
+
// Forward to /threads
|
|
186
|
+
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
187
|
+
const dashboard = createDashboard(
|
|
188
|
+
registry,
|
|
189
|
+
theme,
|
|
190
|
+
() => done(),
|
|
191
|
+
(id) => { registry.kill(id); ctx.ui.notify(`Killed ${id}`, "warning"); tui.requestRender(); },
|
|
192
|
+
(id) => { const t = registry.get(id); if (t) { ctx.ui.notify(`Thread ${id}: ${t.tasks.map((tk) => `${tk.id}: ${tk.result?.slice(0,100) ?? tk.error ?? "(pending)"}`).join("\n")}`, "info"); } },
|
|
193
|
+
(id, message) => { executor.injectReply(id, message); ctx.ui.notify(`Replied to ${id}: ${message.slice(0, 50)}...`, "info"); tui.requestRender(); }
|
|
194
|
+
);
|
|
195
|
+
return { render: (w: number) => dashboard.render(w), invalidate: () => dashboard.invalidate(), handleInput: (data: string) => { dashboard.handleInput(data); tui.requestRender(); } };
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
// ββ End /agents alias βββββββββββββββββββββββββββββββββββββββ
|
|
200
|
+
|
|
181
201
|
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
182
202
|
const dashboard = createDashboard(
|
|
183
203
|
registry,
|
|
@@ -194,6 +214,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
194
214
|
const preview = t.tasks.map((tk) => `${tk.id}: ${tk.result?.slice(0, 100) ?? tk.error ?? "(pending)"}`).join("\n");
|
|
195
215
|
ctx.ui.notify(`Thread ${id} results:\n${preview}`, "info");
|
|
196
216
|
}
|
|
217
|
+
},
|
|
218
|
+
(id, message) => {
|
|
219
|
+
// Inline reply β send message to blocked thread
|
|
220
|
+
executor.injectReply(id, message);
|
|
221
|
+
ctx.ui.notify(`Replied to ${id}: ${message.slice(0, 50)}...`, "info");
|
|
222
|
+
tui.requestRender();
|
|
197
223
|
}
|
|
198
224
|
);
|
|
199
225
|
|
|
@@ -542,7 +568,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
542
568
|
parameters: Type.Object({
|
|
543
569
|
id: Type.Optional(Type.String({ description: "Thread ID (t-001) or Story ID (s-001). Omit for all." })),
|
|
544
570
|
}),
|
|
545
|
-
async execute(_toolCallId, params) {
|
|
571
|
+
async execute(_toolCallId, params, _signal?: any, _onUpdate?: any, _ctx?: any) {
|
|
546
572
|
if (params.id) {
|
|
547
573
|
// Check threads first
|
|
548
574
|
const t = registry.get(params.id);
|
|
@@ -579,7 +605,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
579
605
|
};
|
|
580
606
|
}
|
|
581
607
|
|
|
582
|
-
return { content: [{ type: "text", text: `ID ${params.id} not found` }], isError: true };
|
|
608
|
+
return { content: [{ type: "text", text: `ID ${params.id} not found` }], isError: true } as any;
|
|
583
609
|
}
|
|
584
610
|
|
|
585
611
|
// All
|
|
@@ -604,10 +630,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
604
630
|
}
|
|
605
631
|
|
|
606
632
|
if (lines.length === 0) {
|
|
607
|
-
return { content: [{ type: "text", text: "No threads or stories." }] };
|
|
633
|
+
return { content: [{ type: "text", text: "No threads or stories." }] } as any;
|
|
608
634
|
}
|
|
609
635
|
|
|
610
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
636
|
+
return { content: [{ type: "text", text: lines.join("\n") }] } as any;
|
|
611
637
|
},
|
|
612
638
|
});
|
|
613
639
|
|
|
@@ -618,11 +644,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
618
644
|
parameters: Type.Object({
|
|
619
645
|
id: Type.String({ description: "Thread ID to kill" }),
|
|
620
646
|
}),
|
|
621
|
-
async execute(_toolCallId, params) {
|
|
647
|
+
async execute(_toolCallId, params, _signal?: any, _onUpdate?: any, _ctx?: any) {
|
|
622
648
|
const t = registry.get(params.id);
|
|
623
649
|
if (!t) return { content: [{ type: "text", text: `Thread ${params.id} not found` }], isError: true };
|
|
624
650
|
registry.kill(params.id);
|
|
625
|
-
return { content: [{ type: "text", text: `Thread ${params.id} killed.` }] };
|
|
651
|
+
return { content: [{ type: "text", text: `Thread ${params.id} killed.` }] } as any;
|
|
626
652
|
},
|
|
627
653
|
});
|
|
628
654
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-thread-engine",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Thread
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Thread-Based Engineering for pi β all 7 thread types + stories + fusion + zero-touch + TUI dashboard. Based on @IndyDevDan framework from agenticengineer.com.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "artale",
|
|
@@ -43,12 +43,16 @@
|
|
|
43
43
|
"extensions/",
|
|
44
44
|
"src/",
|
|
45
45
|
"README.md",
|
|
46
|
-
"PLAN.md"
|
|
46
|
+
"PLAN.md",
|
|
47
|
+
"docs/"
|
|
47
48
|
],
|
|
48
49
|
"peerDependencies": {
|
|
49
50
|
"@mariozechner/pi-ai": "^0.56.0",
|
|
50
51
|
"@mariozechner/pi-coding-agent": "^0.56.0",
|
|
51
52
|
"@mariozechner/pi-tui": "^0.56.0",
|
|
52
53
|
"@sinclair/typebox": "^0.34.48"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"typescript": "^6.0.3"
|
|
53
57
|
}
|
|
54
58
|
}
|
package/src/core/executor.ts
CHANGED
|
@@ -197,4 +197,24 @@ export class ThreadExecutor {
|
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
+
|
|
201
|
+
/** Inject a reply into a running thread β sends message to its session */
|
|
202
|
+
injectReply(threadId: string, message: string): void {
|
|
203
|
+
const thread = this.registry.get(threadId);
|
|
204
|
+
if (!thread) return;
|
|
205
|
+
|
|
206
|
+
// Mark the blocked task as no longer needing input
|
|
207
|
+
for (const task of thread.tasks) {
|
|
208
|
+
if (task.state === "needs_input") {
|
|
209
|
+
task.state = "running";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Forward the reply to the running subagent session via sendUserMessage
|
|
214
|
+
// The subagent will pick up the new context and continue
|
|
215
|
+
this.pi.sendUserMessage(
|
|
216
|
+
`[Thread ${threadId}] Reply to blocked thread: ${message}`,
|
|
217
|
+
{ deliverAs: "followUp" }
|
|
218
|
+
);
|
|
219
|
+
}
|
|
200
220
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export type ThreadType = "base" | "parallel" | "chained" | "fusion" | "meta" | "long" | "zero";
|
|
3
3
|
|
|
4
4
|
/** Thread lifecycle states */
|
|
5
|
-
export type ThreadState = "pending" | "running" | "paused" | "completed" | "failed" | "killed";
|
|
5
|
+
export type ThreadState = "pending" | "running" | "paused" | "completed" | "failed" | "killed" | "needs_input";
|
|
6
6
|
|
|
7
7
|
/** How a thread is executed */
|
|
8
8
|
export type ExecutionBackend = "subagent" | "native";
|
package/src/dashboard.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Keys: ββ navigate, Enter expand, k kill, r review, p prune, q/Esc close
|
|
2
|
+
* Thread Dashboard v2 β Agent View-style grouping + inline reply + search
|
|
3
|
+
* Groups: Needs Input | Working | Done
|
|
4
|
+
* Keys: ββ navigate, Enter expand, i reply, / search, k kill, p prune, q close
|
|
6
5
|
*/
|
|
7
6
|
import { matchesKey, Key, truncateToWidth } from "@mariozechner/pi-tui";
|
|
8
7
|
import type { ThreadRegistry } from "./core/registry.js";
|
|
@@ -12,11 +11,23 @@ export interface DashboardTheme {
|
|
|
12
11
|
bold: (text: string) => string;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
interface
|
|
14
|
+
interface Row {
|
|
16
15
|
id: string;
|
|
17
16
|
kind: "thread" | "story";
|
|
18
17
|
label: string;
|
|
19
|
-
|
|
18
|
+
state: string;
|
|
19
|
+
progress: string;
|
|
20
|
+
elapsed: string;
|
|
21
|
+
result: string;
|
|
22
|
+
error: string;
|
|
23
|
+
type: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Group {
|
|
27
|
+
name: string;
|
|
28
|
+
icon: string;
|
|
29
|
+
color: string;
|
|
30
|
+
rows: Row[];
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
export function createDashboard(
|
|
@@ -24,13 +35,21 @@ export function createDashboard(
|
|
|
24
35
|
theme: DashboardTheme,
|
|
25
36
|
onClose: () => void,
|
|
26
37
|
onKill?: (id: string) => void,
|
|
27
|
-
onReview?: (id: string) => void
|
|
38
|
+
onReview?: (id: string) => void,
|
|
39
|
+
onReply?: (id: string, message: string) => void
|
|
28
40
|
) {
|
|
29
41
|
let selected = 0;
|
|
30
42
|
let expanded: string | null = null;
|
|
31
|
-
let
|
|
43
|
+
let searchQuery = "";
|
|
44
|
+
let showSearch = false;
|
|
45
|
+
let replyTarget: string | null = null;
|
|
46
|
+
let replyBuffer = "";
|
|
47
|
+
let groups: Group[] = [];
|
|
32
48
|
let cachedWidth: number | undefined;
|
|
33
49
|
|
|
50
|
+
// Safety: cap at 100 rows to prevent terminal overflow
|
|
51
|
+
const MAX_ROWS = 100;
|
|
52
|
+
|
|
34
53
|
function stateIcon(state: string): string {
|
|
35
54
|
switch (state) {
|
|
36
55
|
case "running": return "β³";
|
|
@@ -41,6 +60,7 @@ export function createDashboard(
|
|
|
41
60
|
case "executing": return "β‘";
|
|
42
61
|
case "verifying": return "π";
|
|
43
62
|
case "done": return "β
";
|
|
63
|
+
case "needs_input": return "β ";
|
|
44
64
|
default: return "?";
|
|
45
65
|
}
|
|
46
66
|
}
|
|
@@ -50,6 +70,7 @@ export function createDashboard(
|
|
|
50
70
|
case "running": case "executing": return "warning";
|
|
51
71
|
case "completed": case "done": return "success";
|
|
52
72
|
case "failed": case "killed": return "error";
|
|
73
|
+
case "needs_input": return "warning";
|
|
53
74
|
default: return "muted";
|
|
54
75
|
}
|
|
55
76
|
}
|
|
@@ -66,25 +87,86 @@ export function createDashboard(
|
|
|
66
87
|
}
|
|
67
88
|
}
|
|
68
89
|
|
|
69
|
-
function
|
|
70
|
-
const
|
|
90
|
+
function buildGroups(): Group[] {
|
|
91
|
+
const allThreads = registry.all();
|
|
92
|
+
const allStories = registry.allStories();
|
|
71
93
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const display = `π ${theme.fg("accent", s.id)} [${theme.fg(stateColor(s.state), s.state)}] ${s.goal.slice(0, 45)} ${theme.fg("dim", phases)}`;
|
|
76
|
-
result.push({ id: s.id, kind: "story", label: s.goal, display });
|
|
77
|
-
}
|
|
94
|
+
const needsInput: Row[] = [];
|
|
95
|
+
const working: Row[] = [];
|
|
96
|
+
const done: Row[] = [];
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
for (const t of registry.all()) {
|
|
98
|
+
for (const t of allThreads) {
|
|
81
99
|
const sum = registry.summarize(t);
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
const row: Row = {
|
|
101
|
+
id: sum.id,
|
|
102
|
+
kind: "thread",
|
|
103
|
+
label: sum.label,
|
|
104
|
+
state: sum.state,
|
|
105
|
+
progress: sum.progress,
|
|
106
|
+
elapsed: sum.elapsed,
|
|
107
|
+
result: t.tasks.find(x => x.result)?.result?.slice(0, 80) ?? "",
|
|
108
|
+
error: t.tasks.find(x => x.error)?.error?.slice(0, 80) ?? "",
|
|
109
|
+
type: t.type ?? "",
|
|
110
|
+
};
|
|
111
|
+
if (sum.state === "needs_input") needsInput.push(row);
|
|
112
|
+
else if (["running", "pending", "executing", "verifying", "planning"].includes(sum.state as string)) working.push(row);
|
|
113
|
+
else done.push(row);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const s of allStories) {
|
|
117
|
+
const row: Row = {
|
|
118
|
+
id: s.id,
|
|
119
|
+
kind: "story",
|
|
120
|
+
label: s.goal,
|
|
121
|
+
state: s.state,
|
|
122
|
+
progress: "",
|
|
123
|
+
elapsed: "",
|
|
124
|
+
result: "",
|
|
125
|
+
error: "",
|
|
126
|
+
type: "story",
|
|
127
|
+
};
|
|
128
|
+
if ((s.state as string) === "done" || (s.state as string) === "failed" || (s.state as string) === "completed") done.push(row);
|
|
129
|
+
else working.push(row);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const result: Group[] = [];
|
|
133
|
+
if (needsInput.length > 0) result.push({ name: "Needs Input", icon: "β ", color: "warning", rows: needsInput });
|
|
134
|
+
if (working.length > 0) result.push({ name: "Working", icon: "β³", color: "warning", rows: working });
|
|
135
|
+
if (done.length > 0) result.push({ name: "Done", icon: "β", color: "success", rows: done });
|
|
136
|
+
|
|
137
|
+
if (searchQuery) {
|
|
138
|
+
const q = searchQuery.toLowerCase();
|
|
139
|
+
for (const g of result) {
|
|
140
|
+
g.rows = g.rows.filter(r => r.label.toLowerCase().includes(q) || r.id.toLowerCase().includes(q));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const g of result) {
|
|
145
|
+
if (g.rows.length > MAX_ROWS) g.rows.length = MAX_ROWS;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result.filter(g => g.rows.length > 0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function totalRows(): number {
|
|
152
|
+
let n = 0;
|
|
153
|
+
for (const g of groups) n += g.rows.length;
|
|
154
|
+
return n;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getSelected(): { group: number; row: number } | null {
|
|
158
|
+
let idx = 0;
|
|
159
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
160
|
+
for (let ri = 0; ri < groups[gi].rows.length; ri++) {
|
|
161
|
+
if (idx === selected) return { group: gi, row: ri };
|
|
162
|
+
idx++;
|
|
163
|
+
}
|
|
85
164
|
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
86
167
|
|
|
87
|
-
|
|
168
|
+
function ensureGroups() {
|
|
169
|
+
if (groups.length === 0) groups = buildGroups();
|
|
88
170
|
}
|
|
89
171
|
|
|
90
172
|
function renderExpanded(id: string, width: number): string[] {
|
|
@@ -92,7 +174,6 @@ export function createDashboard(
|
|
|
92
174
|
const indent = " ";
|
|
93
175
|
const maxW = width - 6;
|
|
94
176
|
|
|
95
|
-
// Thread detail
|
|
96
177
|
const t = registry.get(id);
|
|
97
178
|
if (t) {
|
|
98
179
|
lines.push(theme.fg("accent", theme.bold(` Thread ${t.id} (${t.type}) β ${t.state}`)));
|
|
@@ -113,7 +194,6 @@ export function createDashboard(
|
|
|
113
194
|
return lines;
|
|
114
195
|
}
|
|
115
196
|
|
|
116
|
-
// Story detail
|
|
117
197
|
const s = registry.getStory(id);
|
|
118
198
|
if (s) {
|
|
119
199
|
lines.push(theme.fg("accent", theme.bold(` Story ${s.id} β ${s.state}`)));
|
|
@@ -132,82 +212,177 @@ export function createDashboard(
|
|
|
132
212
|
return [theme.fg("error", ` ${id} not found`)];
|
|
133
213
|
}
|
|
134
214
|
|
|
215
|
+
function tw(s: string, w: number): string {
|
|
216
|
+
return truncateToWidth(s, Math.max(1, w));
|
|
217
|
+
}
|
|
218
|
+
|
|
135
219
|
const component = {
|
|
136
220
|
handleInput(data: string) {
|
|
221
|
+
if (replyTarget !== null) {
|
|
222
|
+
if (matchesKey(data, Key.enter)) {
|
|
223
|
+
onReply?.(replyTarget, replyBuffer);
|
|
224
|
+
replyTarget = null;
|
|
225
|
+
replyBuffer = "";
|
|
226
|
+
cachedWidth = undefined;
|
|
227
|
+
} else if (matchesKey(data, Key.escape)) {
|
|
228
|
+
replyTarget = null;
|
|
229
|
+
replyBuffer = "";
|
|
230
|
+
cachedWidth = undefined;
|
|
231
|
+
} else if (data.length === 1 && !data.startsWith("\x1b") && data !== "[" && data !== "o") {
|
|
232
|
+
replyBuffer += data;
|
|
233
|
+
cachedWidth = undefined;
|
|
234
|
+
} else if (data === "Backspace" || data === "\x7f") {
|
|
235
|
+
replyBuffer = replyBuffer.slice(0, -1);
|
|
236
|
+
cachedWidth = undefined;
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
137
241
|
if (matchesKey(data, Key.escape) || data === "q") {
|
|
138
242
|
onClose();
|
|
139
243
|
return;
|
|
140
244
|
}
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
245
|
+
if (data === "/") {
|
|
246
|
+
showSearch = !showSearch;
|
|
247
|
+
if (!showSearch) { searchQuery = ""; }
|
|
144
248
|
cachedWidth = undefined;
|
|
249
|
+
return;
|
|
145
250
|
}
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
expanded = null;
|
|
251
|
+
if (showSearch && data.length === 1) {
|
|
252
|
+
searchQuery += data;
|
|
149
253
|
cachedWidth = undefined;
|
|
254
|
+
return;
|
|
150
255
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
256
|
+
if (showSearch && (data === "Backspace" || data === "\x7f")) {
|
|
257
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
153
258
|
cachedWidth = undefined;
|
|
259
|
+
return;
|
|
154
260
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
261
|
+
|
|
262
|
+
const total = totalRows();
|
|
263
|
+
if (matchesKey(data, Key.up) && selected > 0) { selected--; ensureGroups(); }
|
|
264
|
+
if (matchesKey(data, Key.down) && selected < total - 1) { selected++; ensureGroups(); }
|
|
265
|
+
|
|
266
|
+
if (data === "i") {
|
|
267
|
+
const sel = getSelected();
|
|
268
|
+
if (sel) {
|
|
269
|
+
replyTarget = groups[sel.group].rows[sel.row].id;
|
|
270
|
+
replyBuffer = "";
|
|
271
|
+
cachedWidth = undefined;
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
158
274
|
}
|
|
159
|
-
if (data
|
|
160
|
-
|
|
161
|
-
|
|
275
|
+
if (matchesKey(data, Key.enter)) {
|
|
276
|
+
const sel = getSelected();
|
|
277
|
+
if (sel) {
|
|
278
|
+
const id = groups[sel.group].rows[sel.row].id;
|
|
279
|
+
expanded = expanded === id ? null : id;
|
|
280
|
+
cachedWidth = undefined;
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
162
283
|
}
|
|
163
|
-
if (data === "
|
|
164
|
-
|
|
165
|
-
cachedWidth = undefined;
|
|
284
|
+
if (data === "k") {
|
|
285
|
+
const sel = getSelected();
|
|
286
|
+
if (sel) { onKill?.(groups[sel.group].rows[sel.row].id); cachedWidth = undefined; }
|
|
287
|
+
return;
|
|
166
288
|
}
|
|
289
|
+
if (data === "r") {
|
|
290
|
+
const sel = getSelected();
|
|
291
|
+
if (sel) { onReview?.(groups[sel.group].rows[sel.row].id); cachedWidth = undefined; }
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (data === "p") { registry.prune(); ensureGroups(); cachedWidth = undefined; }
|
|
167
295
|
},
|
|
168
296
|
|
|
169
297
|
render(width: number): string[] {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (selected >= rows.length) selected = Math.max(0, rows.length - 1);
|
|
298
|
+
groups = buildGroups();
|
|
299
|
+
const total = totalRows();
|
|
300
|
+
if (selected >= total && total > 0) selected = total - 1;
|
|
174
301
|
|
|
175
302
|
const lines: string[] = [];
|
|
176
303
|
const border = "β".repeat(Math.min(width - 4, 80));
|
|
177
304
|
|
|
178
|
-
// Header
|
|
179
305
|
lines.push("");
|
|
180
306
|
lines.push(theme.fg("accent", theme.bold(" π§΅ Thread Dashboard")));
|
|
181
|
-
lines.push(theme.fg("dim", ` ${border}`));
|
|
307
|
+
lines.push(theme.fg("dim", ` ${tw(border, width)}`));
|
|
182
308
|
|
|
183
|
-
if (
|
|
309
|
+
if (groups.length === 0 && total === 0) {
|
|
184
310
|
lines.push("");
|
|
185
311
|
lines.push(theme.fg("muted", " No threads or stories."));
|
|
186
312
|
lines.push(theme.fg("dim", " Use /pthread /fthread /zthread /story to start."));
|
|
187
313
|
} else {
|
|
188
|
-
|
|
189
|
-
for (
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
lines.push(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
314
|
+
let flatIdx = 0;
|
|
315
|
+
for (const g of groups) {
|
|
316
|
+
// Group header
|
|
317
|
+
const gColor = g.color === "success" ? "success" : "accent";
|
|
318
|
+
lines.push("");
|
|
319
|
+
lines.push(theme.fg(gColor, ` ${g.icon} ${g.name} (${g.rows.length})`));
|
|
320
|
+
|
|
321
|
+
for (const row of g.rows) {
|
|
322
|
+
const isSelected = flatIdx === selected;
|
|
323
|
+
const prefix = isSelected ? theme.fg("accent", " βΈ ") : " ";
|
|
324
|
+
|
|
325
|
+
let display: string;
|
|
326
|
+
if (row.kind === "story") {
|
|
327
|
+
const s = registry.getStory(row.id);
|
|
328
|
+
if (s) {
|
|
329
|
+
const phases = s.phases.map(p => `${stateIcon(p.state)}${p.name}`).join("β");
|
|
330
|
+
display = `π ${theme.fg("accent", row.id)} [${theme.fg(stateColor(row.state), row.state)}] ${tw(row.label, 22)} ${theme.fg("dim", tw(phases, 16))}`;
|
|
331
|
+
} else {
|
|
332
|
+
display = `π ${theme.fg("accent", row.id)} ${tw(row.label, 50)}`;
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
// Thread with progress bar + last output snippet
|
|
336
|
+
let progressBar = "ββββββββββ";
|
|
337
|
+
if (row.result) {
|
|
338
|
+
progressBar = "ββββββββββ"; // done
|
|
339
|
+
} else if (row.state === "running" || row.state === "executing") {
|
|
340
|
+
progressBar = "ββββββββββ"; // in progress
|
|
341
|
+
} else if (row.state === "failed") {
|
|
342
|
+
progressBar = "ββββββββββ"; // failed
|
|
343
|
+
}
|
|
344
|
+
const snippet = row.result
|
|
345
|
+
? `β${row.result.slice(0, 40)}`
|
|
346
|
+
: row.error
|
|
347
|
+
? `β${row.error.slice(0, 40)}`
|
|
348
|
+
: row.elapsed
|
|
349
|
+
? `β±${row.elapsed}`
|
|
350
|
+
: "";
|
|
351
|
+
display = `${typeIcon(row.type)} ${theme.fg("accent", row.id)} ${progressBar} [${theme.fg(stateColor(row.state), row.state)}] ${tw(row.label, 20)} ${theme.fg("muted", tw(snippet, 22))}`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
lines.push(tw(prefix + display, width));
|
|
355
|
+
|
|
356
|
+
if (isSelected && expanded === row.id) {
|
|
357
|
+
lines.push(...renderExpanded(row.id, width));
|
|
358
|
+
lines.push("");
|
|
359
|
+
}
|
|
360
|
+
flatIdx++;
|
|
199
361
|
}
|
|
200
362
|
}
|
|
201
363
|
}
|
|
202
364
|
|
|
365
|
+
// Reply mode banner
|
|
366
|
+
if (replyTarget !== null) {
|
|
367
|
+
lines.push("");
|
|
368
|
+
lines.push(tw(theme.fg("warning", theme.bold(" ββ REPLY ββββββββββββββββββββββββββββββ")), width));
|
|
369
|
+
lines.push(tw(theme.fg("warning", ` β ${tw(replyTarget || "", 28).padEnd(28)} β`), width));
|
|
370
|
+
lines.push(tw(theme.fg("warning", ` β ${tw(replyBuffer || "(type message)", 28).padEnd(28)} β`), width));
|
|
371
|
+
lines.push(tw(theme.fg("warning", ` β Enter=send Esc=cancel β`), width));
|
|
372
|
+
lines.push(tw(theme.fg("warning", theme.bold(" ββββββββββββββββββββββββββββββββββββββββ")), width));
|
|
373
|
+
}
|
|
374
|
+
|
|
203
375
|
// Footer
|
|
204
376
|
lines.push("");
|
|
205
|
-
lines.push(theme.fg("dim", ` ${border}`));
|
|
206
|
-
const help =
|
|
377
|
+
lines.push(theme.fg("dim", ` ${tw(border, width)}`));
|
|
378
|
+
const help = showSearch
|
|
379
|
+
? tw(`Search: ${searchQuery}_ Enter done Esc cancel`, width - 4)
|
|
380
|
+
: tw("nav=ββ exp=Enter rep=i srch=/ kill=k rev=r prune=p quit=q", width - 4);
|
|
207
381
|
lines.push(theme.fg("dim", ` ${help}`));
|
|
208
382
|
lines.push("");
|
|
209
383
|
|
|
210
|
-
|
|
384
|
+
// Final safety: truncate ALL lines
|
|
385
|
+
return lines.map(l => tw(l, width));
|
|
211
386
|
},
|
|
212
387
|
|
|
213
388
|
invalidate() {
|
|
@@ -216,4 +391,4 @@ export function createDashboard(
|
|
|
216
391
|
};
|
|
217
392
|
|
|
218
393
|
return component;
|
|
219
|
-
}
|
|
394
|
+
}
|