mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-20 09:09:35 +02:00
fix(copilot): codeman agent read returns the last answer
`/api/sessions/:id/last-response` fell through to the Claude transcript scan for copilot (not in the external-CLI list), so `codeman agent read` answered empty even with an answer on screen. Copilot is a repainting full-screen TUI whose stream is absolute cursor moves, so the buffer cannot be cut into lines either; the route now reads the pane's visible screen (tmux capture-pane) for such modes and falls back to the buffer where there is no pane. The segmenter gets a small per-CLI "pane dialect" row instead of another branch in the Codex-oriented loop: `❯ <prompt> HH:MM`, `● <answer>` with indented continuation, the cwd / `Session: N AIC used` row, composer, footer and the `┃` scrollbar gutter are chrome, and everything before the first prompt (banner, trust dialog) is dropped so a session that has not been asked anything answers nothing rather than serving its logo. Fixture is a real Copilot 1.0.95 pane. Tool-call rows are not modelled and read as answer text.
This commit is contained in:
1 parent
d7c7564415
commit
89a82c68d1
3 files changed
+199
-2
No files matched your search
@@ -460,3 +460,73 @@ Here is the assistant answer at column zero.
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GitHub Copilot pane dialect', () => {
|
||||
// Captured from a real Copilot 1.0.95 pane inside tmux (a throwaway COPILOT_HOME, a local
|
||||
// OpenAI-compatible endpoint): banner, two turns, status rows, composer between rules, footer.
|
||||
const rule = '─'.repeat(100);
|
||||
const pane = [
|
||||
' ╭─╮╭─╮',
|
||||
' ╰─╯╰─╯ Copilot v1.0.95 uses AI.',
|
||||
' █ ▘▝ █ Check for mistakes.',
|
||||
' ▔▔▔▔',
|
||||
' ! Model "mock" is not in the built-in catalog. Using defaults for: prompt tokens',
|
||||
' (COPILOT_PROVIDER_MAX_PROMPT_TOKENS), output tokens (COPILOT_PROVIDER_MAX_OUTPUT_TOKENS). Run copilot help',
|
||||
' providers for configuration details.',
|
||||
' ● Tip: /theme',
|
||||
' └ View or set color mode',
|
||||
' ❯ multi please 11:16',
|
||||
' ● First line of the answer. ┃',
|
||||
' ┃',
|
||||
' Second paragraph with bold text. ┃',
|
||||
' ┃',
|
||||
' • bullet one ┃',
|
||||
' • bullet two ┃',
|
||||
' ❯ WORKDONE followed by _4711 11:17',
|
||||
' ● WORKDONE_4711',
|
||||
' ● MCP Servers reloaded: 1 server connected',
|
||||
' /tmp/work/.../scratch-2 Session: credits unavailable',
|
||||
rule,
|
||||
'❯',
|
||||
rule,
|
||||
' ← open sidebar · Interactive · Manual Approval · / commands · ? help · tab next tab mock',
|
||||
].join('\n');
|
||||
|
||||
it('is a transcript mode', () => {
|
||||
expect(isExternalCliTranscriptMode('copilot')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns the last answer, with no banner, status rows, composer or footer in it', () => {
|
||||
const blocks = parseExternalCliTranscript(pane, 'copilot');
|
||||
expect(getLastTranscriptResponse(blocks)).toBe('WORKDONE_4711');
|
||||
});
|
||||
|
||||
it('keeps a multi-paragraph answer whole and strips the prompt timestamp', () => {
|
||||
const blocks = parseExternalCliTranscript(pane, 'copilot');
|
||||
const prompts = blocks.filter((b) => b.kind === 'prompt').map((b) => b.text);
|
||||
expect(prompts).toEqual(['multi please', 'WORKDONE followed by _4711']);
|
||||
const first = blocks.find((b) => b.kind === 'response')!;
|
||||
expect(first.text).toContain('First line of the answer.');
|
||||
expect(first.text).toContain('Second paragraph with bold text.');
|
||||
expect(first.text).toContain('• bullet two');
|
||||
expect(first.text).not.toMatch(/Session:|open sidebar|Tip:/);
|
||||
// The scrollbar gutter Copilot draws at the right edge of every row never reaches the text.
|
||||
expect(first.text).not.toContain('┃');
|
||||
});
|
||||
|
||||
it('answers nothing for a session that has only drawn its banner or a trust dialog', () => {
|
||||
const banner = pane.split('\n').slice(0, 9).join('\n');
|
||||
expect(getLastTranscriptResponse(parseExternalCliTranscript(banner, 'copilot'))).toBe('');
|
||||
const trust = [
|
||||
'│ Do you trust the files in this folder?',
|
||||
'│ ❯ 1. Yes',
|
||||
'│ 2. Yes, and remember this folder for future sessions',
|
||||
].join('\n');
|
||||
expect(getLastTranscriptResponse(parseExternalCliTranscript(trust, 'copilot'))).toBe('');
|
||||
});
|
||||
|
||||
it('does not take the empty composer glyph for a prompt', () => {
|
||||
const blocks = parseExternalCliTranscript(pane, 'copilot');
|
||||
expect(blocks.some((b) => b.kind === 'prompt' && b.text === '')).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user