@zhuxixi/pi-agent-board 0.3.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/IMPLEMENTATION_PLAN.md +920 -0
- package/LICENSE +21 -0
- package/PRD.md +484 -0
- package/PROGRESS.md +127 -0
- package/README.md +131 -0
- package/VERIFY.md +113 -0
- package/docs/BATCH_SELECTION_READ_FLOW.md +277 -0
- package/docs/EXPLORATION.md +187 -0
- package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +579 -0
- package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +704 -0
- package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +499 -0
- package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +366 -0
- package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +105 -0
- package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +142 -0
- package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +59 -0
- package/index.ts +6 -0
- package/package.json +81 -0
- package/runner/job-runner.mjs +420 -0
- package/runner/pty-runner.mjs +310 -0
- package/runner/state-runner.mjs +120 -0
- package/runner/title-runner.mjs +80 -0
- package/scripts/patch-vulns.mjs +59 -0
- package/src/commands/agent-board.ts +318 -0
- package/src/commands/attach-flow.ts +231 -0
- package/src/commands/bg.ts +70 -0
- package/src/core/atomic.mjs +145 -0
- package/src/core/auto-state.mjs +320 -0
- package/src/core/dashboard-render.mjs +10 -0
- package/src/core/derive.mjs +114 -0
- package/src/core/diagnostics.mjs +109 -0
- package/src/core/events.mjs +268 -0
- package/src/core/evidence.mjs +242 -0
- package/src/core/follow-up-queue.mjs +193 -0
- package/src/core/heuristics.mjs +240 -0
- package/src/core/ids.mjs +35 -0
- package/src/core/invocation.mjs +43 -0
- package/src/core/launch-options.mjs +317 -0
- package/src/core/launch.mjs +116 -0
- package/src/core/locks.mjs +80 -0
- package/src/core/paths.mjs +86 -0
- package/src/core/pid.mjs +42 -0
- package/src/core/prewarm-schedule.mjs +41 -0
- package/src/core/prompt-transport.mjs +13 -0
- package/src/core/pty-attach-jiggle-retry.mjs +90 -0
- package/src/core/pty-attach-render.mjs +51 -0
- package/src/core/pty-input.mjs +15 -0
- package/src/core/pty-links.mjs +71 -0
- package/src/core/pty-scroll.mjs +155 -0
- package/src/core/pty-support.mjs +327 -0
- package/src/core/repo.mjs +47 -0
- package/src/core/rows.mjs +290 -0
- package/src/core/screen-log-gc.mjs +198 -0
- package/src/core/screen-log.mjs +160 -0
- package/src/core/session-view.mjs +174 -0
- package/src/core/steering-prompts.mjs +34 -0
- package/src/core/steering.mjs +133 -0
- package/src/core/store.mjs +308 -0
- package/src/core/title.mjs +43 -0
- package/src/core/types.mjs +380 -0
- package/src/core/worktree.mjs +64 -0
- package/src/index.ts +109 -0
- package/src/runtime/service.mjs +1194 -0
- package/src/ui/dashboard-evidence.mjs +85 -0
- package/src/ui/dashboard.ts +1952 -0
- package/src/ui/pty-attach.ts +1378 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared data shapes for the agent-board store. JSDoc typedefs (consumed by the TS
|
|
3
|
+
* extension via `allowJs`) plus the canonical state vocabularies as runtime constants.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Semantic (task) state of a row. @typedef {"queued"|"working"|"needs_input"|"idle"|"completed"|"failed"|"stopped"} SemanticState */
|
|
7
|
+
/** Process/liveness state. @typedef {"alive"|"exited"} ProcessState */
|
|
8
|
+
/** PTY host mode. @typedef {"json-runner"|"pty"} HostMode */
|
|
9
|
+
/** PTY host liveness. @typedef {"starting"|"alive"|"exited"|"failed"} HostState */
|
|
10
|
+
/** How a run was kicked off. @typedef {"dispatch"|"reply"|"plan"|"plan_change"|"plan_approval"} RunKind */
|
|
11
|
+
/** Worktree isolation mode for a row. @typedef {"off"|"worktree"} WorktreeMode */
|
|
12
|
+
/** Diagnostic severity. @typedef {"info"|"warn"|"error"} DiagnosticLevel */
|
|
13
|
+
/** Diagnostic event source. @typedef {"runner"|"host"|"service"|"queue"|"steering"|"evidence"|"store"} DiagnosticSource */
|
|
14
|
+
/** Evidence outcome. @typedef {"unknown"|"in_progress"|"ready"|"needs_input"|"failed"|"stopped"|"queued"|"working"|"idle"|"completed"} EvidenceOutcome */
|
|
15
|
+
/** Command kind. @typedef {"test"|"build"|"lint"|"git"|"install"|"other"} EvidenceCommandKind */
|
|
16
|
+
/** Command status. @typedef {"started"|"passed"|"failed"|"unknown"} EvidenceCommandStatus */
|
|
17
|
+
/** File change action. @typedef {"edited"|"written"|"deleted"|"unknown"} EvidenceFileAction */
|
|
18
|
+
/** Follow-up kind. @typedef {"reply"|"plan_request"|"plan_approval"|"plan_change"} FollowUpKind */
|
|
19
|
+
/** Follow-up status. @typedef {"queued"|"claimed"|"completed"|"failed"|"cancelled"} FollowUpStatus */
|
|
20
|
+
/** Steering state. @typedef {"none"|"plan_requested"|"awaiting_approval"|"approved"|"changes_requested"|"executing_approved_plan"} SteeringModeState */
|
|
21
|
+
/** Dashboard auto-state bucket. @typedef {"needs_input"|"in_progress"|"done"} AutoStateKind */
|
|
22
|
+
/** Auto-state classifier source. @typedef {"model"|"heuristic"} AutoStateSource */
|
|
23
|
+
/** Auto-state classifier confidence. @typedef {"high"|"medium"|"low"} AutoStateConfidence */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* LLM/heuristic classification of the latest assistant turn.
|
|
27
|
+
* @typedef {Object} AutoStateClassification
|
|
28
|
+
* @property {number} version
|
|
29
|
+
* @property {AutoStateKind} kind
|
|
30
|
+
* @property {SemanticState} semanticState
|
|
31
|
+
* @property {AutoStateConfidence} confidence
|
|
32
|
+
* @property {AutoStateSource} source
|
|
33
|
+
* @property {string} reason
|
|
34
|
+
* @property {string|null} question
|
|
35
|
+
* @property {number} classifiedAt
|
|
36
|
+
* @property {number|null} lastAgentActivityAt
|
|
37
|
+
* @property {string} textHash
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export const SEMANTIC_STATES = /** @type {const} */ ([
|
|
41
|
+
"queued",
|
|
42
|
+
"working",
|
|
43
|
+
"needs_input",
|
|
44
|
+
"idle",
|
|
45
|
+
"completed",
|
|
46
|
+
"failed",
|
|
47
|
+
"stopped",
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
export const PROCESS_STATES = /** @type {const} */ (["alive", "exited"]);
|
|
51
|
+
|
|
52
|
+
/** Order rows are grouped/shown in the dashboard (most-actionable first). */
|
|
53
|
+
export const GROUP_ORDER = /** @type {const} */ ([
|
|
54
|
+
"queued",
|
|
55
|
+
"working",
|
|
56
|
+
"needs_input",
|
|
57
|
+
"idle",
|
|
58
|
+
"completed",
|
|
59
|
+
"failed",
|
|
60
|
+
"stopped",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
/** Human labels for group headers. @type {Record<SemanticState,string>} */
|
|
64
|
+
export const GROUP_LABELS = {
|
|
65
|
+
needs_input: "Needs input",
|
|
66
|
+
working: "Running",
|
|
67
|
+
queued: "Queued",
|
|
68
|
+
failed: "Failed",
|
|
69
|
+
completed: "Done",
|
|
70
|
+
idle: "In Progress",
|
|
71
|
+
stopped: "Stopped",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Stable row metadata (`meta.json`).
|
|
76
|
+
* @typedef {Object} ViewMeta
|
|
77
|
+
* @property {number} version
|
|
78
|
+
* @property {string} id
|
|
79
|
+
* @property {string} name
|
|
80
|
+
* @property {string} cwd Working dir the worker runs in (repo or worktree path).
|
|
81
|
+
* @property {string} repoCwd Original repo dir requested at dispatch (== cwd unless worktree).
|
|
82
|
+
* @property {string|null} repoRoot Git repo root, if any.
|
|
83
|
+
* @property {string} sessionFile Managed Pi session JSONL path.
|
|
84
|
+
* @property {number} createdAt
|
|
85
|
+
* @property {number} updatedAt
|
|
86
|
+
* @property {boolean} pinned
|
|
87
|
+
* @property {"pi-session"} kind
|
|
88
|
+
* @property {string|null} defaultModel
|
|
89
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} defaultThinking
|
|
90
|
+
* @property {WorktreeMode} worktreeMode
|
|
91
|
+
* @property {string|null} worktreePath
|
|
92
|
+
* @property {boolean} writeCapable Whether this session may mutate files (default true).
|
|
93
|
+
* @property {boolean} archived Soft-deleted from the dashboard (data preserved).
|
|
94
|
+
* @property {"agent-board"} source
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Derived dashboard state for a row (`state.json`).
|
|
99
|
+
* @typedef {Object} ViewState
|
|
100
|
+
* @property {number} version
|
|
101
|
+
* @property {string} viewId
|
|
102
|
+
* @property {string|null} currentRunId
|
|
103
|
+
* @property {SemanticState} semanticState
|
|
104
|
+
* @property {ProcessState} processState
|
|
105
|
+
* @property {string} summary
|
|
106
|
+
* @property {number} lastActivityAt
|
|
107
|
+
* @property {number} updatedAt
|
|
108
|
+
* @property {boolean} needsInput
|
|
109
|
+
* @property {boolean} hasError
|
|
110
|
+
* @property {string} latestAssistantPreview
|
|
111
|
+
* @property {{name:string, path:string|null}|null} latestTool
|
|
112
|
+
* @property {string|null} question
|
|
113
|
+
* @property {Array<{toolCallId:string, question:string}>} pendingQuestions
|
|
114
|
+
* @property {string|null} error
|
|
115
|
+
* @property {number|null} lastVisitedAt
|
|
116
|
+
* @property {number|null} lastAgentActivityAt Timestamp of the latest assistant reply shown in the row.
|
|
117
|
+
* @property {ReviewSummary} [review] Compact collected-evidence summary.
|
|
118
|
+
* @property {DiagnosticSummary} [diagnostics] Compact diagnostics summary.
|
|
119
|
+
* @property {FollowUpSummary} [followUps] Compact queued follow-up summary.
|
|
120
|
+
* @property {SteeringSummary} [steering] Compact plan/approval steering summary.
|
|
121
|
+
* @property {AutoStateClassification|null} [autoState] Latest automatic terminal-state classification.
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Durable PTY host snapshot (`host.json`).
|
|
126
|
+
* @typedef {Object} HostStatus
|
|
127
|
+
* @property {number} version
|
|
128
|
+
* @property {string} viewId
|
|
129
|
+
* @property {HostMode} mode
|
|
130
|
+
* @property {number|null} runnerPid
|
|
131
|
+
* @property {number|null} childPid
|
|
132
|
+
* @property {string|null} socketPath
|
|
133
|
+
* @property {HostState} state
|
|
134
|
+
* @property {number} startedAt
|
|
135
|
+
* @property {number} lastSeenAt
|
|
136
|
+
* @property {number|null} endedAt
|
|
137
|
+
* @property {number|null} exitCode
|
|
138
|
+
* @property {string|null} error
|
|
139
|
+
* @property {number} cols
|
|
140
|
+
* @property {number} rows
|
|
141
|
+
* @property {number} attachedClients
|
|
142
|
+
* @property {boolean} [attachedEver] Whether any client attached to this host.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Durable per-run snapshot (`runs/<runId>/status.json`).
|
|
147
|
+
* @typedef {Object} RunStatus
|
|
148
|
+
* @property {number} version
|
|
149
|
+
* @property {string} runId
|
|
150
|
+
* @property {string} viewId
|
|
151
|
+
* @property {number|null} pid
|
|
152
|
+
* @property {number} startedAt
|
|
153
|
+
* @property {number|null} endedAt
|
|
154
|
+
* @property {number|null} exitCode
|
|
155
|
+
* @property {RunKind} kind
|
|
156
|
+
* @property {string} prompt
|
|
157
|
+
* @property {string|null} model
|
|
158
|
+
* @property {SemanticState} semanticState
|
|
159
|
+
* @property {ProcessState} processState
|
|
160
|
+
* @property {string} summary
|
|
161
|
+
* @property {number} lastActivityAt
|
|
162
|
+
* @property {{name:string, path:string|null}|null} currentTool
|
|
163
|
+
* @property {string} latestAssistantPreview
|
|
164
|
+
* @property {string|null} question
|
|
165
|
+
* @property {Array<{toolCallId:string, question:string}>} pendingQuestions
|
|
166
|
+
* @property {string|null} error
|
|
167
|
+
* @property {number|null} lastAgentActivityAt Timestamp of the latest assistant reply.
|
|
168
|
+
* @property {string|null} stopReason
|
|
169
|
+
* @property {boolean} stoppedByUser
|
|
170
|
+
* @property {number} turns
|
|
171
|
+
* @property {number} toolCount
|
|
172
|
+
* @property {number} [eventCount]
|
|
173
|
+
* @property {number|null} [lastEventAt]
|
|
174
|
+
* @property {EvidenceUsage|null} [usage]
|
|
175
|
+
* @property {string|null} [stallReason]
|
|
176
|
+
* @property {ReviewSummary|null} [evidenceSummary]
|
|
177
|
+
* @property {AutoStateClassification|null} [autoState]
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Configuration handed to the detached runner (written to the run dir as `config.json`
|
|
182
|
+
* and passed by path on argv).
|
|
183
|
+
* @typedef {Object} RunConfig
|
|
184
|
+
* @property {string} root
|
|
185
|
+
* @property {string} viewId
|
|
186
|
+
* @property {string} runId
|
|
187
|
+
* @property {RunKind} kind
|
|
188
|
+
* @property {string} sessionFile
|
|
189
|
+
* @property {string} cwd
|
|
190
|
+
* @property {string} prompt
|
|
191
|
+
* @property {string} piCommand Executable to launch the worker (e.g. "pi" or a node path).
|
|
192
|
+
* @property {string[]} piArgsPrefix Args before our flags (e.g. [cliJsPath] when piCommand is node).
|
|
193
|
+
* @property {string|null} model
|
|
194
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} thinkingLevel
|
|
195
|
+
* @property {string|null} tools
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Configuration handed to the detached PTY host runner (written as `host-config.json`).
|
|
200
|
+
* @typedef {Object} HostConfig
|
|
201
|
+
* @property {string} root
|
|
202
|
+
* @property {string} viewId
|
|
203
|
+
* @property {string} sessionFile
|
|
204
|
+
* @property {string} cwd
|
|
205
|
+
* @property {string|null} initialPrompt
|
|
206
|
+
* @property {string} piCommand
|
|
207
|
+
* @property {string[]} piArgsPrefix
|
|
208
|
+
* @property {string|null} model
|
|
209
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} thinkingLevel
|
|
210
|
+
* @property {string|null} tools
|
|
211
|
+
* @property {Record<string,string>} env
|
|
212
|
+
* @property {number} cols
|
|
213
|
+
* @property {number} rows
|
|
214
|
+
* @property {number|null} screenLogMaxBytes per-view screen.log write cap; null = runner default
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Configuration handed to the detached title runner (written as `title-config.json`).
|
|
219
|
+
* @typedef {Object} TitleConfig
|
|
220
|
+
* @property {string} root
|
|
221
|
+
* @property {string} viewId
|
|
222
|
+
* @property {string} cwd
|
|
223
|
+
* @property {string} prompt
|
|
224
|
+
* @property {string} fallbackName
|
|
225
|
+
* @property {string} piCommand
|
|
226
|
+
* @property {string[]} piArgsPrefix
|
|
227
|
+
* @property {string|null} model
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Configuration handed to the detached auto-state runner.
|
|
232
|
+
* @typedef {Object} AutoStateConfig
|
|
233
|
+
* @property {string} root
|
|
234
|
+
* @property {string} viewId
|
|
235
|
+
* @property {string|null} runId
|
|
236
|
+
* @property {string} cwd
|
|
237
|
+
* @property {string} piCommand
|
|
238
|
+
* @property {string[]} piArgsPrefix
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @typedef {Object} DiagnosticEvent
|
|
243
|
+
* @property {number} version
|
|
244
|
+
* @property {number} at
|
|
245
|
+
* @property {string} viewId
|
|
246
|
+
* @property {string|null} runId
|
|
247
|
+
* @property {DiagnosticSource|string} source
|
|
248
|
+
* @property {DiagnosticLevel} level
|
|
249
|
+
* @property {string} code
|
|
250
|
+
* @property {string} message
|
|
251
|
+
* @property {Record<string,any>} details
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @typedef {Object} DiagnosticSummary
|
|
256
|
+
* @property {number} count
|
|
257
|
+
* @property {number} warningCount
|
|
258
|
+
* @property {number} errorCount
|
|
259
|
+
* @property {number|null} lastAt
|
|
260
|
+
* @property {DiagnosticLevel|null} lastLevel
|
|
261
|
+
* @property {string|null} lastCode
|
|
262
|
+
* @property {string|null} lastMessage
|
|
263
|
+
* @property {boolean} stalled
|
|
264
|
+
* @property {string|null} stallReason
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @typedef {Object} EvidenceUsage
|
|
269
|
+
* @property {number} inputTokens
|
|
270
|
+
* @property {number} outputTokens
|
|
271
|
+
* @property {number} totalTokens
|
|
272
|
+
*/
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @typedef {Object} EvidenceCommand
|
|
276
|
+
* @property {string} id
|
|
277
|
+
* @property {number} at
|
|
278
|
+
* @property {string} command
|
|
279
|
+
* @property {EvidenceCommandKind} kind
|
|
280
|
+
* @property {EvidenceCommandStatus} status
|
|
281
|
+
* @property {number|null} exitCode
|
|
282
|
+
* @property {number|null} durationMs
|
|
283
|
+
* @property {string} outputPreview
|
|
284
|
+
*/
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @typedef {Object} EvidenceFileChange
|
|
288
|
+
* @property {string} path
|
|
289
|
+
* @property {EvidenceFileAction} action
|
|
290
|
+
* @property {string|null} toolName
|
|
291
|
+
* @property {number} firstSeenAt
|
|
292
|
+
* @property {number} lastSeenAt
|
|
293
|
+
* @property {number} count
|
|
294
|
+
*/
|
|
295
|
+
|
|
296
|
+
/** @typedef {{ at:number, source:string, message:string, toolName:string|null }} EvidenceError */
|
|
297
|
+
/** @typedef {{ at:number, text:string }} EvidenceAssistantClaim */
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @typedef {Object} EvidenceSnapshot
|
|
301
|
+
* @property {number} version
|
|
302
|
+
* @property {string} viewId
|
|
303
|
+
* @property {string|null} runId
|
|
304
|
+
* @property {number} updatedAt
|
|
305
|
+
* @property {EvidenceOutcome|string} outcome
|
|
306
|
+
* @property {boolean} ready
|
|
307
|
+
* @property {string} summary
|
|
308
|
+
* @property {EvidenceCommand[]} commands
|
|
309
|
+
* @property {EvidenceFileChange[]} fileChanges
|
|
310
|
+
* @property {EvidenceError[]} errors
|
|
311
|
+
* @property {EvidenceAssistantClaim[]} assistantEvidence
|
|
312
|
+
* @property {EvidenceUsage|null} usage
|
|
313
|
+
* @property {number} [eventCount]
|
|
314
|
+
* @property {string} [source]
|
|
315
|
+
*/
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @typedef {Object} ReviewSummary
|
|
319
|
+
* @property {boolean} ready
|
|
320
|
+
* @property {EvidenceOutcome|string} outcome
|
|
321
|
+
* @property {number} fileChangeCount
|
|
322
|
+
* @property {number} commandCount
|
|
323
|
+
* @property {number} failedCommandCount
|
|
324
|
+
* @property {number} errorCount
|
|
325
|
+
* @property {string} latestAssistantEvidence
|
|
326
|
+
* @property {number|null} updatedAt
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @typedef {Object} FollowUpItem
|
|
331
|
+
* @property {string} id
|
|
332
|
+
* @property {number} seq
|
|
333
|
+
* @property {string} viewId
|
|
334
|
+
* @property {FollowUpKind} kind
|
|
335
|
+
* @property {string} text
|
|
336
|
+
* @property {number} createdAt
|
|
337
|
+
* @property {number} updatedAt
|
|
338
|
+
* @property {FollowUpStatus} status
|
|
339
|
+
* @property {string} source
|
|
340
|
+
* @property {"auto"|"now"|"queue"} delivery
|
|
341
|
+
* @property {string|null} runId
|
|
342
|
+
* @property {number|null} claimedAt
|
|
343
|
+
* @property {number|null} completedAt
|
|
344
|
+
* @property {number} attempts
|
|
345
|
+
* @property {string|null} error
|
|
346
|
+
*/
|
|
347
|
+
|
|
348
|
+
/** @typedef {{ version:number, viewId:string, nextSeq:number, updatedAt:number, items:FollowUpItem[] }} FollowUpQueue */
|
|
349
|
+
/** @typedef {{ queuedCount:number, claimedCount:number, lastQueuedAt:number|null, lastQueuedPreview:string|null }} FollowUpSummary */
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @typedef {Object} SteeringState
|
|
353
|
+
* @property {number} version
|
|
354
|
+
* @property {string} viewId
|
|
355
|
+
* @property {SteeringModeState} status
|
|
356
|
+
* @property {number} updatedAt
|
|
357
|
+
* @property {string} planText
|
|
358
|
+
* @property {string|null} planRunId
|
|
359
|
+
* @property {number|null} approvedAt
|
|
360
|
+
* @property {string|null} changeRequest
|
|
361
|
+
* @property {string|null} executionRunId
|
|
362
|
+
* @property {Array<{at:number, from:string, to:string, action:string, runId:string|null, note:string|null}>} history
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
/** @typedef {{ status:SteeringModeState, awaitingApproval:boolean, planPreview:string|null, updatedAt:number|null, question:string|null }} SteeringSummary */
|
|
366
|
+
|
|
367
|
+
/** Roster index (`roster.json`). @typedef {Object} Roster @property {number} version @property {string[]} views */
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Persisted launch dialog defaults (`launch-prefs.json`).
|
|
371
|
+
* @typedef {Object} LaunchPrefs
|
|
372
|
+
* @property {number} version
|
|
373
|
+
* @property {string|null} cwd
|
|
374
|
+
* @property {string|null} model
|
|
375
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} thinkingLevel
|
|
376
|
+
* @property {number|null} screenLogRetentionDays days before an ended view's screen.log is GC'd; 0 disables GC
|
|
377
|
+
* @property {number|null} screenLogMaxSize per-view screen.log write cap in bytes; null = built-in default
|
|
378
|
+
*/
|
|
379
|
+
|
|
380
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git worktree isolation for same-repo parallel writer sessions (plan §11.2).
|
|
3
|
+
* Each managed view gets its own worktree + branch so concurrent writers never clobber.
|
|
4
|
+
*/
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} repoRoot
|
|
10
|
+
* @param {string[]} args
|
|
11
|
+
* @returns {{ ok: boolean, stdout: string, error: string|null }}
|
|
12
|
+
*/
|
|
13
|
+
function git(repoRoot, args) {
|
|
14
|
+
try {
|
|
15
|
+
const stdout = execFileSync("git", ["-C", repoRoot, ...args], {
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18
|
+
});
|
|
19
|
+
return { ok: true, stdout: stdout.trim(), error: null };
|
|
20
|
+
} catch (err) {
|
|
21
|
+
const e = /** @type {any} */ (err);
|
|
22
|
+
const error = (e?.stderr?.toString?.() || e?.message || "git error").trim();
|
|
23
|
+
return { ok: false, stdout: "", error };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a worktree at `worktreePath` on a fresh branch `branch`, based on HEAD.
|
|
29
|
+
* Idempotent-ish: if the path already exists we assume it's already a worktree.
|
|
30
|
+
* @param {string} repoRoot
|
|
31
|
+
* @param {string} worktreePath
|
|
32
|
+
* @param {string} branch
|
|
33
|
+
* @returns {{ ok: boolean, error: string|null }}
|
|
34
|
+
*/
|
|
35
|
+
export function addWorktree(repoRoot, worktreePath, branch) {
|
|
36
|
+
if (existsSync(worktreePath)) return { ok: true, error: null };
|
|
37
|
+
// Try with a new branch first; fall back to attaching if the branch already exists.
|
|
38
|
+
let res = git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
39
|
+
if (!res.ok && /already exists/i.test(res.error ?? "")) {
|
|
40
|
+
res = git(repoRoot, ["worktree", "add", worktreePath, branch]);
|
|
41
|
+
}
|
|
42
|
+
return { ok: res.ok, error: res.error };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Remove a worktree (forced) and prune. Best-effort.
|
|
47
|
+
* @param {string} repoRoot
|
|
48
|
+
* @param {string} worktreePath
|
|
49
|
+
* @returns {{ ok: boolean, error: string|null }}
|
|
50
|
+
*/
|
|
51
|
+
export function removeWorktree(repoRoot, worktreePath) {
|
|
52
|
+
const res = git(repoRoot, ["worktree", "remove", "--force", worktreePath]);
|
|
53
|
+
git(repoRoot, ["worktree", "prune"]);
|
|
54
|
+
return { ok: res.ok, error: res.error };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A short, filesystem/branch-safe identifier for a view's worktree branch.
|
|
59
|
+
* @param {string} viewId
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
export function worktreeBranch(viewId) {
|
|
63
|
+
return `agent-board/${viewId}`;
|
|
64
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi Agent Board — extension entry point.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `/agent-board` dashboard command, resolves how to
|
|
5
|
+
* launch background workers and the detached runner, and keeps a small footer status with
|
|
6
|
+
* the count of sessions needing attention. See docs/EXPLORATION.md for the design.
|
|
7
|
+
*/
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { resolvePiInvocation } from "./core/invocation.mjs";
|
|
11
|
+
import { defaultRoot } from "./core/paths.mjs";
|
|
12
|
+
import { listRows } from "./core/store.mjs";
|
|
13
|
+
import { createService } from "./runtime/service.mjs";
|
|
14
|
+
import { openDashboard, registerAgentBoardCommand } from "./commands/agent-board.js";
|
|
15
|
+
import { registerBgCommand } from "./commands/bg.js";
|
|
16
|
+
|
|
17
|
+
const RUNNER_SCRIPT = fileURLToPath(new URL("../runner/job-runner.mjs", import.meta.url));
|
|
18
|
+
const PTY_RUNNER_SCRIPT = fileURLToPath(new URL("../runner/pty-runner.mjs", import.meta.url));
|
|
19
|
+
const TITLE_RUNNER_SCRIPT = fileURLToPath(new URL("../runner/title-runner.mjs", import.meta.url));
|
|
20
|
+
const AUTO_STATE_RUNNER_SCRIPT = fileURLToPath(new URL("../runner/state-runner.mjs", import.meta.url));
|
|
21
|
+
|
|
22
|
+
export default function piAgentBoard(pi: ExtensionAPI): void {
|
|
23
|
+
const root = defaultRoot();
|
|
24
|
+
const { piCommand, piArgsPrefix } = resolvePiInvocation();
|
|
25
|
+
|
|
26
|
+
const isHostedChild = process.env.AGENT_BOARD_CHILD === "1" || process.env.AGENT_VIEW_CHILD === "1";
|
|
27
|
+
const hostedViewId = process.env.AGENT_BOARD_VIEW_ID ?? process.env.AGENT_VIEW_VIEW_ID;
|
|
28
|
+
|
|
29
|
+
const commandOpts = {
|
|
30
|
+
root,
|
|
31
|
+
runnerScript: RUNNER_SCRIPT,
|
|
32
|
+
titleRunnerScript: TITLE_RUNNER_SCRIPT,
|
|
33
|
+
autoStateRunnerScript: AUTO_STATE_RUNNER_SCRIPT,
|
|
34
|
+
ptyRunnerScript: PTY_RUNNER_SCRIPT,
|
|
35
|
+
piCommand,
|
|
36
|
+
piArgsPrefix,
|
|
37
|
+
getThinkingLevel: () => pi.getThinkingLevel(),
|
|
38
|
+
};
|
|
39
|
+
registerAgentBoardCommand(pi, commandOpts);
|
|
40
|
+
registerBgCommand(pi, commandOpts);
|
|
41
|
+
pi.registerFlag("agent-board", {
|
|
42
|
+
description: "Open the agent-board dashboard on startup",
|
|
43
|
+
type: "boolean",
|
|
44
|
+
default: false,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Footer status: reconcile stale rows and surface how many need attention.
|
|
48
|
+
const serviceFor = (ctx: ExtensionContext) =>
|
|
49
|
+
createService({ root, runnerScript: RUNNER_SCRIPT, ptyRunnerScript: PTY_RUNNER_SCRIPT, titleRunnerScript: TITLE_RUNNER_SCRIPT, autoStateRunnerScript: AUTO_STATE_RUNNER_SCRIPT, piCommand, piArgsPrefix, defaultCwd: ctx.cwd });
|
|
50
|
+
|
|
51
|
+
const updateStatus = (ctx: ExtensionContext) => {
|
|
52
|
+
try {
|
|
53
|
+
serviceFor(ctx).reconcile();
|
|
54
|
+
const rows = listRows(root);
|
|
55
|
+
const needs = rows.filter((r) => r.state?.semanticState === "needs_input").length;
|
|
56
|
+
const working = rows.filter((r) => r.alive).length;
|
|
57
|
+
const queued = rows.reduce((sum, r) => sum + (r.state?.followUps?.queuedCount ?? 0), 0);
|
|
58
|
+
if (isHostedChild) return;
|
|
59
|
+
if (rows.length === 0) {
|
|
60
|
+
ctx.ui.setStatus("agent-board", undefined);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const parts: string[] = [];
|
|
64
|
+
if (working > 0) parts.push(ctx.ui.theme.fg("accent", `●${working}`));
|
|
65
|
+
if (needs > 0) parts.push(ctx.ui.theme.fg("warning", `◆${needs}`));
|
|
66
|
+
if (queued > 0) parts.push(ctx.ui.theme.fg("muted", `q${queued}`));
|
|
67
|
+
ctx.ui.setStatus("agent-board", parts.length ? `${ctx.ui.theme.fg("muted", "agents")} ${parts.join(" ")}` : undefined);
|
|
68
|
+
} catch {
|
|
69
|
+
/* never break the session over a status update */
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
pi.on("session_start", async (event, ctx) => {
|
|
74
|
+
updateStatus(ctx);
|
|
75
|
+
if (event.reason === "startup" && !isHostedChild && pi.getFlag("agent-board") === true && ctx.hasUI) {
|
|
76
|
+
const service = createService({ root, runnerScript: RUNNER_SCRIPT, ptyRunnerScript: PTY_RUNNER_SCRIPT, titleRunnerScript: TITLE_RUNNER_SCRIPT, autoStateRunnerScript: AUTO_STATE_RUNNER_SCRIPT, piCommand, piArgsPrefix, defaultCwd: ctx.cwd });
|
|
77
|
+
service.reconcile();
|
|
78
|
+
ctx.ui.setWorkingVisible(false);
|
|
79
|
+
ctx.ui.setHeader(() => ({ render: () => [], invalidate() {} }));
|
|
80
|
+
ctx.ui.setFooter(() => ({ render: () => [], invalidate() {} }));
|
|
81
|
+
ctx.ui.setTitle("agent board");
|
|
82
|
+
const result = await openDashboard(ctx, service, { currentThinkingLevel: pi.getThinkingLevel() });
|
|
83
|
+
if (result.action === "attach") {
|
|
84
|
+
ctx.ui.notify("Attach requires the /agent-board command path; launch from a normal Pi session for now.", "warning");
|
|
85
|
+
} else {
|
|
86
|
+
ctx.shutdown();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const syncForeground = (event: unknown, ctx: ExtensionContext) => {
|
|
91
|
+
try {
|
|
92
|
+
const service = serviceFor(ctx);
|
|
93
|
+
if (hostedViewId) service.syncHostedEvent(hostedViewId, event);
|
|
94
|
+
else service.syncForegroundEvent(ctx.sessionManager.getSessionFile(), event);
|
|
95
|
+
updateStatus(ctx);
|
|
96
|
+
} catch {
|
|
97
|
+
/* never break the session over dashboard mirroring */
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
pi.on("input", async (event, ctx) => syncForeground(event, ctx));
|
|
102
|
+
pi.on("before_agent_start", async (event, ctx) => syncForeground(event, ctx));
|
|
103
|
+
pi.on("agent_start", async (event, ctx) => syncForeground(event, ctx));
|
|
104
|
+
pi.on("tool_execution_start", async (event, ctx) => syncForeground(event, ctx));
|
|
105
|
+
pi.on("tool_execution_end", async (event, ctx) => syncForeground(event, ctx));
|
|
106
|
+
pi.on("message_start", async (event, ctx) => syncForeground(event, ctx));
|
|
107
|
+
pi.on("message_end", async (event, ctx) => syncForeground(event, ctx));
|
|
108
|
+
pi.on("agent_end", async (event, ctx) => syncForeground(event, ctx));
|
|
109
|
+
}
|