@sebastianandreasson/pi-autonomous-agents 0.2.0 → 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/README.md +12 -1
- package/SETUP.md +6 -0
- package/docs/PI_SUPERVISOR.md +7 -9
- package/package.json +3 -3
- package/pi.config.json +1 -0
- package/src/pi-client.mjs +37 -0
- package/src/pi-config.mjs +48 -17
- package/src/pi-history.mjs +2 -0
- package/src/pi-preflight.mjs +48 -17
- package/src/pi-prompts.mjs +292 -103
- package/src/pi-repo.mjs +6 -3
- package/src/pi-rpc-adapter.mjs +31 -0
- package/src/pi-supervisor.mjs +408 -26
- package/src/pi-telemetry.mjs +14 -1
- package/templates/pi.config.example.json +2 -1
package/src/pi-supervisor.mjs
CHANGED
|
@@ -79,6 +79,14 @@ function printTerminalSummary(config, summary) {
|
|
|
79
79
|
lines.push(`[PI supervisor] notes=${summary.notes}`)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
if (summary.terminalReason) {
|
|
83
|
+
lines.push(`[PI supervisor] terminal_reason=${summary.terminalReason}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (summary.commitPlanFound !== undefined) {
|
|
87
|
+
lines.push(`[PI supervisor] commit_plan_found=${summary.commitPlanFound}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
if (summary.sessionId) {
|
|
83
91
|
lines.push(`[PI supervisor] session=${summary.sessionId}`)
|
|
84
92
|
}
|
|
@@ -87,6 +95,14 @@ function printTerminalSummary(config, summary) {
|
|
|
87
95
|
lines.push(`[PI supervisor] last_output=${summary.outputPath}`)
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
if (config.lastPromptFile) {
|
|
99
|
+
lines.push(`[PI supervisor] last_prompt=${toDisplayPath(config, config.lastPromptFile)}`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (config.lastIterationSummaryFile) {
|
|
103
|
+
lines.push(`[PI supervisor] iteration_summary=${toDisplayPath(config, config.lastIterationSummaryFile)}`)
|
|
104
|
+
}
|
|
105
|
+
|
|
90
106
|
process.stderr.write(`${lines.join('\n')}\n`)
|
|
91
107
|
}
|
|
92
108
|
|
|
@@ -109,6 +125,98 @@ function parseTesterVerdict(output) {
|
|
|
109
125
|
return match?.[1]?.toUpperCase() ?? 'UNKNOWN'
|
|
110
126
|
}
|
|
111
127
|
|
|
128
|
+
function buildRetryReason(invocation) {
|
|
129
|
+
const loopSignature = String(invocation?.result?.loopSignature ?? '')
|
|
130
|
+
const notes = String(invocation?.result?.notes ?? '')
|
|
131
|
+
|
|
132
|
+
if (loopSignature.startsWith('same_path:')) {
|
|
133
|
+
const target = loopSignature.slice('same_path:'.length)
|
|
134
|
+
return `The previous turn got stuck repeatedly editing ${target}. Reread ${target} exactly once before any new edit. Switch approach. Do not attempt another exact oldText patch on ${target} unless the file changed since the failed attempt.`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (notes.includes('loop_detected=')) {
|
|
138
|
+
return `The previous turn got stuck repeating the same tool call (${notes}). Continue from the current repo state without rereading the same file over and over.`
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return 'The previous turn stalled or timed out. Continue from the current repo state.'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatIterationSummary(summary) {
|
|
145
|
+
return `${JSON.stringify(summary, null, 2)}\n`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function writeIterationSummary(config, summary) {
|
|
149
|
+
await writeTextFile(config.lastIterationSummaryFile, formatIterationSummary(summary))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function createIterationSummary({
|
|
153
|
+
iteration,
|
|
154
|
+
phase,
|
|
155
|
+
task,
|
|
156
|
+
repoChanged,
|
|
157
|
+
developerStatus,
|
|
158
|
+
testerStatus,
|
|
159
|
+
testerVerdict,
|
|
160
|
+
verificationStatus,
|
|
161
|
+
commitPlanFound,
|
|
162
|
+
gitFinalizeStatus,
|
|
163
|
+
visualStatus,
|
|
164
|
+
terminalReason,
|
|
165
|
+
sessionId,
|
|
166
|
+
developerModel,
|
|
167
|
+
testerModel,
|
|
168
|
+
visualModel,
|
|
169
|
+
}) {
|
|
170
|
+
return {
|
|
171
|
+
iteration,
|
|
172
|
+
phase,
|
|
173
|
+
task,
|
|
174
|
+
repoChanged,
|
|
175
|
+
developerStatus,
|
|
176
|
+
testerStatus,
|
|
177
|
+
testerVerdict,
|
|
178
|
+
verificationStatus,
|
|
179
|
+
commitPlanFound,
|
|
180
|
+
gitFinalizeStatus,
|
|
181
|
+
visualStatus,
|
|
182
|
+
terminalReason,
|
|
183
|
+
sessionId,
|
|
184
|
+
developerModel,
|
|
185
|
+
testerModel,
|
|
186
|
+
visualModel,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function didInvocationCreateCommit(invocation) {
|
|
191
|
+
return invocation?.beforeSnapshot?.head !== invocation?.afterSnapshot?.head
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function clampPromptLines(text, maxLines) {
|
|
195
|
+
const normalized = String(text ?? '').trim()
|
|
196
|
+
if (normalized === '') {
|
|
197
|
+
return ''
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const lines = normalized.split('\n')
|
|
201
|
+
if (!Number.isFinite(maxLines) || maxLines <= 0 || lines.length <= maxLines) {
|
|
202
|
+
return normalized
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const remaining = lines.length - maxLines
|
|
206
|
+
return `${lines.slice(0, maxLines).join('\n')}\n... (${remaining} more lines omitted)`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function compactNotePartsForPrompt(config, noteParts, fallback = '(none provided)') {
|
|
210
|
+
const items = Array.isArray(noteParts) ? noteParts.filter(Boolean) : []
|
|
211
|
+
if (items.length === 0) {
|
|
212
|
+
return fallback
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const maxItems = Math.min(6, items.length)
|
|
216
|
+
const selected = items.slice(-maxItems)
|
|
217
|
+
return clampPromptLines(selected.join('\n'), Number(config.maxPromptNotesLines) || 16)
|
|
218
|
+
}
|
|
219
|
+
|
|
112
220
|
function isInfrastructureVerificationFailure(output) {
|
|
113
221
|
const text = String(output ?? '')
|
|
114
222
|
return [
|
|
@@ -142,6 +250,16 @@ async function runAgentInvocation({
|
|
|
142
250
|
}) {
|
|
143
251
|
const beforeSnapshot = getRepoSnapshot(config.cwd)
|
|
144
252
|
const resolvedModel = resolveRoleModel(config, role)
|
|
253
|
+
const promptSnapshot = [
|
|
254
|
+
`role=${role}`,
|
|
255
|
+
`kind=${kind}`,
|
|
256
|
+
`phase=${phase}`,
|
|
257
|
+
`reason=${reason}`,
|
|
258
|
+
`model=${resolvedModel.model || '(PI default)'}`,
|
|
259
|
+
'',
|
|
260
|
+
prompt,
|
|
261
|
+
].join('\n')
|
|
262
|
+
await writeTextFile(config.lastPromptFile, `${promptSnapshot}\n`)
|
|
145
263
|
const result = await runAgentTurn({
|
|
146
264
|
config,
|
|
147
265
|
model: resolvedModel.model,
|
|
@@ -178,6 +296,17 @@ async function runAgentInvocation({
|
|
|
178
296
|
changedFilesCount: changedFiles.length,
|
|
179
297
|
verificationStatus,
|
|
180
298
|
retryCount,
|
|
299
|
+
role,
|
|
300
|
+
model: resolvedModel.model || '(PI default)',
|
|
301
|
+
toolCalls: result.toolCalls ?? 0,
|
|
302
|
+
toolErrors: result.toolErrors ?? 0,
|
|
303
|
+
messageUpdates: result.messageUpdates ?? 0,
|
|
304
|
+
stopReason: result.stopReason ?? '',
|
|
305
|
+
loopDetected: result.loopDetected === true,
|
|
306
|
+
loopSignature: result.loopSignature ?? '',
|
|
307
|
+
testerVerdict: '',
|
|
308
|
+
commitPlanFound: '',
|
|
309
|
+
terminalReason: result.terminalReason ?? '',
|
|
181
310
|
notes: `${result.notes} role=${role} model=${resolvedModel.model || '(PI default)'}`.trim(),
|
|
182
311
|
})
|
|
183
312
|
|
|
@@ -202,7 +331,7 @@ async function readLatestVisualFeedback(config) {
|
|
|
202
331
|
if (trimmed === '') {
|
|
203
332
|
return ''
|
|
204
333
|
}
|
|
205
|
-
return trimmed.
|
|
334
|
+
return clampPromptLines(trimmed, Number(config.maxVisualFeedbackLines) || 20)
|
|
206
335
|
}
|
|
207
336
|
|
|
208
337
|
async function readLatestTesterFeedback(config) {
|
|
@@ -211,7 +340,7 @@ async function readLatestTesterFeedback(config) {
|
|
|
211
340
|
if (trimmed === '') {
|
|
212
341
|
return ''
|
|
213
342
|
}
|
|
214
|
-
return trimmed.
|
|
343
|
+
return clampPromptLines(trimmed, Number(config.maxTesterFeedbackLines) || 32)
|
|
215
344
|
}
|
|
216
345
|
|
|
217
346
|
async function writeTesterFeedback(config, { iteration, phase, task, source, status, output }) {
|
|
@@ -241,17 +370,43 @@ async function writeTesterFeedback(config, { iteration, phase, task, source, sta
|
|
|
241
370
|
|
|
242
371
|
function parseCommitPlan(output) {
|
|
243
372
|
const raw = String(output ?? '')
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
?
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
373
|
+
const lines = raw.split('\n')
|
|
374
|
+
const messageLine = lines.find((line) => /^\s*(?:[-*]\s+)?COMMIT_MESSAGE:\s*(.+?)\s*$/i.test(line))
|
|
375
|
+
const message = messageLine
|
|
376
|
+
? messageLine.replace(/^\s*(?:[-*]\s+)?COMMIT_MESSAGE:\s*/i, '').trim()
|
|
377
|
+
: ''
|
|
378
|
+
|
|
379
|
+
const filesStartIndex = lines.findIndex((line) => /^\s*(?:[-*]\s+)?COMMIT_FILES:\s*$/i.test(line))
|
|
380
|
+
const files = []
|
|
381
|
+
if (filesStartIndex >= 0) {
|
|
382
|
+
for (const line of lines.slice(filesStartIndex + 1)) {
|
|
383
|
+
const trimmed = line.trim()
|
|
384
|
+
if (trimmed === '') {
|
|
385
|
+
if (files.length > 0) {
|
|
386
|
+
break
|
|
387
|
+
}
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
390
|
+
if (/^VERDICT:/i.test(trimmed)) {
|
|
391
|
+
break
|
|
392
|
+
}
|
|
393
|
+
if (/^(?:[-*]\s+)?[A-Z_]+:\s*/.test(trimmed)) {
|
|
394
|
+
break
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const normalized = trimmed
|
|
398
|
+
.replace(/^[-*]\s+/, '')
|
|
399
|
+
.replace(/^\d+\.\s+/, '')
|
|
400
|
+
.trim()
|
|
401
|
+
|
|
402
|
+
if (normalized !== '') {
|
|
403
|
+
files.push(normalized)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
252
407
|
|
|
253
408
|
return {
|
|
254
|
-
message
|
|
409
|
+
message,
|
|
255
410
|
files: [...new Set(files)],
|
|
256
411
|
}
|
|
257
412
|
}
|
|
@@ -275,6 +430,7 @@ async function runHarnessGitFinalize({
|
|
|
275
430
|
const commitMessage = String(commitPlan.message ?? '').trim()
|
|
276
431
|
let status = 'success'
|
|
277
432
|
let notes = ''
|
|
433
|
+
let terminalReason = 'commit_created'
|
|
278
434
|
const cleanupNewlyStagedFiles = (stagedBefore, stagedNow) => {
|
|
279
435
|
const stagedBeforeSet = new Set(stagedBefore)
|
|
280
436
|
const newlyStagedFiles = stagedNow.filter((file) => !stagedBeforeSet.has(file))
|
|
@@ -288,6 +444,7 @@ async function runHarnessGitFinalize({
|
|
|
288
444
|
if (commitMessage === '' || requestedFiles.length === 0) {
|
|
289
445
|
status = 'stalled'
|
|
290
446
|
notes = 'commit_plan_missing=true'
|
|
447
|
+
terminalReason = 'awaiting_commit_plan'
|
|
291
448
|
} else {
|
|
292
449
|
const stagedBefore = listStagedFiles(config.cwd)
|
|
293
450
|
const unrelatedStagedBefore = stagedBefore.filter((file) => !requestedFiles.includes(file))
|
|
@@ -295,11 +452,13 @@ async function runHarnessGitFinalize({
|
|
|
295
452
|
if (unrelatedStagedBefore.length > 0) {
|
|
296
453
|
status = 'blocked'
|
|
297
454
|
notes = `commit_blocked_unrelated_staged_files=${unrelatedStagedBefore.join(',')}`
|
|
455
|
+
terminalReason = 'commit_finalize_blocked_unrelated_staged'
|
|
298
456
|
} else {
|
|
299
457
|
const filesToStage = requestedFiles.filter((file) => dirtyFiles.has(file))
|
|
300
458
|
if (filesToStage.length === 0) {
|
|
301
459
|
status = 'stalled'
|
|
302
460
|
notes = 'commit_plan_no_dirty_files=true'
|
|
461
|
+
terminalReason = 'commit_plan_no_dirty_files'
|
|
303
462
|
} else {
|
|
304
463
|
try {
|
|
305
464
|
stageFiles(config.cwd, filesToStage)
|
|
@@ -310,18 +469,22 @@ async function runHarnessGitFinalize({
|
|
|
310
469
|
const cleanedFiles = cleanupNewlyStagedFiles(stagedBefore, stagedAfter)
|
|
311
470
|
status = 'blocked'
|
|
312
471
|
notes = `commit_blocked_unexpected_staged_files=${unexpectedStaged.join(',')} unstaged_cleanup=${cleanedFiles.join(',')}`.trim()
|
|
472
|
+
terminalReason = 'commit_finalize_blocked_unexpected_staged'
|
|
313
473
|
} else if (!stagedAfter.some((file) => requestedFiles.includes(file))) {
|
|
314
474
|
const cleanedFiles = cleanupNewlyStagedFiles(stagedBefore, stagedAfter)
|
|
315
475
|
status = 'stalled'
|
|
316
476
|
notes = `commit_plan_failed_to_stage=true unstaged_cleanup=${cleanedFiles.join(',')}`.trim()
|
|
477
|
+
terminalReason = 'commit_plan_failed_to_stage'
|
|
317
478
|
} else {
|
|
318
479
|
commitStagedFiles(config.cwd, commitMessage)
|
|
319
480
|
notes = `commit_created=true files=${filesToStage.join(',')} message=${commitMessage}`
|
|
481
|
+
terminalReason = 'commit_created'
|
|
320
482
|
}
|
|
321
483
|
} catch (error) {
|
|
322
484
|
const cleanedFiles = cleanupNewlyStagedFiles(stagedBefore, listStagedFiles(config.cwd))
|
|
323
485
|
status = 'failed'
|
|
324
486
|
notes = `commit_failed=${formatExecError(error)}${cleanedFiles.length > 0 ? ` unstaged_cleanup=${cleanedFiles.join(',')}` : ''}`
|
|
487
|
+
terminalReason = 'commit_finalize_failed'
|
|
325
488
|
}
|
|
326
489
|
}
|
|
327
490
|
}
|
|
@@ -329,12 +492,16 @@ async function runHarnessGitFinalize({
|
|
|
329
492
|
|
|
330
493
|
const afterSnapshot = getRepoSnapshot(config.cwd)
|
|
331
494
|
const changedFiles = listChangedFiles(config.cwd)
|
|
495
|
+
const finalStatus = status === 'success' && beforeSnapshot.head === afterSnapshot.head ? 'stalled' : status
|
|
496
|
+
if (status === 'success' && finalStatus === 'stalled') {
|
|
497
|
+
terminalReason = 'commit_not_created'
|
|
498
|
+
}
|
|
332
499
|
|
|
333
500
|
await recordEvent(config, {
|
|
334
501
|
iteration,
|
|
335
502
|
phase,
|
|
336
503
|
kind: 'git_finalize',
|
|
337
|
-
status,
|
|
504
|
+
status: finalStatus,
|
|
338
505
|
transport: 'local',
|
|
339
506
|
sessionId: '',
|
|
340
507
|
timedOut: false,
|
|
@@ -346,12 +513,24 @@ async function runHarnessGitFinalize({
|
|
|
346
513
|
changedFilesCount: changedFiles.length,
|
|
347
514
|
verificationStatus: 'not_run',
|
|
348
515
|
retryCount: 0,
|
|
516
|
+
role: '',
|
|
517
|
+
model: '',
|
|
518
|
+
toolCalls: 0,
|
|
519
|
+
toolErrors: 0,
|
|
520
|
+
messageUpdates: 0,
|
|
521
|
+
stopReason: '',
|
|
522
|
+
loopDetected: false,
|
|
523
|
+
loopSignature: '',
|
|
524
|
+
testerVerdict: '',
|
|
525
|
+
commitPlanFound: requestedFiles.length > 0,
|
|
526
|
+
terminalReason,
|
|
349
527
|
notes,
|
|
350
528
|
})
|
|
351
529
|
|
|
352
530
|
return {
|
|
353
|
-
status:
|
|
531
|
+
status: finalStatus,
|
|
354
532
|
notes,
|
|
533
|
+
terminalReason,
|
|
355
534
|
}
|
|
356
535
|
}
|
|
357
536
|
|
|
@@ -385,6 +564,17 @@ async function runVerificationStep({ config, iteration, phase, kind }) {
|
|
|
385
564
|
changedFilesCount: changedFiles.length,
|
|
386
565
|
verificationStatus: verification.status,
|
|
387
566
|
retryCount: 0,
|
|
567
|
+
role: '',
|
|
568
|
+
model: '',
|
|
569
|
+
toolCalls: 0,
|
|
570
|
+
toolErrors: 0,
|
|
571
|
+
messageUpdates: 0,
|
|
572
|
+
stopReason: '',
|
|
573
|
+
loopDetected: false,
|
|
574
|
+
loopSignature: '',
|
|
575
|
+
testerVerdict: '',
|
|
576
|
+
commitPlanFound: '',
|
|
577
|
+
terminalReason: `verification_${verification.status}`,
|
|
388
578
|
notes: verificationNotes,
|
|
389
579
|
})
|
|
390
580
|
|
|
@@ -438,6 +628,7 @@ async function runMainTurnWithRetries({ config, iteration, phase, sessionId, ses
|
|
|
438
628
|
result: {
|
|
439
629
|
...invocation.result,
|
|
440
630
|
status: 'stalled',
|
|
631
|
+
terminalReason: 'no_repo_change',
|
|
441
632
|
notes: `${invocation.result.notes} no_repo_change=true`,
|
|
442
633
|
},
|
|
443
634
|
}
|
|
@@ -448,9 +639,7 @@ async function runMainTurnWithRetries({ config, iteration, phase, sessionId, ses
|
|
|
448
639
|
}
|
|
449
640
|
|
|
450
641
|
reason = shouldRetryForTimeout
|
|
451
|
-
? invocation
|
|
452
|
-
? `The previous turn got stuck repeating the same tool call (${invocation.result.notes}). Continue from the current repo state without rereading the same file over and over.`
|
|
453
|
-
: 'The previous turn stalled or timed out. Continue from the current repo state.'
|
|
642
|
+
? buildRetryReason(invocation)
|
|
454
643
|
: 'The previous turn ended without changing the repo. Continue and complete one coherent task.'
|
|
455
644
|
prompt = buildSteeringPrompt(config, reason, {
|
|
456
645
|
visualFeedback: await readLatestVisualFeedback(config),
|
|
@@ -469,7 +658,7 @@ async function runMainTurnWithRetries({ config, iteration, phase, sessionId, ses
|
|
|
469
658
|
async function runFixTurn({ config, iteration, phase, sessionId, sessionFile, testerOutput }) {
|
|
470
659
|
const fixPrompt = buildFixPrompt(
|
|
471
660
|
config,
|
|
472
|
-
testerOutput
|
|
661
|
+
clampPromptLines(testerOutput, Number(config.maxVerificationExcerptLines) || 40),
|
|
473
662
|
{
|
|
474
663
|
visualFeedback: await readLatestVisualFeedback(config),
|
|
475
664
|
testerFeedback: await readLatestTesterFeedback(config),
|
|
@@ -600,22 +789,38 @@ async function runTesterTurn({
|
|
|
600
789
|
const commitPlan = parseCommitPlan(invocation.result.output)
|
|
601
790
|
const notesWithVerdict = `${invocation.result.notes} tester_verdict=${verdict} commit_plan_files=${commitPlan.files.length}`.trim()
|
|
602
791
|
let testerStatus = invocation.result.status
|
|
792
|
+
let terminalReason = invocation.result.terminalReason || ''
|
|
603
793
|
|
|
604
794
|
if (testerStatus === 'success' && verdict === 'FAIL') {
|
|
605
795
|
testerStatus = 'failed'
|
|
796
|
+
terminalReason = 'tester_verdict_fail'
|
|
606
797
|
} else if (testerStatus === 'success' && verdict === 'BLOCKED') {
|
|
607
798
|
testerStatus = 'stalled'
|
|
799
|
+
terminalReason = 'tester_verdict_blocked'
|
|
608
800
|
} else if (testerStatus === 'success' && verdict === 'UNKNOWN') {
|
|
609
801
|
testerStatus = 'stalled'
|
|
802
|
+
terminalReason = 'tester_verdict_unknown'
|
|
803
|
+
} else if (testerStatus === 'success' && config.commitMode === 'plan') {
|
|
804
|
+
terminalReason = commitPlan.message !== '' && commitPlan.files.length > 0
|
|
805
|
+
? 'tester_pass_with_commit_plan'
|
|
806
|
+
: 'awaiting_commit_plan'
|
|
807
|
+
} else if (testerStatus === 'success') {
|
|
808
|
+
terminalReason = didInvocationCreateCommit(invocation)
|
|
809
|
+
? 'tester_pass_with_agent_commit'
|
|
810
|
+
: invocation.repoChanged
|
|
811
|
+
? 'tester_left_uncommitted_changes'
|
|
812
|
+
: 'awaiting_agent_commit'
|
|
610
813
|
}
|
|
611
814
|
|
|
612
815
|
return {
|
|
613
816
|
...invocation,
|
|
614
817
|
testerVerdict: verdict,
|
|
818
|
+
commitPlanFound: commitPlan.message !== '' && commitPlan.files.length > 0,
|
|
615
819
|
commitPlan,
|
|
616
820
|
result: {
|
|
617
821
|
...invocation.result,
|
|
618
822
|
status: testerStatus,
|
|
823
|
+
terminalReason,
|
|
619
824
|
notes: notesWithVerdict,
|
|
620
825
|
},
|
|
621
826
|
}
|
|
@@ -657,22 +862,30 @@ async function runTesterCommitTurn({
|
|
|
657
862
|
const commitPlan = parseCommitPlan(invocation.result.output)
|
|
658
863
|
const notesWithVerdict = `${invocation.result.notes} tester_verdict=${verdict} commit_plan_files=${commitPlan.files.length}`.trim()
|
|
659
864
|
let testerStatus = invocation.result.status
|
|
865
|
+
let terminalReason = invocation.result.terminalReason || ''
|
|
660
866
|
|
|
661
867
|
if (testerStatus === 'success' && verdict === 'BLOCKED') {
|
|
662
868
|
testerStatus = 'stalled'
|
|
869
|
+
terminalReason = 'tester_commit_blocked'
|
|
663
870
|
} else if (testerStatus === 'success' && verdict !== 'PASS') {
|
|
664
871
|
testerStatus = 'stalled'
|
|
872
|
+
terminalReason = 'tester_commit_missing_pass'
|
|
665
873
|
} else if (testerStatus === 'success' && (commitPlan.message === '' || commitPlan.files.length === 0)) {
|
|
666
874
|
testerStatus = 'stalled'
|
|
875
|
+
terminalReason = 'awaiting_commit_plan'
|
|
876
|
+
} else if (testerStatus === 'success') {
|
|
877
|
+
terminalReason = 'tester_commit_plan_ready'
|
|
667
878
|
}
|
|
668
879
|
|
|
669
880
|
return {
|
|
670
881
|
...invocation,
|
|
671
882
|
testerVerdict: verdict,
|
|
883
|
+
commitPlanFound: commitPlan.message !== '' && commitPlan.files.length > 0,
|
|
672
884
|
commitPlan,
|
|
673
885
|
result: {
|
|
674
886
|
...invocation.result,
|
|
675
887
|
status: testerStatus,
|
|
888
|
+
terminalReason,
|
|
676
889
|
notes: notesWithVerdict,
|
|
677
890
|
},
|
|
678
891
|
}
|
|
@@ -701,6 +914,17 @@ async function runVisualReview({ config, iteration, phase, task, changedFiles })
|
|
|
701
914
|
changedFilesCount: changedFiles.length,
|
|
702
915
|
verificationStatus: 'not_run',
|
|
703
916
|
retryCount: 0,
|
|
917
|
+
role: '',
|
|
918
|
+
model: '',
|
|
919
|
+
toolCalls: 0,
|
|
920
|
+
toolErrors: 0,
|
|
921
|
+
messageUpdates: 0,
|
|
922
|
+
stopReason: '',
|
|
923
|
+
loopDetected: false,
|
|
924
|
+
loopSignature: '',
|
|
925
|
+
testerVerdict: '',
|
|
926
|
+
commitPlanFound: '',
|
|
927
|
+
terminalReason: `visual_capture_${capture.status}`,
|
|
704
928
|
notes: capture.status === 'passed'
|
|
705
929
|
? `screenshots=${capture.screenshots.length} manifest=${capture.manifestPath}`
|
|
706
930
|
: capture.output.trim().split('\n').slice(-8).join(' '),
|
|
@@ -777,6 +1001,17 @@ async function runVisualReview({ config, iteration, phase, task, changedFiles })
|
|
|
777
1001
|
changedFilesCount: changedFiles.length,
|
|
778
1002
|
verificationStatus: 'not_run',
|
|
779
1003
|
retryCount: 0,
|
|
1004
|
+
role: 'visualReview',
|
|
1005
|
+
model: visualReviewModel.model || '(unset)',
|
|
1006
|
+
toolCalls: 0,
|
|
1007
|
+
toolErrors: 0,
|
|
1008
|
+
messageUpdates: 0,
|
|
1009
|
+
stopReason: '',
|
|
1010
|
+
loopDetected: false,
|
|
1011
|
+
loopSignature: '',
|
|
1012
|
+
testerVerdict: verdict,
|
|
1013
|
+
commitPlanFound: '',
|
|
1014
|
+
terminalReason: `visual_review_${status}`,
|
|
780
1015
|
notes: `verdict=${verdict} feedback=${config.visualFeedbackFile} role=visualReview model=${visualReviewModel.model || '(unset)'}`.trim(),
|
|
781
1016
|
})
|
|
782
1017
|
|
|
@@ -791,6 +1026,7 @@ async function runIteration({ config, state, iteration }) {
|
|
|
791
1026
|
const testerModelName = resolveRoleModelName(config, 'tester')
|
|
792
1027
|
const visualReviewRoleModel = resolveRoleModel(config, 'visualReview')
|
|
793
1028
|
const visualModelName = visualReviewRoleModel.model
|
|
1029
|
+
const iterationStartSnapshot = getRepoSnapshot(config.cwd)
|
|
794
1030
|
const taskInfo = findFirstUncheckedTaskInfo(config.taskFile)
|
|
795
1031
|
if (!taskInfo.hasUncheckedTasks) {
|
|
796
1032
|
await appendLog(config.logFile, 'No unchecked tasks remain in TODOS.md')
|
|
@@ -808,9 +1044,16 @@ async function runIteration({ config, state, iteration }) {
|
|
|
808
1044
|
summary: {
|
|
809
1045
|
iteration,
|
|
810
1046
|
phase: taskInfo.phase || 'complete',
|
|
1047
|
+
task: '',
|
|
1048
|
+
repoChanged: false,
|
|
811
1049
|
developerStatus: 'complete',
|
|
812
1050
|
testerStatus: 'not_needed',
|
|
1051
|
+
testerVerdict: 'NOT_RUN',
|
|
813
1052
|
verificationStatus: 'not_needed',
|
|
1053
|
+
commitPlanFound: false,
|
|
1054
|
+
gitFinalizeStatus: 'not_run',
|
|
1055
|
+
visualStatus: 'not_run',
|
|
1056
|
+
terminalReason: 'all_tasks_complete',
|
|
814
1057
|
notes: 'No unchecked tasks remain in TODOS.md.',
|
|
815
1058
|
sessionId: state.sessionId || '',
|
|
816
1059
|
outputPath: config.lastAgentOutputFile,
|
|
@@ -854,13 +1097,18 @@ async function runIteration({ config, state, iteration }) {
|
|
|
854
1097
|
let sessionFile = mainInvocation.result.sessionFile || startingSessionFile
|
|
855
1098
|
let developerStatus = mainInvocation.result.status
|
|
856
1099
|
let testerStatus = 'not_run'
|
|
1100
|
+
let testerVerdict = 'NOT_RUN'
|
|
857
1101
|
let finalVerificationStatus = 'not_run'
|
|
858
1102
|
let visualStatus = 'not_run'
|
|
1103
|
+
let commitPlanFound = false
|
|
1104
|
+
let gitFinalizeStatus = 'not_run'
|
|
1105
|
+
let terminalReason = mainInvocation.result.terminalReason || ''
|
|
859
1106
|
const noteParts = [`developer: ${mainInvocation.result.notes}`]
|
|
860
1107
|
|
|
861
1108
|
if (mainInvocation.result.status === 'success' && config.transport === 'mock') {
|
|
862
1109
|
testerStatus = 'skipped'
|
|
863
1110
|
finalVerificationStatus = 'skipped'
|
|
1111
|
+
terminalReason = 'mock_completed'
|
|
864
1112
|
} else if (mainInvocation.result.status === 'success') {
|
|
865
1113
|
const developerVerification = await runDeveloperVerificationAndFix({
|
|
866
1114
|
config,
|
|
@@ -875,6 +1123,13 @@ async function runIteration({ config, state, iteration }) {
|
|
|
875
1123
|
sessionFile = developerVerification.sessionFile
|
|
876
1124
|
developerStatus = developerVerification.developerStatus
|
|
877
1125
|
finalVerificationStatus = developerVerification.verificationStatus
|
|
1126
|
+
if (developerStatus !== 'success') {
|
|
1127
|
+
terminalReason = developerStatus === 'blocked'
|
|
1128
|
+
? 'verification_infrastructure_failure'
|
|
1129
|
+
: 'developer_fix_incomplete'
|
|
1130
|
+
} else if (finalVerificationStatus !== 'passed' && finalVerificationStatus !== 'not_run') {
|
|
1131
|
+
terminalReason = `verification_${finalVerificationStatus}`
|
|
1132
|
+
}
|
|
878
1133
|
|
|
879
1134
|
if (developerVerification.feedbackSource && developerVerification.verificationOutput.trim() !== '') {
|
|
880
1135
|
await writeTesterFeedback(config, {
|
|
@@ -894,11 +1149,14 @@ async function runIteration({ config, state, iteration }) {
|
|
|
894
1149
|
phase,
|
|
895
1150
|
task,
|
|
896
1151
|
changedFiles: listChangedFiles(config.cwd),
|
|
897
|
-
developerNotes:
|
|
1152
|
+
developerNotes: compactNotePartsForPrompt(config, noteParts),
|
|
898
1153
|
reason: 'tester_review_after_basic_smoke_passed',
|
|
899
1154
|
})
|
|
900
1155
|
|
|
901
1156
|
testerStatus = testerInvocation.result.status
|
|
1157
|
+
testerVerdict = testerInvocation.testerVerdict
|
|
1158
|
+
commitPlanFound = testerInvocation.commitPlanFound === true
|
|
1159
|
+
terminalReason = testerInvocation.result.terminalReason || terminalReason
|
|
902
1160
|
noteParts.push(`tester: ${testerInvocation.result.notes}`)
|
|
903
1161
|
await writeTesterFeedback(config, {
|
|
904
1162
|
iteration,
|
|
@@ -911,18 +1169,21 @@ async function runIteration({ config, state, iteration }) {
|
|
|
911
1169
|
|
|
912
1170
|
let commitPlan = testerInvocation.commitPlan
|
|
913
1171
|
|
|
914
|
-
if (testerStatus === 'success' && (commitPlan.message === '' || commitPlan.files.length === 0)) {
|
|
1172
|
+
if (testerStatus === 'success' && config.commitMode === 'plan' && (commitPlan.message === '' || commitPlan.files.length === 0)) {
|
|
915
1173
|
const testerCommitInvocation = await runTesterCommitTurn({
|
|
916
1174
|
config,
|
|
917
1175
|
iteration,
|
|
918
1176
|
phase,
|
|
919
1177
|
task,
|
|
920
1178
|
changedFiles: listChangedFiles(config.cwd),
|
|
921
|
-
developerNotes:
|
|
1179
|
+
developerNotes: compactNotePartsForPrompt(config, noteParts),
|
|
922
1180
|
reason: 'tester_passed_without_commit',
|
|
923
1181
|
})
|
|
924
1182
|
|
|
925
1183
|
testerStatus = testerCommitInvocation.result.status
|
|
1184
|
+
testerVerdict = testerCommitInvocation.testerVerdict
|
|
1185
|
+
commitPlanFound = testerCommitInvocation.commitPlanFound === true
|
|
1186
|
+
terminalReason = testerCommitInvocation.result.terminalReason || terminalReason
|
|
926
1187
|
noteParts.push(`tester_commit: ${testerCommitInvocation.result.notes}`)
|
|
927
1188
|
await writeTesterFeedback(config, {
|
|
928
1189
|
iteration,
|
|
@@ -935,7 +1196,7 @@ async function runIteration({ config, state, iteration }) {
|
|
|
935
1196
|
commitPlan = testerCommitInvocation.commitPlan
|
|
936
1197
|
}
|
|
937
1198
|
|
|
938
|
-
if (testerStatus === 'success') {
|
|
1199
|
+
if (testerStatus === 'success' && config.commitMode === 'plan') {
|
|
939
1200
|
const gitFinalize = await runHarnessGitFinalize({
|
|
940
1201
|
config,
|
|
941
1202
|
iteration,
|
|
@@ -943,10 +1204,27 @@ async function runIteration({ config, state, iteration }) {
|
|
|
943
1204
|
commitPlan,
|
|
944
1205
|
})
|
|
945
1206
|
testerStatus = gitFinalize.status
|
|
1207
|
+
gitFinalizeStatus = gitFinalize.status
|
|
1208
|
+
terminalReason = gitFinalize.terminalReason || terminalReason
|
|
946
1209
|
noteParts.push(`git_finalize: ${gitFinalize.notes}`)
|
|
1210
|
+
} else if (testerStatus === 'success') {
|
|
1211
|
+
if (didInvocationCreateCommit(testerInvocation)) {
|
|
1212
|
+
gitFinalizeStatus = 'committed_by_agent'
|
|
1213
|
+
terminalReason = 'completed_phase_step'
|
|
1214
|
+
} else {
|
|
1215
|
+
testerStatus = 'stalled'
|
|
1216
|
+
gitFinalizeStatus = 'awaiting_agent_commit'
|
|
1217
|
+
terminalReason = testerInvocation.repoChanged
|
|
1218
|
+
? 'tester_left_uncommitted_changes'
|
|
1219
|
+
: 'awaiting_agent_commit'
|
|
1220
|
+
noteParts.push('git_finalize: committed_by_agent=false')
|
|
1221
|
+
}
|
|
947
1222
|
}
|
|
948
1223
|
} else {
|
|
949
1224
|
testerStatus = 'skipped'
|
|
1225
|
+
if (terminalReason === '') {
|
|
1226
|
+
terminalReason = 'tester_skipped_after_verification'
|
|
1227
|
+
}
|
|
950
1228
|
}
|
|
951
1229
|
|
|
952
1230
|
if (testerStatus === 'failed') {
|
|
@@ -956,12 +1234,13 @@ async function runIteration({ config, state, iteration }) {
|
|
|
956
1234
|
phase,
|
|
957
1235
|
sessionId,
|
|
958
1236
|
sessionFile,
|
|
959
|
-
testerOutput: noteParts
|
|
1237
|
+
testerOutput: compactNotePartsForPrompt(config, noteParts),
|
|
960
1238
|
})
|
|
961
1239
|
|
|
962
1240
|
sessionId = fixInvocation.result.sessionId || sessionId
|
|
963
1241
|
sessionFile = fixInvocation.result.sessionFile || sessionFile
|
|
964
1242
|
developerStatus = fixInvocation.result.status
|
|
1243
|
+
terminalReason = fixInvocation.result.terminalReason || 'developer_fix_incomplete'
|
|
965
1244
|
noteParts.push(`developer_fix: ${fixInvocation.result.notes}`)
|
|
966
1245
|
|
|
967
1246
|
if (fixInvocation.result.status === 'success') {
|
|
@@ -976,6 +1255,9 @@ async function runIteration({ config, state, iteration }) {
|
|
|
976
1255
|
})
|
|
977
1256
|
|
|
978
1257
|
testerStatus = testerRecheck.result.status
|
|
1258
|
+
testerVerdict = testerRecheck.testerVerdict
|
|
1259
|
+
commitPlanFound = testerRecheck.commitPlanFound === true
|
|
1260
|
+
terminalReason = testerRecheck.result.terminalReason || terminalReason
|
|
979
1261
|
noteParts.push(`tester_recheck: ${testerRecheck.result.notes}`)
|
|
980
1262
|
await writeTesterFeedback(config, {
|
|
981
1263
|
iteration,
|
|
@@ -988,18 +1270,21 @@ async function runIteration({ config, state, iteration }) {
|
|
|
988
1270
|
|
|
989
1271
|
let commitPlan = testerRecheck.commitPlan
|
|
990
1272
|
|
|
991
|
-
if (testerStatus === 'success' && (commitPlan.message === '' || commitPlan.files.length === 0)) {
|
|
1273
|
+
if (testerStatus === 'success' && config.commitMode === 'plan' && (commitPlan.message === '' || commitPlan.files.length === 0)) {
|
|
992
1274
|
const testerCommitInvocation = await runTesterCommitTurn({
|
|
993
1275
|
config,
|
|
994
1276
|
iteration,
|
|
995
1277
|
phase,
|
|
996
1278
|
task,
|
|
997
1279
|
changedFiles: listChangedFiles(config.cwd),
|
|
998
|
-
developerNotes:
|
|
1280
|
+
developerNotes: compactNotePartsForPrompt(config, noteParts),
|
|
999
1281
|
reason: 'tester_recheck_passed_without_commit',
|
|
1000
1282
|
})
|
|
1001
1283
|
|
|
1002
1284
|
testerStatus = testerCommitInvocation.result.status
|
|
1285
|
+
testerVerdict = testerCommitInvocation.testerVerdict
|
|
1286
|
+
commitPlanFound = testerCommitInvocation.commitPlanFound === true
|
|
1287
|
+
terminalReason = testerCommitInvocation.result.terminalReason || terminalReason
|
|
1003
1288
|
noteParts.push(`tester_commit: ${testerCommitInvocation.result.notes}`)
|
|
1004
1289
|
await writeTesterFeedback(config, {
|
|
1005
1290
|
iteration,
|
|
@@ -1012,7 +1297,7 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1012
1297
|
commitPlan = testerCommitInvocation.commitPlan
|
|
1013
1298
|
}
|
|
1014
1299
|
|
|
1015
|
-
if (testerStatus === 'success') {
|
|
1300
|
+
if (testerStatus === 'success' && config.commitMode === 'plan') {
|
|
1016
1301
|
const gitFinalize = await runHarnessGitFinalize({
|
|
1017
1302
|
config,
|
|
1018
1303
|
iteration,
|
|
@@ -1020,7 +1305,21 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1020
1305
|
commitPlan,
|
|
1021
1306
|
})
|
|
1022
1307
|
testerStatus = gitFinalize.status
|
|
1308
|
+
gitFinalizeStatus = gitFinalize.status
|
|
1309
|
+
terminalReason = gitFinalize.terminalReason || terminalReason
|
|
1023
1310
|
noteParts.push(`git_finalize: ${gitFinalize.notes}`)
|
|
1311
|
+
} else if (testerStatus === 'success') {
|
|
1312
|
+
if (didInvocationCreateCommit(testerRecheck)) {
|
|
1313
|
+
gitFinalizeStatus = 'committed_by_agent'
|
|
1314
|
+
terminalReason = 'completed_phase_step'
|
|
1315
|
+
} else {
|
|
1316
|
+
testerStatus = 'stalled'
|
|
1317
|
+
gitFinalizeStatus = 'awaiting_agent_commit'
|
|
1318
|
+
terminalReason = testerRecheck.repoChanged
|
|
1319
|
+
? 'tester_left_uncommitted_changes'
|
|
1320
|
+
: 'awaiting_agent_commit'
|
|
1321
|
+
noteParts.push('git_finalize: committed_by_agent=false')
|
|
1322
|
+
}
|
|
1024
1323
|
}
|
|
1025
1324
|
|
|
1026
1325
|
if (testerStatus === 'success') {
|
|
@@ -1032,12 +1331,18 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1032
1331
|
})
|
|
1033
1332
|
|
|
1034
1333
|
finalVerificationStatus = reverify.status
|
|
1334
|
+
if (finalVerificationStatus !== 'passed') {
|
|
1335
|
+
terminalReason = `verification_${finalVerificationStatus}`
|
|
1336
|
+
}
|
|
1035
1337
|
}
|
|
1036
1338
|
}
|
|
1037
1339
|
}
|
|
1038
1340
|
} else {
|
|
1039
1341
|
testerStatus = 'not_run'
|
|
1040
1342
|
finalVerificationStatus = 'not_run'
|
|
1343
|
+
if (terminalReason === '') {
|
|
1344
|
+
terminalReason = 'developer_turn_incomplete'
|
|
1345
|
+
}
|
|
1041
1346
|
}
|
|
1042
1347
|
|
|
1043
1348
|
const workflowStatus = deriveWorkflowStatus({
|
|
@@ -1070,6 +1375,9 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1070
1375
|
changedFiles: listChangedFiles(config.cwd),
|
|
1071
1376
|
})
|
|
1072
1377
|
visualStatus = visualReview.status
|
|
1378
|
+
terminalReason = visualReview.status === 'passed'
|
|
1379
|
+
? terminalReason
|
|
1380
|
+
: `visual_review_${visualReview.status}`
|
|
1073
1381
|
noteParts.push(`visual: ${visualReview.notes}`)
|
|
1074
1382
|
} else if (config.visualReviewEnabled) {
|
|
1075
1383
|
visualStatus = 'skipped'
|
|
@@ -1080,6 +1388,22 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1080
1388
|
visualStatus,
|
|
1081
1389
|
})
|
|
1082
1390
|
|
|
1391
|
+
if (finalStatus === 'success') {
|
|
1392
|
+
terminalReason = 'completed_phase_step'
|
|
1393
|
+
} else if (terminalReason === '') {
|
|
1394
|
+
terminalReason = testerStatus === 'failed'
|
|
1395
|
+
? 'tester_verdict_fail'
|
|
1396
|
+
: testerStatus === 'stalled'
|
|
1397
|
+
? 'iteration_stalled'
|
|
1398
|
+
: developerStatus === 'blocked'
|
|
1399
|
+
? 'developer_blocked'
|
|
1400
|
+
: developerStatus === 'failed'
|
|
1401
|
+
? 'developer_failed'
|
|
1402
|
+
: finalVerificationStatus !== 'not_run'
|
|
1403
|
+
? `verification_${finalVerificationStatus}`
|
|
1404
|
+
: 'workflow_incomplete'
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1083
1407
|
const successfulIterations = (
|
|
1084
1408
|
finalStatus === 'success'
|
|
1085
1409
|
? candidateSuccessfulIterations
|
|
@@ -1112,18 +1436,74 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1112
1436
|
|
|
1113
1437
|
await appendLog(
|
|
1114
1438
|
config.logFile,
|
|
1115
|
-
`Finished iteration ${iteration} with status=${finalStatus} verification=${finalVerificationStatus}`
|
|
1439
|
+
`Finished iteration ${iteration} with status=${finalStatus} verification=${finalVerificationStatus} tester_verdict=${testerVerdict} commit_plan_found=${commitPlanFound} terminal_reason=${terminalReason}`
|
|
1116
1440
|
)
|
|
1117
1441
|
|
|
1442
|
+
const iterationEndSnapshot = getRepoSnapshot(config.cwd)
|
|
1443
|
+
const iterationSummary = createIterationSummary({
|
|
1444
|
+
iteration,
|
|
1445
|
+
phase,
|
|
1446
|
+
task,
|
|
1447
|
+
repoChanged: didRepoChange(iterationStartSnapshot, iterationEndSnapshot),
|
|
1448
|
+
developerStatus,
|
|
1449
|
+
testerStatus,
|
|
1450
|
+
testerVerdict,
|
|
1451
|
+
verificationStatus: finalVerificationStatus,
|
|
1452
|
+
commitPlanFound,
|
|
1453
|
+
gitFinalizeStatus,
|
|
1454
|
+
visualStatus,
|
|
1455
|
+
terminalReason,
|
|
1456
|
+
sessionId,
|
|
1457
|
+
developerModel: developerModelName,
|
|
1458
|
+
testerModel: testerModelName,
|
|
1459
|
+
visualModel: visualModelName,
|
|
1460
|
+
})
|
|
1461
|
+
|
|
1462
|
+
await recordEvent(config, {
|
|
1463
|
+
iteration,
|
|
1464
|
+
phase,
|
|
1465
|
+
kind: 'iteration_summary',
|
|
1466
|
+
status: finalStatus,
|
|
1467
|
+
transport: config.transport,
|
|
1468
|
+
sessionId,
|
|
1469
|
+
timedOut: false,
|
|
1470
|
+
exitCode: finalStatus === 'success' ? 0 : 1,
|
|
1471
|
+
durationSeconds: 0,
|
|
1472
|
+
commitBefore: iterationStartSnapshot.head,
|
|
1473
|
+
commitAfter: iterationEndSnapshot.head,
|
|
1474
|
+
repoChanged: iterationSummary.repoChanged,
|
|
1475
|
+
changedFilesCount: listChangedFiles(config.cwd).length,
|
|
1476
|
+
verificationStatus: finalVerificationStatus,
|
|
1477
|
+
retryCount: 0,
|
|
1478
|
+
role: '',
|
|
1479
|
+
model: '',
|
|
1480
|
+
toolCalls: 0,
|
|
1481
|
+
toolErrors: 0,
|
|
1482
|
+
messageUpdates: 0,
|
|
1483
|
+
stopReason: '',
|
|
1484
|
+
loopDetected: false,
|
|
1485
|
+
loopSignature: '',
|
|
1486
|
+
testerVerdict,
|
|
1487
|
+
commitPlanFound,
|
|
1488
|
+
terminalReason,
|
|
1489
|
+
notes: noteParts.join(' | '),
|
|
1490
|
+
})
|
|
1491
|
+
|
|
1118
1492
|
return {
|
|
1119
1493
|
stateUpdate: nextState,
|
|
1120
1494
|
summary: {
|
|
1121
1495
|
iteration,
|
|
1122
1496
|
phase,
|
|
1497
|
+
task,
|
|
1498
|
+
repoChanged: iterationSummary.repoChanged,
|
|
1123
1499
|
developerStatus,
|
|
1124
1500
|
testerStatus,
|
|
1501
|
+
testerVerdict,
|
|
1125
1502
|
verificationStatus: finalVerificationStatus,
|
|
1503
|
+
commitPlanFound,
|
|
1504
|
+
gitFinalizeStatus,
|
|
1126
1505
|
visualStatus,
|
|
1506
|
+
terminalReason,
|
|
1127
1507
|
notes: noteParts.join(' | '),
|
|
1128
1508
|
sessionId,
|
|
1129
1509
|
outputPath: config.lastAgentOutputFile,
|
|
@@ -1134,6 +1514,7 @@ async function runIteration({ config, state, iteration }) {
|
|
|
1134
1514
|
testerModel: testerModelName,
|
|
1135
1515
|
visualModel: visualModelName,
|
|
1136
1516
|
},
|
|
1517
|
+
iterationSummary,
|
|
1137
1518
|
shouldStop: false,
|
|
1138
1519
|
}
|
|
1139
1520
|
}
|
|
@@ -1153,6 +1534,7 @@ async function main() {
|
|
|
1153
1534
|
while (!stopRequested) {
|
|
1154
1535
|
const iteration = state.iteration + 1
|
|
1155
1536
|
const result = await runIteration({ config, state, iteration })
|
|
1537
|
+
await writeIterationSummary(config, result.iterationSummary ?? result.summary)
|
|
1156
1538
|
state = result.stateUpdate
|
|
1157
1539
|
await writeState(config.stateFile, state)
|
|
1158
1540
|
printTerminalSummary(config, result.summary)
|