@sebastianandreasson/pi-autonomous-agents 0.5.2 → 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/README.md +23 -9
- package/SETUP.md +5 -0
- package/docs/PI_SUPERVISOR.md +14 -65
- package/package.json +6 -3
- package/pi.config.json +1 -2
- package/src/cli.mjs +1 -1
- package/src/index.mjs +2 -0
- package/src/pi-client.mjs +68 -119
- package/src/pi-config.mjs +3 -3
- package/src/pi-sdk-turn.mjs +654 -0
- package/src/pi-supervisor.mjs +58 -0
- package/src/pi-telemetry.mjs +4 -0
- package/src/pi-visualizer-shared.mjs +219 -0
- package/src/pi-visualizer.mjs +476 -0
- package/templates/pi.config.example.json +1 -2
- package/src/pi-rpc-adapter.mjs +0 -668
package/src/pi-supervisor.mjs
CHANGED
|
@@ -337,6 +337,15 @@ async function runAgentInvocation({
|
|
|
337
337
|
sessionId,
|
|
338
338
|
sessionFile,
|
|
339
339
|
}) {
|
|
340
|
+
await updateRunOwnership(config, {
|
|
341
|
+
status: 'agent_running',
|
|
342
|
+
iteration,
|
|
343
|
+
phase,
|
|
344
|
+
activeKind: kind,
|
|
345
|
+
activeRole: role,
|
|
346
|
+
activeReason: reason,
|
|
347
|
+
})
|
|
348
|
+
|
|
340
349
|
const beforeSnapshot = getRepoSnapshot(config.cwd)
|
|
341
350
|
const resolvedModel = resolveRoleModel(config, role)
|
|
342
351
|
const promptSnapshot = [
|
|
@@ -513,6 +522,15 @@ async function runHarnessGitFinalize({
|
|
|
513
522
|
phase,
|
|
514
523
|
commitPlan,
|
|
515
524
|
}) {
|
|
525
|
+
await updateRunOwnership(config, {
|
|
526
|
+
status: 'git_finalize_running',
|
|
527
|
+
iteration,
|
|
528
|
+
phase,
|
|
529
|
+
activeKind: 'git_finalize',
|
|
530
|
+
activeRole: '',
|
|
531
|
+
activeReason: '',
|
|
532
|
+
})
|
|
533
|
+
|
|
516
534
|
const beforeSnapshot = getRepoSnapshot(config.cwd)
|
|
517
535
|
const dirtyFiles = new Set(listChangedFiles(config.cwd))
|
|
518
536
|
const requestedFiles = Array.isArray(commitPlan.files) ? commitPlan.files.filter(Boolean) : []
|
|
@@ -624,6 +642,15 @@ async function runHarnessGitFinalize({
|
|
|
624
642
|
}
|
|
625
643
|
|
|
626
644
|
async function runVerificationStep({ config, iteration, phase, kind }) {
|
|
645
|
+
await updateRunOwnership(config, {
|
|
646
|
+
status: 'verification_running',
|
|
647
|
+
iteration,
|
|
648
|
+
phase,
|
|
649
|
+
activeKind: kind,
|
|
650
|
+
activeRole: '',
|
|
651
|
+
activeReason: '',
|
|
652
|
+
})
|
|
653
|
+
|
|
627
654
|
const beforeSnapshot = getRepoSnapshot(config.cwd)
|
|
628
655
|
const verification = await runVerification(config)
|
|
629
656
|
const afterSnapshot = getRepoSnapshot(config.cwd)
|
|
@@ -988,6 +1015,15 @@ async function runTesterCommitTurn({
|
|
|
988
1015
|
}
|
|
989
1016
|
|
|
990
1017
|
async function runVisualReview({ config, iteration, phase, task, changedFiles }) {
|
|
1018
|
+
await updateRunOwnership(config, {
|
|
1019
|
+
status: 'visual_capture_running',
|
|
1020
|
+
iteration,
|
|
1021
|
+
phase,
|
|
1022
|
+
activeKind: 'visual_capture',
|
|
1023
|
+
activeRole: '',
|
|
1024
|
+
activeReason: '',
|
|
1025
|
+
})
|
|
1026
|
+
|
|
991
1027
|
const capture = await runVisualCapture(config, {
|
|
992
1028
|
iteration,
|
|
993
1029
|
phase,
|
|
@@ -1035,6 +1071,15 @@ async function runVisualReview({ config, iteration, phase, task, changedFiles })
|
|
|
1035
1071
|
}
|
|
1036
1072
|
}
|
|
1037
1073
|
|
|
1074
|
+
await updateRunOwnership(config, {
|
|
1075
|
+
status: 'visual_review_running',
|
|
1076
|
+
iteration,
|
|
1077
|
+
phase,
|
|
1078
|
+
activeKind: 'visual_review',
|
|
1079
|
+
activeRole: 'visualReview',
|
|
1080
|
+
activeReason: '',
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1038
1083
|
const visualReviewModel = resolveRoleModel(config, 'visualReview')
|
|
1039
1084
|
const reviewRequest = {
|
|
1040
1085
|
iteration,
|
|
@@ -1196,6 +1241,9 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1196
1241
|
iteration,
|
|
1197
1242
|
phase,
|
|
1198
1243
|
task,
|
|
1244
|
+
activeKind: '',
|
|
1245
|
+
activeRole: '',
|
|
1246
|
+
activeReason: '',
|
|
1199
1247
|
})
|
|
1200
1248
|
const canResumePriorSession = (
|
|
1201
1249
|
state.lastTransport === config.transport
|
|
@@ -1576,6 +1624,9 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1576
1624
|
task,
|
|
1577
1625
|
lastCompletedIteration: iteration,
|
|
1578
1626
|
lastStatus: finalStatus,
|
|
1627
|
+
activeKind: '',
|
|
1628
|
+
activeRole: '',
|
|
1629
|
+
activeReason: '',
|
|
1579
1630
|
})
|
|
1580
1631
|
|
|
1581
1632
|
await appendLog(
|
|
@@ -1681,6 +1732,7 @@ async function main() {
|
|
|
1681
1732
|
config.runTelemetryCsv = path.join(runDir, 'pi_telemetry.csv')
|
|
1682
1733
|
config.runStateFile = path.join(runDir, 'state.json')
|
|
1683
1734
|
config.runLastIterationSummaryFile = path.join(runDir, 'last-iteration.json')
|
|
1735
|
+
config.runLastAgentOutputFile = path.join(runDir, 'last-output.txt')
|
|
1684
1736
|
|
|
1685
1737
|
ensureRepo(config.cwd)
|
|
1686
1738
|
await ensureFileExists(config.taskFile, 'task file')
|
|
@@ -1728,6 +1780,9 @@ async function main() {
|
|
|
1728
1780
|
await updateRunOwnership(config, {
|
|
1729
1781
|
status: 'starting_iteration',
|
|
1730
1782
|
iteration,
|
|
1783
|
+
activeKind: '',
|
|
1784
|
+
activeRole: '',
|
|
1785
|
+
activeReason: '',
|
|
1731
1786
|
})
|
|
1732
1787
|
const result = await runIteration({ config, state, iteration })
|
|
1733
1788
|
await writeIterationSummary(config, result.iterationSummary ?? result.summary)
|
|
@@ -1754,6 +1809,9 @@ async function main() {
|
|
|
1754
1809
|
await updateRunOwnership(config, {
|
|
1755
1810
|
status: stopRequested ? 'stopped' : 'finished',
|
|
1756
1811
|
heartbeatAt: timestamp(),
|
|
1812
|
+
activeKind: '',
|
|
1813
|
+
activeRole: '',
|
|
1814
|
+
activeReason: '',
|
|
1757
1815
|
})
|
|
1758
1816
|
await releaseRunLock(config.activeRunFile, runId)
|
|
1759
1817
|
delete process.env.PI_RUN_ID
|
package/src/pi-telemetry.mjs
CHANGED
|
@@ -10,6 +10,10 @@ function csvEscape(value) {
|
|
|
10
10
|
|
|
11
11
|
export async function ensureTelemetryFiles(config) {
|
|
12
12
|
await fs.writeFile(config.lastAgentOutputFile, '', 'utf8')
|
|
13
|
+
if (config.runLastAgentOutputFile && config.runLastAgentOutputFile !== config.lastAgentOutputFile) {
|
|
14
|
+
await fs.mkdir(path.dirname(config.runLastAgentOutputFile), { recursive: true })
|
|
15
|
+
await fs.writeFile(config.runLastAgentOutputFile, '', 'utf8')
|
|
16
|
+
}
|
|
13
17
|
await fs.writeFile(config.lastVerificationOutputFile, '', 'utf8')
|
|
14
18
|
await fs.writeFile(config.changedFilesFile, '', 'utf8')
|
|
15
19
|
await fs.writeFile(config.lastPromptFile, '', 'utf8')
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const FLOW_STEPS = [
|
|
2
|
+
{ key: 'developer', label: 'Developer' },
|
|
3
|
+
{ key: 'verification', label: 'Verification' },
|
|
4
|
+
{ key: 'tester', label: 'Tester' },
|
|
5
|
+
{ key: 'fix', label: 'Fix' },
|
|
6
|
+
{ key: 'git_finalize', label: 'Git Finalize' },
|
|
7
|
+
{ key: 'visual_capture', label: 'Visual Capture' },
|
|
8
|
+
{ key: 'visual_review', label: 'Visual Review' },
|
|
9
|
+
{ key: 'summary', label: 'Summary' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const KIND_LABELS = {
|
|
13
|
+
main_agent: 'Developer',
|
|
14
|
+
developer_verification: 'Verification',
|
|
15
|
+
developer_reverification: 'Reverification',
|
|
16
|
+
tester_reverification: 'Tester Reverify',
|
|
17
|
+
tester_agent: 'Tester',
|
|
18
|
+
tester_commit: 'Tester Commit',
|
|
19
|
+
fix_agent: 'Fix',
|
|
20
|
+
git_finalize: 'Git Finalize',
|
|
21
|
+
visual_capture: 'Visual Capture',
|
|
22
|
+
visual_review: 'Visual Review',
|
|
23
|
+
iteration_summary: 'Summary',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SUCCESS_STATUSES = new Set(['success', 'passed', 'complete'])
|
|
27
|
+
const ERROR_STATUSES = new Set(['failed', 'timed_out', 'stalled', 'blocked', 'canceled'])
|
|
28
|
+
const SKIP_STATUSES = new Set(['skipped', 'not_run', 'not_needed'])
|
|
29
|
+
|
|
30
|
+
export function getFlowSteps() {
|
|
31
|
+
return FLOW_STEPS.map((step) => ({ ...step }))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getLabelForKind(kind) {
|
|
35
|
+
const key = String(kind ?? '').trim()
|
|
36
|
+
if (key === '') {
|
|
37
|
+
return 'Unknown'
|
|
38
|
+
}
|
|
39
|
+
return KIND_LABELS[key] || key
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getStepKeyForKind(kind) {
|
|
43
|
+
switch (String(kind ?? '')) {
|
|
44
|
+
case 'main_agent':
|
|
45
|
+
return 'developer'
|
|
46
|
+
case 'developer_verification':
|
|
47
|
+
case 'developer_reverification':
|
|
48
|
+
case 'tester_reverification':
|
|
49
|
+
return 'verification'
|
|
50
|
+
case 'tester_agent':
|
|
51
|
+
case 'tester_commit':
|
|
52
|
+
return 'tester'
|
|
53
|
+
case 'fix_agent':
|
|
54
|
+
return 'fix'
|
|
55
|
+
case 'git_finalize':
|
|
56
|
+
return 'git_finalize'
|
|
57
|
+
case 'visual_capture':
|
|
58
|
+
return 'visual_capture'
|
|
59
|
+
case 'visual_review':
|
|
60
|
+
return 'visual_review'
|
|
61
|
+
case 'iteration_summary':
|
|
62
|
+
return 'summary'
|
|
63
|
+
default:
|
|
64
|
+
return ''
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getStepKeyForActiveRun(activeRun) {
|
|
69
|
+
const activeKind = String(activeRun?.activeKind ?? '').trim()
|
|
70
|
+
if (activeKind !== '') {
|
|
71
|
+
return getStepKeyForKind(activeKind)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const status = String(activeRun?.status ?? '').trim()
|
|
75
|
+
if (status === 'starting_iteration' || status === 'iteration_in_progress' || status === 'agent_running') {
|
|
76
|
+
return 'developer'
|
|
77
|
+
}
|
|
78
|
+
if (status === 'verification_running') {
|
|
79
|
+
return 'verification'
|
|
80
|
+
}
|
|
81
|
+
if (status === 'git_finalize_running') {
|
|
82
|
+
return 'git_finalize'
|
|
83
|
+
}
|
|
84
|
+
if (status === 'visual_capture_running') {
|
|
85
|
+
return 'visual_capture'
|
|
86
|
+
}
|
|
87
|
+
if (status === 'visual_review_running') {
|
|
88
|
+
return 'visual_review'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return ''
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeEventStatus(status) {
|
|
95
|
+
const value = String(status ?? '').trim().toLowerCase()
|
|
96
|
+
if (SUCCESS_STATUSES.has(value)) {
|
|
97
|
+
return 'done'
|
|
98
|
+
}
|
|
99
|
+
if (ERROR_STATUSES.has(value)) {
|
|
100
|
+
return 'error'
|
|
101
|
+
}
|
|
102
|
+
if (SKIP_STATUSES.has(value)) {
|
|
103
|
+
return 'skipped'
|
|
104
|
+
}
|
|
105
|
+
return 'pending'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function deriveCurrentIteration({ activeRun, summary, telemetry }) {
|
|
109
|
+
const activeIteration = Number(activeRun?.iteration)
|
|
110
|
+
if (Number.isFinite(activeIteration) && activeIteration > 0) {
|
|
111
|
+
return activeIteration
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const summaryIteration = Number(summary?.iteration)
|
|
115
|
+
if (Number.isFinite(summaryIteration) && summaryIteration > 0) {
|
|
116
|
+
return summaryIteration
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const lastTelemetryIteration = Number(telemetry?.at?.(-1)?.iteration)
|
|
120
|
+
if (Number.isFinite(lastTelemetryIteration) && lastTelemetryIteration > 0) {
|
|
121
|
+
return lastTelemetryIteration
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return 0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function deriveFlowSnapshot({ activeRun, summary, telemetry }) {
|
|
128
|
+
const currentIteration = deriveCurrentIteration({ activeRun, summary, telemetry })
|
|
129
|
+
const iterationTelemetry = Array.isArray(telemetry)
|
|
130
|
+
? telemetry.filter((event) => Number(event?.iteration) === currentIteration)
|
|
131
|
+
: []
|
|
132
|
+
const activeStepKey = getStepKeyForActiveRun(activeRun)
|
|
133
|
+
const steps = FLOW_STEPS.map((step) => {
|
|
134
|
+
const matchingEvents = iterationTelemetry.filter((event) => getStepKeyForKind(event?.kind) === step.key)
|
|
135
|
+
const latestEvent = matchingEvents.at(-1) ?? null
|
|
136
|
+
const status = activeStepKey === step.key
|
|
137
|
+
? 'active'
|
|
138
|
+
: latestEvent
|
|
139
|
+
? normalizeEventStatus(latestEvent.status)
|
|
140
|
+
: 'pending'
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...step,
|
|
144
|
+
status,
|
|
145
|
+
latestEvent,
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
iteration: currentIteration,
|
|
151
|
+
activeStepKey,
|
|
152
|
+
steps,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function deriveStageGraph({ activeRun, summary, telemetry }) {
|
|
157
|
+
const flow = deriveFlowSnapshot({ activeRun, summary, telemetry })
|
|
158
|
+
const currentIteration = flow.iteration
|
|
159
|
+
const iterationTelemetry = Array.isArray(telemetry)
|
|
160
|
+
? telemetry.filter((event) => Number(event?.iteration) === currentIteration)
|
|
161
|
+
: []
|
|
162
|
+
|
|
163
|
+
const nodes = iterationTelemetry.map((event, index) => {
|
|
164
|
+
const stepKey = getStepKeyForKind(event?.kind)
|
|
165
|
+
const status = flow.activeStepKey !== '' && flow.activeStepKey === stepKey && index === iterationTelemetry.length - 1
|
|
166
|
+
? 'active'
|
|
167
|
+
: normalizeEventStatus(event?.status)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
id: `${String(event?.kind ?? 'event')}-${index}`,
|
|
171
|
+
index,
|
|
172
|
+
kind: String(event?.kind ?? ''),
|
|
173
|
+
label: getLabelForKind(event?.kind),
|
|
174
|
+
stepKey,
|
|
175
|
+
status,
|
|
176
|
+
role: String(event?.role ?? ''),
|
|
177
|
+
terminalReason: String(event?.terminalReason ?? ''),
|
|
178
|
+
notes: String(event?.notes ?? ''),
|
|
179
|
+
iteration: Number(event?.iteration) || currentIteration,
|
|
180
|
+
phase: String(event?.phase ?? ''),
|
|
181
|
+
timestamp: String(event?.timestamp ?? ''),
|
|
182
|
+
retryCount: Number.isFinite(Number(event?.retryCount)) ? Number(event.retryCount) : 0,
|
|
183
|
+
event,
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const edges = nodes.slice(1).map((node, index) => ({
|
|
188
|
+
from: nodes[index].id,
|
|
189
|
+
to: node.id,
|
|
190
|
+
}))
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
iteration: currentIteration,
|
|
194
|
+
nodes,
|
|
195
|
+
edges,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function formatActiveLabel(activeRun, flow) {
|
|
200
|
+
const activeStepKey = flow?.activeStepKey || getStepKeyForActiveRun(activeRun)
|
|
201
|
+
if (activeStepKey !== '') {
|
|
202
|
+
const step = FLOW_STEPS.find((entry) => entry.key === activeStepKey)
|
|
203
|
+
if (step) {
|
|
204
|
+
return step.label
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const status = String(activeRun?.status ?? '').trim()
|
|
209
|
+
if (status === 'idle') {
|
|
210
|
+
return 'Idle'
|
|
211
|
+
}
|
|
212
|
+
if (status === 'starting') {
|
|
213
|
+
return 'Starting'
|
|
214
|
+
}
|
|
215
|
+
if (status === 'starting_iteration') {
|
|
216
|
+
return 'Starting Iteration'
|
|
217
|
+
}
|
|
218
|
+
return status === '' ? 'Unknown' : status
|
|
219
|
+
}
|