mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-02 17:40:02 +02:00
test: settle the post-response composer before seeding; give the TPA recorder adapter its infra helper
submitPlanSeed accepted a stale empty composer when the transcript recorded end_turn before the CLI repainted (late-repaint-typed-current fails 5/5 on the old helper, passes 5/5 now). The TPA recording fixture extracted recordE2E without isPreTurnInfraFailure, so every failed case threw before recording.
This commit is contained in:
1 parent
80c92c86ed
commit
3fc05932b7
4 files changed
+20
-9
No files matched your search
Vendored
+3
-1
@@ -85,8 +85,10 @@ process.stdin.on('data',chunk=>{
|
||||
const current=scenario==='prose-question'?'\r\nWhich option do you prefer?\r\nA) Full review (recommended)\r\nB) Skip review\r\n❯ '
|
||||
:scenario.endsWith('multiline-current')?'\r\n keep this draft'
|
||||
:scenario.endsWith('typed-current')?'keep this draft':'';
|
||||
frame(current,history,scenario.endsWith('missing-current-top')||scenario==='unframed-current'?'':rule,
|
||||
// The native CLI can write end_turn before it repaints; the stale empty box must not count.
|
||||
const paint=()=>frame(current,history,scenario.endsWith('missing-current-top')||scenario==='unframed-current'?'':rule,
|
||||
scenario.endsWith('missing-current-bottom')||scenario==='unframed-current'?'':scenario==='mismatched-current-rules'?rule.slice(1):rule);
|
||||
if(scenario==='late-repaint-typed-current')setTimeout(paint,60);else paint();
|
||||
if(scenario==='stray-prompt-after-current')process.stdout.write('\r❯ keep this later draft');
|
||||
},180);return;
|
||||
}
|
||||
|
||||
@@ -152,6 +152,9 @@ export async function submitPlanSeed(session: SeedSession, seed: string, opts: {
|
||||
});
|
||||
if (Date.now() >= opts.deadlineAt) throw new PlanSeedTimeout('Plan seed submission exhausted the existing case budget');
|
||||
session.sendKey('Enter'); // Separate input event after the acknowledged paste.
|
||||
// The transcript can record end_turn before the CLI repaints, so an empty
|
||||
// composer counts only when the same frame survives one more poll.
|
||||
let settled = '';
|
||||
await until(async () => {
|
||||
const owned = read();
|
||||
if (!owned || owned.pendingBytes) return false;
|
||||
@@ -178,13 +181,18 @@ export async function submitPlanSeed(session: SeedSession, seed: string, opts: {
|
||||
}
|
||||
if (row.type === 'user') for (const c of content(row)) if (c.type === 'tool_result') pending.delete(c.tool_use_id);
|
||||
}
|
||||
if (!complete || pending.size || owned.status.waitingFor) return false;
|
||||
const unsettled = () => { settled = ''; return false; };
|
||||
if (!complete || pending.size || owned.status.waitingFor) return unsettled();
|
||||
const frame = await session.currentScreen!();
|
||||
if (opts.isQuestionOrPermission(frame.text)) throw new Error('Plan seed response requires an answer before skill invocation');
|
||||
const input = composer(frame.text);
|
||||
if (frame.rawEnd !== session.mark() || !input
|
||||
|| input.line.replace(/^❯[ \u00a0]*/, '').trim() !== '') return false;
|
||||
|| input.line.replace(/^❯[ \u00a0]*/, '').trim() !== '') return unsettled();
|
||||
const fresh = read();
|
||||
return !!fresh && !fresh.pendingBytes && fresh.rows.length === owned.rows.length && !fresh.status.waitingFor;
|
||||
if (!fresh || fresh.pendingBytes || fresh.rows.length !== owned.rows.length || fresh.status.waitingFor) return unsettled();
|
||||
const signature = `${frame.rawEnd}:${fresh.rows.length}:${frame.text}`;
|
||||
if (signature === settled) return true;
|
||||
settled = signature;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -18,7 +18,7 @@ for (const scenario of ['success', 'completed-tool', 'status-updating', 'history
|
||||
'startup-waiting', 'startup-prose-question', 'startup-permission', 'startup-fresh-waiting',
|
||||
'no-ack', 'fused', 'duplicate', 'session-switch', 'foreign-cwd',
|
||||
'pending-tool', 'question', 'prose-question', 'permission', 'no-end-turn', 'partial', 'wrong-pid',
|
||||
'typed-current', 'multiline-current', 'history-box-typed-current', 'history-box-multiline-current',
|
||||
'typed-current', 'late-repaint-typed-current', 'multiline-current', 'history-box-typed-current', 'history-box-multiline-current',
|
||||
'missing-current-top', 'missing-current-bottom', 'mismatched-current-rules', 'unframed-current',
|
||||
'history-no-current', 'history-missing-current-top', 'history-missing-current-bottom',
|
||||
'stray-prompt-after-current', 'stale-response-frame',
|
||||
|
||||
@@ -24,10 +24,11 @@ const recoveryResponses = JSON.parse(readFile(path.join(root, 'test/fixtures/thi
|
||||
const writeFile = fs.writeFileSync.bind(fs);
|
||||
const remove = fs.rmSync.bind(fs);
|
||||
const source = readFile(path.join(root, 'test/helpers/e2e-helpers.ts'), 'utf8');
|
||||
const start = source.indexOf('export function recordE2E(');
|
||||
const end = source.indexOf('/**', start);
|
||||
if (start < 0 || end < 0) throw new Error('recordE2E source boundary missing');
|
||||
const recordSource = new Bun.Transpiler({ loader: 'ts' }).transformSync(source.slice(start, end).replace('export function', 'function'));
|
||||
const start = source.indexOf('const INFRA_EXIT_REASONS');
|
||||
const recordStart = source.indexOf('export function recordE2E(');
|
||||
const end = source.indexOf('/**', recordStart);
|
||||
if (start < 0 || recordStart < start || end < 0) throw new Error('recordE2E source boundary missing');
|
||||
const recordSource = new Bun.Transpiler({ loader: 'ts' }).transformSync(source.slice(start, end).replaceAll('export function', 'function'));
|
||||
const recordE2E = new Function(recordSource + '\\nreturn recordE2E;')();
|
||||
const cases = new Map();
|
||||
const records = [];
|
||||
|
||||
Reference in new issue
Block a user