11// @vitest -environment node
22import { spawn } from "node:child_process" ;
3- import { mkdtempSync , readdirSync , readFileSync , rmSync , writeFileSync } from "node:fs" ;
3+ import { mkdtempSync , readdirSync , readFileSync , rmSync , utimesSync , writeFileSync } from "node:fs" ;
44import type { AddressInfo } from "node:net" ;
55import { tmpdir } from "node:os" ;
66import { join , resolve } from "node:path" ;
@@ -265,17 +265,20 @@ describe("hyperframes history, one owner", () => {
265265 } ) ;
266266
267267 it ( "a turn whose writes come closer together than the idle limit stays the agent's past it" , async ( ) => {
268- const { write, json, hf } = project ( ) ;
268+ const { dir , write, json, hf } = project ( ) ;
269269 await hf ( ) ;
270- historyDeps . turnIdleMs = 300 ;
271- await hf ( "begin" , "--who" , "claude" , "--label" , "Retitle" ) ;
272- await pause ( 200 ) ;
273- write ( "index.html" , "2" ) ;
274- await hf ( ) ; // a command mid-turn files the turn so far
275- for ( const path of [ "notes.html" , "extra.html" ] ) {
276- await pause ( 200 ) ;
270+ historyDeps . turnIdleMs = 400 ;
271+ // Written "later" by their times, so the test never races the clock.
272+ const writeAt = ( path : string , at : number ) => {
277273 write ( path , "2" ) ;
278- }
274+ utimesSync ( join ( dir , path ) , at / 1000 , at / 1000 ) ;
275+ } ;
276+ await hf ( "begin" , "--who" , "claude" , "--label" , "Retitle" ) ;
277+ const at = Date . now ( ) + 300 ;
278+ writeAt ( "index.html" , at ) ;
279+ await hf ( ) ; // a command mid-turn files the turn so far; the rest counts from index.html
280+ writeAt ( "notes.html" , at + 300 ) ;
281+ writeAt ( "extra.html" , at + 600 ) ;
279282 await hf ( "end" ) ;
280283 const entries = ( await json ( ) ) . entries . reverse ( ) ;
281284 expect (
@@ -286,6 +289,17 @@ describe("hyperframes history, one owner", () => {
286289 ] ) ;
287290 } ) ;
288291
292+ it ( "a turn marker with no last write time has ended, so the next edit is not the agent's" , async ( ) => {
293+ const { dir, write, json, hf } = project ( ) ;
294+ await hf ( ) ;
295+ const who = { kind : "agent" , name : "claude" } ;
296+ const marker = { via : "direct" , id : "turn-1" , who, label : "Retitle" , startedAt : 1 } ;
297+ writeFileSync ( join ( dir , ".hyperframes" , "history-turn.json" ) , JSON . stringify ( marker ) ) ;
298+ write ( "index.html" , "A2" ) ;
299+
300+ expect ( ( await json ( ) ) . entries [ 0 ] ) . toMatchObject ( { who : { kind : "outside" } } ) ;
301+ } ) ;
302+
289303 it ( "a CLI run waits while another process holds the history, instead of forking its log" , async ( ) => {
290304 const { dir, write } = project ( ) ;
291305 const home = tempDir ( "hf-history-cli-home-" ) ;
@@ -332,13 +346,15 @@ describe("hyperframes history, refusals", () => {
332346 } ) ;
333347
334348 it ( "restore refuses a ref that is neither an entry nor a date, instead of reading it as a time" , async ( ) => {
335- const { read, write, hf, turn } = project ( ) ;
349+ const { read, write, hf, json , turn } = project ( ) ;
336350 await hf ( ) ;
337351 await turn ( "claude" , "Retitle" , "index.html" , "A2" ) ;
338352 write ( "added.html" , "new" ) ;
353+ const ids = ( await json ( ) ) . entries . map ( ( entry : { id : string } ) => entry . id ) ;
354+ const digit = [ ..."0123456789" ] . find ( ( d ) => ! ids . some ( ( id : string ) => id . startsWith ( d ) ) ) ! ;
339355
340- const refused = await hf ( "restore" , "1" ) ;
341- expect ( [ refused . code , refused . err ] ) . toEqual ( [ 2 , ' No entry "1 " in this history' ] ) ;
356+ const refused = await hf ( "restore" , digit ) ;
357+ expect ( [ refused . code , refused . err ] ) . toEqual ( [ 2 , ` No entry "${ digit } " in this history` ] ) ;
342358 expect ( read ( "added.html" ) ) . toBe ( "new" ) ;
343359 } ) ;
344360
0 commit comments