@@ -142,6 +142,14 @@ function reportText(value: number, summary = "tweak"): string {
142142 return `Done.\n\`\`\`autoresearch\nmetric: ${ value } \nsummary: ${ summary } \n\`\`\`` ;
143143}
144144
145+ function researchText (
146+ summary = "Mapped the execution path" ,
147+ finding = "The metric is computed in the workspace server." ,
148+ nextStep = "Trace the benchmark command" ,
149+ ) : string {
150+ return `Investigated.\n\`\`\`autoresearch\ntype: research\nsummary: ${ summary } \nfinding: ${ finding } \nnext: ${ nextStep } \n\`\`\`` ;
151+ }
152+
145153/** A session `model` config option (and optional `thought_level`). */
146154function modelConfig (
147155 currentValue : string ,
@@ -202,6 +210,14 @@ function completeTurn(text: string, taskRunId = TASK_RUN_ID): void {
202210 } ) ;
203211}
204212
213+ function streamTurnText ( text : string , taskRunId = TASK_RUN_ID ) : void {
214+ const events = sessionStore . getState ( ) . sessions [ taskRunId ] ?. events ?? [ ] ;
215+ sessionStoreSetters . updateSession ( taskRunId , {
216+ isPromptPending : true ,
217+ events : [ ...events , agentChunkEvent ( text ) ] ,
218+ } ) ;
219+ }
220+
205221function runTurn ( text : string , taskRunId = TASK_RUN_ID ) : void {
206222 beginTurn ( taskRunId ) ;
207223 completeTurn ( text , taskRunId ) ;
@@ -251,6 +267,7 @@ function makeRun(
251267 phase : null ,
252268 originalModel : null ,
253269 originalEffort : null ,
270+ researchFindings : [ ] ,
254271 iterations : [ ] ,
255272 startedAt : 1_000 ,
256273 endedAt : null ,
@@ -350,7 +367,7 @@ describe("AutoresearchService", () => {
350367 } ) ;
351368
352369 describe ( "registerRun" , ( ) => {
353- it ( "registers without sending — the kickoff rode the task's initial prompt" , ( ) => {
370+ it ( "registers without sending because the kickoff rode the task initial prompt" , ( ) => {
354371 const run = service . registerRun ( baseConfig ) ;
355372
356373 expect ( run . status ) . toBe ( "running" ) ;
@@ -367,13 +384,145 @@ describe("AutoresearchService", () => {
367384 expect ( sentPrompts [ 0 ] . prompt ) . toContain ( "iteration 2" ) ;
368385 } ) ;
369386
387+ it ( "tracks reports from an initial prompt already in progress" , ( ) => {
388+ beginTurn ( ) ;
389+ service . registerRun ( baseConfig ) ;
390+
391+ streamTurnText ( `${ reportText ( 10 , "baseline" ) } \nIteration 2 starts now.` ) ;
392+ streamTurnText (
393+ `${ reportText ( 12 , "iteration 2" ) } \nIteration 3 starts now.` ,
394+ ) ;
395+ streamTurnText (
396+ `${ reportText ( 14 , "iteration 3" ) } \nIteration 4 starts now.` ,
397+ ) ;
398+
399+ expect ( activeRun ( ) . iterations ) . toEqual ( [
400+ expect . objectContaining ( { index : 1 , value : 10 } ) ,
401+ expect . objectContaining ( { index : 2 , value : 12 } ) ,
402+ expect . objectContaining ( { index : 3 , value : 14 } ) ,
403+ ] ) ;
404+ expect ( sentPrompts ) . toHaveLength ( 0 ) ;
405+
406+ completeTurn ( "Continuing after the reported iterations." ) ;
407+
408+ expect ( activeRun ( ) . iterations ) . toHaveLength ( 3 ) ;
409+ expect ( sentPrompts ) . toHaveLength ( 1 ) ;
410+ expect ( sentPrompts [ 0 ] . prompt ) . toContain ( "iteration 4" ) ;
411+ } ) ;
412+
413+ it ( "recovers when an active prompt was incorrectly marked handled" , ( ) => {
414+ beginTurn ( ) ;
415+ const run = service . registerRun ( baseConfig ) ;
416+ const internals = service as unknown as {
417+ promptCursor : Map < string , number > ;
418+ } ;
419+ internals . promptCursor . set ( run . id , 1 ) ;
420+
421+ streamTurnText ( `${ reportText ( 10 , "baseline" ) } \nIteration 2 starts now.` ) ;
422+
423+ expect ( activeRun ( ) . iterations ) . toEqual ( [
424+ expect . objectContaining ( { index : 1 , value : 10 } ) ,
425+ ] ) ;
426+ } ) ;
427+
370428 it ( "shares the one-live-run-per-task guard with startRun" , ( ) => {
371429 service . registerRun ( baseConfig ) ;
372430 expect ( ( ) => service . startRun ( baseConfig ) ) . toThrow ( / a l r e a d y r u n n i n g / ) ;
373431 } ) ;
374432 } ) ;
375433
376434 describe ( "iteration loop" , ( ) => {
435+ it ( "records a metric block after the next iteration starts" , ( ) => {
436+ service . startRun ( baseConfig ) ;
437+ beginTurn ( ) ;
438+ streamTurnText ( `${ reportText ( 10 , "baseline" ) } \nIteration 2 starts now.` ) ;
439+
440+ expect ( activeRun ( ) . iterations ) . toEqual ( [
441+ expect . objectContaining ( { index : 1 , value : 10 , summary : "baseline" } ) ,
442+ ] ) ;
443+ expect ( sentPrompts ) . toHaveLength ( 1 ) ;
444+
445+ streamTurnText ( "Continuing to inspect the next change." ) ;
446+ expect ( activeRun ( ) . iterations ) . toHaveLength ( 1 ) ;
447+
448+ completeTurn ( "Finished the turn." ) ;
449+ expect ( activeRun ( ) . iterations ) . toHaveLength ( 1 ) ;
450+ expect ( sentPrompts ) . toHaveLength ( 2 ) ;
451+ expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain (
452+ "Then make the next focused change" ,
453+ ) ;
454+ } ) ;
455+
456+ it ( "keeps the tail report provisional until the turn ends" , ( ) => {
457+ service . startRun ( { ...baseConfig , maxIterations : 1 } ) ;
458+ beginTurn ( ) ;
459+ streamTurnText ( reportText ( 10 , "draft baseline" ) ) ;
460+
461+ expect ( activeRun ( ) . iterations ) . toHaveLength ( 0 ) ;
462+ expect ( activeRun ( ) . status ) . toBe ( "running" ) ;
463+
464+ streamTurnText ( reportText ( 12 , "corrected baseline" ) ) ;
465+ expect ( activeRun ( ) . iterations ) . toHaveLength ( 0 ) ;
466+
467+ completeTurn ( "Finalized the corrected report." ) ;
468+
469+ expect ( activeRun ( ) . iterations ) . toEqual ( [
470+ expect . objectContaining ( {
471+ index : 1 ,
472+ value : 12 ,
473+ summary : "corrected baseline" ,
474+ } ) ,
475+ ] ) ;
476+ expect ( activeRun ( ) . status ) . toBe ( "completed" ) ;
477+ expect ( activeRun ( ) . endReason ) . toBe ( "max-iterations" ) ;
478+ } ) ;
479+
480+ it ( "records prebaseline research without consuming an iteration" , ( ) => {
481+ service . startRun ( baseConfig ) ;
482+ runTurn ( researchText ( ) ) ;
483+
484+ const researchingRun = activeRun ( ) ;
485+ expect ( researchingRun . researchFindings ) . toEqual ( [
486+ expect . objectContaining ( {
487+ index : 1 ,
488+ summary : "Mapped the execution path" ,
489+ finding : "The metric is computed in the workspace server." ,
490+ nextStep : "Trace the benchmark command" ,
491+ } ) ,
492+ ] ) ;
493+ expect ( researchingRun . iterations ) . toHaveLength ( 0 ) ;
494+ expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain (
495+ "Continue investigating the codebase or establish the baseline measurement" ,
496+ ) ;
497+
498+ runTurn ( reportText ( 10 , "baseline" ) ) ;
499+
500+ expect ( activeRun ( ) . iterations ) . toEqual ( [
501+ expect . objectContaining ( { index : 1 , value : 10 , summary : "baseline" } ) ,
502+ ] ) ;
503+ } ) ;
504+
505+ it ( "does not accept research checkpoints after the baseline" , async ( ) => {
506+ service . startRun ( baseConfig ) ;
507+ runTurn ( reportText ( 10 , "baseline" ) ) ;
508+ runTurn ( researchText ( ) ) ;
509+ await passReminderGrace ( ) ;
510+
511+ expect ( activeRun ( ) . researchFindings ) . toHaveLength ( 0 ) ;
512+ expect ( activeRun ( ) . iterations ) . toHaveLength ( 1 ) ;
513+ expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain ( "did not include" ) ;
514+ } ) ;
515+
516+ it ( "prefers a metric when a prebaseline reply contains both report types" , ( ) => {
517+ service . startRun ( baseConfig ) ;
518+ runTurn ( `${ researchText ( ) } \n${ reportText ( 10 , "baseline" ) } ` ) ;
519+
520+ expect ( activeRun ( ) . researchFindings ) . toHaveLength ( 0 ) ;
521+ expect ( activeRun ( ) . iterations ) . toEqual ( [
522+ expect . objectContaining ( { index : 1 , value : 10 , summary : "baseline" } ) ,
523+ ] ) ;
524+ } ) ;
525+
377526 it ( "records an iteration and sends a continuation prompt" , ( ) => {
378527 service . startRun ( baseConfig ) ;
379528 runTurn ( reportText ( 10 , "baseline" ) ) ;
@@ -534,7 +683,7 @@ describe("AutoresearchService", () => {
534683 ) ;
535684 expect ( activeRun ( ) . metricUnit ) . toBe ( "kB" ) ;
536685
537- // First unit wins, like the name — a stable unit keeps values readable.
686+ // First unit wins, like the name. A stable unit keeps values readable.
538687 runTurn ( "```autoresearch\nmetric: 400000\nunit: bytes\n```" ) ;
539688 expect ( activeRun ( ) . metricUnit ) . toBe ( "kB" ) ;
540689 } ) ;
@@ -572,7 +721,7 @@ describe("AutoresearchService", () => {
572721 expect ( modelSwitches ) . toEqual ( [
573722 { taskId : TASK_ID , model : "claude-opus-4-8" } ,
574723 ] ) ;
575- expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain ( "implementation phase" ) ;
724+ expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain ( "Implementation phase" ) ;
576725 expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain (
577726 "Do NOT run the measurement" ,
578727 ) ;
@@ -610,7 +759,7 @@ describe("AutoresearchService", () => {
610759
611760 expect ( activeRun ( ) . iterations ) . toHaveLength ( 2 ) ;
612761 expect ( activeRun ( ) . phase ) . toBe ( "implement" ) ;
613- expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain ( "implementation phase" ) ;
762+ expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain ( "Implementation phase" ) ;
614763 } ) ;
615764
616765 it ( "still fails a measure turn that never reports" , async ( ) => {
@@ -988,6 +1137,9 @@ describe("AutoresearchService", () => {
9881137 bestValue : 10 ,
9891138 delta : null ,
9901139 summary : "baseline" ,
1140+ hypothesis : null ,
1141+ plan : null ,
1142+ approach : null ,
9911143 at : 1_000 ,
9921144 } ,
9931145 ] ,
@@ -1024,7 +1176,7 @@ describe("AutoresearchService", () => {
10241176 ] ) ;
10251177 await service . rehydrate ( ) ;
10261178
1027- // No session exists for task- 9 yet — recovery asks the host to
1179+ // No session exists for task 9 yet. Recovery asks the host to
10281180 // reconnect it.
10291181 await passRecoveryDelay ( ) ;
10301182 expect ( reconnectCalls ) . toEqual ( [ "task-9" ] ) ;
@@ -1074,7 +1226,7 @@ describe("AutoresearchService", () => {
10741226 // The in-memory run (with its recorded iteration) wins over the row.
10751227 expect ( state . runs [ live . id ] ?. iterations ) . toHaveLength ( 1 ) ;
10761228 expect ( state . runs [ "ar-past" ] ?. status ) . toBe ( "completed" ) ;
1077- // The live run stays the active one — it started later.
1229+ // The live run stays active because it started later.
10781230 expect ( state . activeRunIdByTask [ TASK_ID ] ) . toBe ( live . id ) ;
10791231 } ) ;
10801232
@@ -1203,7 +1355,9 @@ describe("AutoresearchService", () => {
12031355
12041356 // No phase alternation: the next prompt is a plain continuation.
12051357 expect ( activeRun ( ) . phase ) . toBeNull ( ) ;
1206- expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain ( "Continue:" ) ;
1358+ expect ( sentPrompts . at ( - 1 ) ?. prompt ) . toContain (
1359+ "Then make the next focused change" ,
1360+ ) ;
12071361 } ) ;
12081362 } ) ;
12091363
0 commit comments