@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.
@@ -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
@@ -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
+ }