mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-11 17:32:22 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4bebd42eb |
@@ -5,7 +5,7 @@ import {compactionRoutes} from '../src/routes/compaction.js';
|
||||
import {mkdtempSync, mkdirSync, writeFileSync, renameSync, rmSync, readFileSync, symlinkSync, lstatSync, statSync, utimesSync, chmodSync, existsSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join, dirname} from 'node:path';
|
||||
import {ClaudeCompactionEnforcer, AUTO_PREP_WAIT_MS_DEFAULT, AUTO_RETRY_AFTER_STOP_MS_DEFAULT, MANUAL_PREP_WAIT_MS_DEFAULT} from '../src/domain/claude-compaction-enforcer.js';
|
||||
import {ClaudeCompactionEnforcer, COMPACTION_RECORD_FILE, AUTO_PREP_WAIT_MS_DEFAULT, AUTO_RETRY_AFTER_STOP_MS_DEFAULT, MANUAL_PREP_WAIT_MS_DEFAULT} from '../src/domain/claude-compaction-enforcer.js';
|
||||
import {SessionTransport} from '../src/domain/session-transport.js';
|
||||
import {SeatDeliveryGuard} from '../src/domain/seat-delivery-guard.js';
|
||||
import {TmuxAdapter} from '../src/adapters/tmux.js';
|
||||
@@ -40,6 +40,62 @@ function stageMap(f:ReturnType<typeof fixture>,suffix=''){
|
||||
writeFileSync(a.mapPath+'.tmp',`# Restore map\nCurrent work and next step.\n${a.marker}${suffix}\n`);
|
||||
}
|
||||
const compacts=(f:ReturnType<typeof fixture>)=>f.writes.filter(t=>t.startsWith('/compact'));
|
||||
// Real readable capture and managed send, including the adapter's final beforePaste check.
|
||||
async function tinyPane(kind:'empty'|'unknown'|'typed'='empty',liveProbe=true){
|
||||
const f=fixture();vi.spyOn(Date,'now').mockImplementation(()=>f.clock());
|
||||
await f.e.maybeAutoCompact(input);stageMap(f);
|
||||
const frame=readFileSync(join(import.meta.dirname,'fixtures/input-box/claude-code-2.1.296/openrig-seat-accept-edits-idle.txt'),'utf8').trimEnd().split('\n');
|
||||
const row=frame.findLastIndex(line=>line.startsWith('❯'));
|
||||
if(kind==='typed')frame[row]='❯ keep this draft';
|
||||
const window={width:11,height:20,own:null as string|null,recorded:''};
|
||||
const screen=()=>window.width<120?'clipped pane':kind==='unknown'?'unclassified screen':`\x1b[0m${frame.join('\n')}`;
|
||||
const tmux=Object.assign(f.tmux,{
|
||||
hasSession:vi.fn(async()=>true),
|
||||
readWindow:vi.fn(async()=>({id:'@tiny',userOption:window.recorded})),
|
||||
listPanes:vi.fn(async()=>[{id:'%1',index:0,cwd:'/project',width:window.width,height:window.height,active:true}]),
|
||||
getWindowLocalOption:vi.fn(async()=>window.own),
|
||||
setWindowOption:vi.fn(async(_target:string,option:string,value:string)=>{if(option==='window-size')window.own=value;else window.recorded=value;return{ok:true};}),
|
||||
restoreWindowOption:vi.fn(async(_target:string,option:string,value:string|null)=>{if(option==='window-size')window.own=value;else window.recorded=value??'';return{ok:true};}),
|
||||
resizeWindow:vi.fn(async(_target:string,width:number,height:number)=>{window.width=width;window.height=height;window.own='manual';return{ok:true};}),
|
||||
getPaneCursorPosition:vi.fn(async()=>({x:kind==='typed'?17:2,y:row,width:window.width,height:window.height})),
|
||||
capturePaneContent:vi.fn(async()=>screen()),capturePaneScreen:vi.fn(async()=>screen()),
|
||||
sendText:vi.fn(async(_target:string,text:string,beforeInput?:()=>void,options?:{beforePaste?:()=>Promise<void>})=>{
|
||||
try{await options?.beforePaste?.();beforeInput?.();f.writes.push(text);return{ok:true};}
|
||||
catch(error){return{ok:false,code:(error as {code?:string}).code??'fixture_error',message:String(error)};}
|
||||
}),
|
||||
});
|
||||
if(liveProbe)delete(f.transport as any).classifySendReadiness;
|
||||
return{...f,tmux,window};
|
||||
}
|
||||
it.each(['live-pane','final-composer'])('submits managed /compact from an 11x20 pane through the %s readable read and restores the window',async gate=>{
|
||||
const f=await tinyPane('empty',gate==='live-pane');
|
||||
// The pre-existing readable path can classify this fixture and give the inherited sizing back.
|
||||
expect((await f.transport.capture(seat,{inputBox:true,readable:{width:120,height:40}})).inputBox).toMatchObject({status:'empty',confidence:'high'});
|
||||
expect(f.window).toEqual({width:11,height:20,own:null,recorded:''});
|
||||
await f.e.maybeAutoCompact(input);
|
||||
expect(f.window).toEqual({width:11,height:20,own:null,recorded:''});
|
||||
expect(compacts(f)).toHaveLength(1);
|
||||
expect(f.tmux.sendText).toHaveBeenCalledWith(seat,expect.stringMatching(/^\/compact/),expect.any(Function),expect.objectContaining({beforePaste:expect.any(Function)}));
|
||||
});
|
||||
it.each(['unknown','typed'] as const)('keeps a readable %s composer from authorizing /compact and gives its window back',async kind=>{
|
||||
const f=await tinyPane(kind);
|
||||
const box=(await f.transport.capture(seat,{inputBox:true,readable:{width:120,height:40}})).inputBox;
|
||||
expect(box?.status).toBe(kind==='typed'?'typed':'no-input-box');
|
||||
await f.e.maybeAutoCompact(input);
|
||||
expect(compacts(f)).toHaveLength(0);
|
||||
expect(f.window).toEqual({width:11,height:20,own:null,recorded:''});
|
||||
});
|
||||
it.each(['incomplete-map','completed-map','cancel-after-compact'] as const)('persists the computed end reason for %s',async end=>{
|
||||
const f=fixture();await f.e.maybeAutoCompact(input);
|
||||
if(end!=='incomplete-map')stageMap(f);
|
||||
if(end==='cancel-after-compact'){await f.e.maybeAutoCompact(input);expect(compacts(f)).toHaveLength(1);f.e.cancelPreparation(seat);}
|
||||
else{f.advance(AUTO_PREP_WAIT_MS_DEFAULT+(end==='completed-map'?AUTO_RETRY_AFTER_STOP_MS_DEFAULT:0)+1);f.e.reconcilePreparations();}
|
||||
const state=f.e.getPreparationState(seat)!;
|
||||
const reason=end==='incomplete-map'?'preparation_incomplete':end==='completed-map'?'map_complete_seat_never_idle':'compaction_cancelled';
|
||||
expect(state).toMatchObject({status:'stopped',reason});
|
||||
const record=JSON.parse(readFileSync(join(dirname(state.mapPath),COMPACTION_RECORD_FILE),'utf8'));
|
||||
expect(record.phase).toBe('ended');expect(record.reason).toBe(reason);
|
||||
});
|
||||
it('polls wait after delivered prep, ordinary real transport still circulates, exact map releases once',async()=>{
|
||||
const f=fixture();expect(AUTO_PREP_WAIT_MS_DEFAULT).toBe(25*60_000);
|
||||
await f.e.maybeAutoCompact(input);await f.e.maybeAutoCompact(input);expect(compacts(f)).toHaveLength(0);
|
||||
|
||||
Reference in new issue
Block a user