mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +01:00
fix(copilot): Free plan usage, tall answers in agent read, page-key fallback, review nits
Review of 8659ba25..be987fbb:
- Usage: a Free account answers premium_interactions {entitlement: 0, remaining: 0,
percent_remaining: 0, unlimited: false, has_quota: false}, which read as 100% used. The parser now
returns null when has_quota is false or the entitlement is not positive, with that payload as a
fixture. Free meters chat (200) and completions (2000) too, so the comment no longer says they
are unlimited on every plan.
- agent read: rows were skipped until a `❯ <prompt>` row, so an answer taller than the pane
(prompt scrolled off the top) read as "". With no prompt row AND no banner / trust dialog on
screen, the rows above the composer are now the tail of the last answer. Text typed in the
composer (a prompt-shaped row between the two rules) is not a prompt row.
- Page keys: measured on 1.0.95 in tmux that Copilot pages on PageUp/PageDown (three PageUp moved
lines 109-140 to 010-042, PageDown returned) as well as on SGR wheel reports, so copilot joins
claude and opencode in _localScrollbackIsHollow(); the stale-tracking-flag fallback the comment
and docs describe is now true, for the main pane and a TerminalTile (same function), with tests.
- X-GitHub-Api-Version is 2022-11-28, a version GitHub supports (verified against the live
endpoint); readPlanUsageTelemetryEnabled() is checked before isCopilotAvailable() so a switched-off
chip skips the resolver probe too.
- Palette label for Copilot in panels-ui.js; stale comments in types/session.ts and stock.ts.
- Docs: Past Sessions continues with --continue (resumeSessionId is for API callers), nothing sets
continueSession on a respawn, and only a tab name the user set is passed as --name; the usage
wording for Free; the wheel and mouse-strip paragraphs in CLAUDE.md and
architecture-invariants; the changeset describes the whole feature.
This commit is contained in:
1 parent
5b77c271b0
commit
740474ebca
13 files changed
+156
-31
No files matched your search
@@ -1655,7 +1655,8 @@ const OMP: CliEntry = {
|
||||
// GitHub Copilot CLI (`copilot`, github/copilot-cli, npm @github/copilot). Measured on a live
|
||||
// 1.0.94 pane inside tmux (capture-pane through real turns, a shell tool call and /model,
|
||||
// 2026-10-09). It is an ALTERNATE-SCREEN TUI with mouse tracking on (tmux `alternate_on=1`,
|
||||
// `mouse_any_flag=1`, `history_size=0`), so it joins grok in the tmux-attach-time strip only.
|
||||
// `mouse_any_flag=1`, `history_size=0`), so like opencode its mouse DECSETs are stripped
|
||||
// (`altScreen: 'strip-mux-and-mouse'` below): a drag selects locally and the wheel is forwarded.
|
||||
// Keystrokes sent with `tmux send-keys` reach its composer and a shell tool call completes
|
||||
// inside tmux, which github/copilot-cli#4180 (a driven PTY ignoring input) and #4223 (a shell
|
||||
// command never reported done under tmux) say they do not on 1.0.70 to 1.0.74: neither
|
||||
@@ -1756,6 +1757,10 @@ const COPILOT: CliEntry = {
|
||||
// (`cliMouseTracking`). Same shape and reason as opencode. The wheel keeps working because
|
||||
// `_shouldForwardWheelToApp` forwards it as SGR reports while the CLI is tracking.
|
||||
altScreen: 'strip-mux-and-mouse',
|
||||
// Pages its own transcript on PageUp/PageDown (1.0.95, measured in tmux: three PageUp moved
|
||||
// lines 109-140 to 010-042 and PageDown returned). This is the fallback when the
|
||||
// wheel-forward flag is stale after a server restart, so the wheel and a swipe never go dead.
|
||||
transcriptPageKeys: true,
|
||||
echo: { policy: 'buffer', anchor: { kind: 'cursor' } },
|
||||
// What `codeman agent spawn` waits for. The composer's footer hint, NOT the `❯` glyph: the
|
||||
// folder-trust dialog draws `❯ 1. Yes` for its selected row, so the glyph would call a worker
|
||||
|
||||
@@ -546,7 +546,10 @@ export interface CopilotConfig {
|
||||
allowAll?: boolean;
|
||||
/** Resume the most recent session (--continue). Skipped when resumeSessionId is set. */
|
||||
continueSession?: boolean;
|
||||
/** Resume a specific session by ID (--resume=<id>). Ids only, never names. */
|
||||
/**
|
||||
* Resume a specific session (--resume <value>): an id, id prefix or session name, as ONE plain word
|
||||
* starting with a letter or digit (a leading `-` would make the value its own flag).
|
||||
*/
|
||||
resumeSessionId?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -533,7 +533,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
_buildCommandPaletteNewSessionItem(query = '') {
|
||||
const mode = this.runMode || this._runMode || 'claude';
|
||||
const labels = { claude: 'Claude', opencode: 'OpenCode', codex: 'Codex', gemini: 'Gemini', antigravity: 'Antigravity', pi: 'Pi', grok: 'Grok', deepseek: 'DeepSeek', omp: 'OMP' };
|
||||
const labels = { claude: 'Claude', opencode: 'OpenCode', codex: 'Codex', gemini: 'Gemini', antigravity: 'Antigravity', pi: 'Pi', grok: 'Grok', deepseek: 'DeepSeek', omp: 'OMP', copilot: 'Copilot' };
|
||||
const caseName = this._findCommandPaletteCaseMatch(query) || document.getElementById('quickStartCase')?.value || 'testcase';
|
||||
return {
|
||||
id: 'new-session',
|
||||
|
||||
@@ -30,6 +30,12 @@ interface PaneDialect {
|
||||
ignore: RegExp[];
|
||||
/** Furniture drawn at the end of a row (a scrollbar gutter), removed before anything else reads it. */
|
||||
lineTrailer?: RegExp;
|
||||
/**
|
||||
* Text that is on screen only before the first prompt has scrolled the screen: the banner, a
|
||||
* trust dialog. Its presence is how "nothing has been asked yet" is told apart from "the prompt
|
||||
* row has scrolled off the top".
|
||||
*/
|
||||
startup: RegExp[];
|
||||
}
|
||||
|
||||
const PANE_DIALECTS: Record<string, PaneDialect> = {
|
||||
@@ -52,6 +58,7 @@ const PANE_DIALECTS: Record<string, PaneDialect> = {
|
||||
ignore: [/^\s*❯\s*$/],
|
||||
// The right-hand scrollbar gutter, `┃` on a row (or alone on a blank one).
|
||||
lineTrailer: /\s*[┃▐]\s*$/,
|
||||
startup: [/Copilot v\d+\.\d+\.\d+ uses AI/, /Confirm folder trust/, /Do you trust the files in this folder/],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -320,12 +327,23 @@ export function transcriptNeedsScreenCapture(mode: string | null | undefined): b
|
||||
* Segment a pane that has its own dialect. Everything before the first prompt is the banner (or a
|
||||
* trust dialog) and is dropped, so a session that has not been asked anything answers "nothing
|
||||
* yet" rather than serving its logo as a reply.
|
||||
*
|
||||
* The screen is all there is to read, so a long answer scrolls the prompt row off the top. When no
|
||||
* prompt row is on screen AND no startup text is either, the session has already been asked
|
||||
* something: the rows above the composer are the tail of the last answer, not "nothing yet".
|
||||
*/
|
||||
function parseDialectTranscript(cleaned: string, dialect: PaneDialect): ResponseViewerTranscriptBlock[] {
|
||||
const blocks: ResponseViewerTranscriptBlock[] = [];
|
||||
let kind: ResponseViewerTranscriptKind | null = null;
|
||||
let current: string[] = [];
|
||||
let sawPrompt = false;
|
||||
const rows = cleaned.split('\n').map((row) => (dialect.lineTrailer ? row.replace(dialect.lineTrailer, '') : row));
|
||||
// The composer sits between two rules and holds whatever is typed but not sent. That is not a
|
||||
// prompt row: otherwise typing under a tall answer would make the answer's tail read as banner.
|
||||
const isComposerRow = (i: number) =>
|
||||
i > 0 && i < rows.length - 1 && isDividerOnlyLine(rows[i - 1] ?? '') && isDividerOnlyLine(rows[i + 1] ?? '');
|
||||
const hasPrompt = rows.some((row, i) => dialect.prompt.test(row) && !isComposerRow(i));
|
||||
const startupVisible = rows.some((row) => dialect.startup.some((re) => re.test(row)));
|
||||
let sawPrompt = !hasPrompt && !startupVisible;
|
||||
|
||||
const flush = () => {
|
||||
if (sawPrompt) pushBlock(blocks, kind, current);
|
||||
@@ -333,13 +351,8 @@ function parseDialectTranscript(cleaned: string, dialect: PaneDialect): Response
|
||||
current = [];
|
||||
};
|
||||
|
||||
for (const rawLine of cleaned.split('\n')) {
|
||||
const line = dialect.lineTrailer ? rawLine.replace(dialect.lineTrailer, '') : rawLine;
|
||||
if (isDividerOnlyLine(line)) {
|
||||
flush();
|
||||
continue;
|
||||
}
|
||||
if (dialect.ignore.some((re) => re.test(line))) {
|
||||
for (const [index, line] of rows.entries()) {
|
||||
if (isDividerOnlyLine(line) || isComposerRow(index) || dialect.ignore.some((re) => re.test(line))) {
|
||||
flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user