Skip to content

feat(cli-registry): GitHub Copilot CLI as a run mode - #592

Merged
Ark0N merged 12 commits into
Ark0N:masterfrom
opticon454:feat/cli-copilot
Oct 18, 2026
Merged

Ark0N merged 12 commits into
Ark0N:masterfrom
opticon454:feat/cli-copilot

Conversation

@opticon454

@opticon454 opticon454 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

GitHub Copilot CLI (copilot, npm @github/copilot) as a stock run mode, with the same launch options as the other agent CLIs. Covers #198.

It shows up in the Run menu, welcome screen, phone picker and cron agent list once installed, runs in tmux like the other agent CLIs, and CLI management can install it (npm install -g @github/copilot).

  • Launch options: a copilotConfig on the session create and quick-start bodies, the same shape as grokConfig/ompConfig: model (--model), resumeSessionId (--resume <value> for API callers: one plain word starting with a letter or digit, so a flag-shaped value such as --yolo is dropped), continueSession (--continue, which Past Sessions sends: the most recent conversation in that directory) and allowAll (--yolo).
  • Permissions: the Run button sends allowAll: true, like grok's alwaysApprove and antigravity's dangerouslySkipPermissions. allowAll is a privilegedParam, so a non-granted multi-user owner has it forced back to false; an absent config is not materialized, because a bare copilot is already in Manual Approval. COPILOT_HOME and COPILOT_ALLOW_ALL are privileged env keys.
  • Working/idle: from the footer. A running turn reads ◉ Working esc edit prompt (the dot alternates ◉/◎); the composer is the ❯ between two rules. A tool waiting for approval replaces the composer and the tab reads idle.
  • Model: read from the right-hand field of the footer's last row.
  • Custom endpoints: Copilot's BYOK variables (COPILOT_PROVIDER_BASE_URL, _API_KEY, COPILOT_MODEL) are injected for a Custom Model Endpoint, with the /v1 suffix, so the Run menu's custom endpoint rows work for it; the three are privileged env keys.
  • Naming: a tab name you set yourself is passed as --name (an automatic w1-… name is not pinned), except next to --resume/--continue, which Copilot refuses to combine with it (a continued session keeps its name).
  • Selection and wheel: Copilot turns mouse tracking on itself, so it uses altScreen: 'strip-mux-and-mouse' like opencode: a plain drag is a local selection (Auto Copy and copy-on-select work, not just Shift+drag), clicks still reach the CLI through the hand-encoded tap, and the wheel is forwarded as SGR reports while the CLI is tracking the mouse.
  • codeman agent verbs: the session exports CODEMAN_MUX, CODEMAN_SESSION_ID and CODEMAN_API_URL, so ls, spawn, send, wait, read, interrupt and rm work. composerReadyMark is the composer's footer hint (/ commands · ? help), not ❯, which the folder-trust dialog also draws. agent read returns the last answer: the route reads the pane's visible screen for this repainting TUI and segments it with a per-CLI pane dialect (prompt, answer bullet, chrome, scrollbar gutter). Tool-call rows are not modelled and read as answer text. A prompt row scrolled off by a tall answer reads as the answer's tail (unless the banner or trust dialog is on screen). --until stop is refused for Copilot like other hook-less modes; idle, exit or --match markers work.
  • Plan usage: the header chip gets a Copilot row with the calendar-month premium-request quota (mo, with request counts and the reset in the tooltip), read from GET api.github.com/copilot_internal/user with the CLI's own sign-in token. The endpoint is not part of GitHub's documented API (the editors use it), so the read is best-effort and silent: 10-minute host poll, only when the CLI is installed, a token resolves and plan-usage display is on; any failure, an unlimited quota or no premium quota at all (a Free account answers entitlement: 0, has_quota: false) just shows no row.
  • Probe: GET /api/copilot/status (available, path, version), backed by copilot-cli-resolver.ts, which only accepts a binary whose --version says GitHub Copilot CLI.
  • Env: the COPILOT_ prefix only (a headless host sets COPILOT_GITHUB_TOKEN); GH_TOKEN/GITHUB_TOKEN stay out of the global allowlist.
  • MCP sync: the entry declares capabilities.mcpConfig (~/.copilot/mcp-config.json, COPILOT_HOME relocation), so Settings no longer lists Copilot as unsupported. The sync-only row in mcp-sync-targets.ts is skipped while the run mode is enabled.
  • Docker: no credStore; Copilot's token is plain text in config.json on a host without a keyring, so it is not seeded. Sign in inside the container (documented in Docker-Cases.md).
  • Also: the agent image's npm package list, Run-menu logo (a drawn mark, not GitHub's), phone-picker entry, skill docs and the plugin mirror.

Measured on 1.0.94 inside tmux

Alternate-screen TUI with mouse tracking on. Keystrokes sent with tmux send-keys and through POST /api/sessions/:id/input reach the composer, Shift+Enter inserts a newline, mouse-wheel scrolling works, and shell tool calls complete (an approval prompt, a 6 s command, 3000 lines of output). github/copilot-cli#4180 (a driven PTY ignores input) and #4223 (a shell command never completes under tmux) were filed against 1.0.70 to 1.0.74; neither reproduces on 1.0.94, so older installs may still hit them.

Also checked against a mock OpenAI endpoint (1.0.95)

With COPILOT_PROVIDER_BASE_URL=http://127.0.0.1:<port>/v1, COPILOT_PROVIDER_API_KEY and COPILOT_MODEL set, copilot -p posts to /v1/chat/completions with the key and the model name, no GitHub sign-in. --resume <id> --model <m> and --continue --model <m> resume the earlier session; --name with either is rejected, hence the rule above.

Checked end to end

A throwaway Codeman instance (own data dir and tmux socket) against a local OpenAI-compatible endpoint, with a real Chromium: a plain drag in a Copilot pane produced a 38-character selection that Auto Copy put on the clipboard, and the wheel went out as ESC[<64;…M reports. A shell pane that turns mouse tracking on itself (the pre-fix situation) produced no selection and left the clipboard alone, so the check does detect the failure. The codeman agent lifecycle ran against two Copilot panes: spawn warns instead of claiming ready while the trust dialog is up, --wait resolves on idle, read returns the answer, the WORKDONE_4711 marker wait matches, interrupt and rm work, and rm of self is refused.

Tests

Spawn golden and copilot-cli-resolver tests, the multi-user clamp, history-resume fidelity, the capability-predicate row, remote/docker overlay commands, install.sh detection parity, docker-agent-image coverage, the id-branching guard (legacy <Mode>Config plumbing allowlisted as for grok), and the registry load/schema tests. tsc --noEmit and lint clean.

@opticon454

Copy link
Copy Markdown
Contributor Author

Switched to draft as I'll implement the missing features so GH copilot goes in complete

@opticon454
opticon454 force-pushed the feat/cli-copilot branch 5 times, most recently from ccead2e to 7407bbc Compare October 10, 2026 06:54
@opticon454
opticon454 marked this pull request as ready for review October 10, 2026 07:06
@opticon454

Copy link
Copy Markdown
Contributor Author

I've been using this manually from my desktop and mobile and I can't find any faults with it

@Ark0N

Ark0N commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Thanks @opticon454, this is a thorough piece of work: GitHub Copilot CLI becomes a full run mode through a registry entry, with launch options, the multi-user clamp, BYOK custom endpoints, footer-based working/idle and model detection, a status probe and tests to match. The measurements in the entry comments are exactly what makes a new CLI reviewable. Typecheck, lint, format and the full test gate all pass here. A few things to change before merge:

  1. GH_TOKEN / GITHUB_TOKEN in env.allowedKeys (src/config/cli-registry/stock.ts:1660). The env allowlist is one global list with no mode context, so these two keys become settable on every session (I checked: a claude, shell or codex create with them now validates). CLAUDE.md and the comment above allowedEnvPrefixes() in src/web/schemas.ts keep each CLI to its own vendor namespace for that reason, which is why pi and DeepSeek leave their foreign provider keys out. Copilot reads COPILOT_GITHUB_TOKEN first and the COPILOT_ prefix already admits it, so please set allowedKeys: [] and point headless users at COPILOT_GITHUB_TOKEN in the comment and docs.

  2. MCP sync shows Copilot as unsupported (src/web/routes/mcp-sync-routes.ts:62). Now that Copilot is an agent entry without capabilities.mcpConfig, mcpUnsupportedLabels() lists "GitHub Copilot" while the sync-only target still syncs "GitHub Copilot CLI", so Settings says both. Declaring mcpConfig: { path: '.copilot/mcp-config.json', format: 'copilot-json', relocation: { envVar: 'COPILOT_HOME', path: 'mcp-config.json' } } on the entry fixes it (the dialect and the disabledMcpServers read are keyed by format, and the sync-only row is then skipped). The src/mcp-sync-targets.ts @fileoverview and the paragraph in docs/cli-registry.md need the matching update.

  3. Resume ids that start with - (src/web/schemas.ts:436 and the resumeId param at stock.ts:1609). resumeSessionId: '--yolo' passes both patterns and renders copilot --resume --yolo; since --resume takes an optional value, --yolo is then its own flag, which gets around the allowAll clamp for a non-granted owner. Please require a leading alphanumeric (/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/ in the schema, pattern: 'path-segment' on the param), add a golden case for it, and reword the "ids only, never names" comments here and in docs/cli-registry.md:284, since a name like my-feature also matches. Grok has the same shape; that one is ours to fix separately.

  4. Docker credentials (stock.ts:1713). overlays.credStore is one of the registry fields no code reads yet; Docker seeding comes from CRED_STORES in src/docker-hosts.ts:854, which has no .copilot row, so Copilot in a Docker case starts signed out in each new container. Either add a row there (the token sits in config.json on hosts without a keyring, so an enabledByEnv opt-in like the gh row is worth considering) or drop the credStore line and say in docs/wiki/Docker-Cases.md that you sign in inside the container.

Smaller things I can pick up at merge time if you prefer: the Prettier re-padding of the unrelated tables in docs/cli-registry.md (docs are outside our Prettier scope), a 'Run GitHub Copilot' zh-CN entry in src/web/public/i18n.js, short: 'Copilot' in mobile-overview.js:61, and the CLAUDE.md / docs/wiki/Agent-CLIs.md / allowlist error message updates for the new mode.

Once 1 to 4 are in, this is ready to merge. Thanks again for driving it all the way to a complete mode.

@opticon454
opticon454 force-pushed the feat/cli-copilot branch 2 times, most recently from 1d107ec to 25ee641 Compare October 11, 2026 03:34
@opticon454

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review. All four are in as new commits on top of the original, so the delta is easy to see (f4d68f47 for the review, then three follow-ups for bugs found while testing).

  1. GH_TOKEN / GITHUB_TOKEN: allowedKeys is now [], and the comment points headless hosts at COPILOT_GITHUB_TOKEN (already admitted by the COPILOT_ prefix). A test pins that both names are rejected for claude, shell, codex and copilot creates.
  2. MCP sync: the entry declares mcpConfig (.copilot/mcp-config.json, copilot-json, COPILOT_HOME relocation). I kept the sync-only row: it is skipped while the run mode is enabled and still applies if someone disables the mode with the tool installed. The mcp-sync-targets.ts @fileoverview, the route comments and docs/cli-registry.md are updated, and test/mcp-sync-registry.test.ts now expects copilot.
  3. Resume ids starting with -: you were right, --resume --yolo rendered --yolo as its own flag. The param is now path-segment and the schema regex needs a leading alphanumeric. The golden test covers --yolo, -n, --allow-all-tools, ., .. and .hidden (all dropped) and that my-feature still passes. The "ids only" wording is corrected in the entry, schema and docs.
  4. Docker credentials: I dropped the credStore line rather than add a CRED_STORES row, because the token is plain text in config.json on a host without a keyring. Docker-Cases.md now says to sign in inside the container.

From the smaller list I did the zh-CN Run GitHub Copilot entry and put the unrelated Prettier re-padding in docs/cli-registry.md back to upstream's. I left short as 'GitHub Copilot' in mobile-overview.js: test/mobile-overview.test.ts pins the static table to the registry label, so 'Copilot' there fails that parity check. The CLAUDE.md / Agent-CLIs.md / allowlist error-message updates I've left for merge time, as you offered.

One more fix, found while testing: copy on selection. Copilot's TUI turns mouse tracking on itself. Under strip-mux-only those DECSETs reached xterm, which then reported a plain drag to the TUI instead of selecting, so Auto Copy and copy-on-select did nothing (only Shift+drag selected). It is the failure opencode had and strip-mux-and-mouse exists for, so Copilot now uses it: a drag is a local selection and clicks still reach the CLI through the hand-encoded tap. Stripping the DECSETs also stops xterm encoding the wheel, so _shouldForwardWheelToApp forwards it as SGR reports while Copilot is tracking the mouse (the same bytes xterm sent natively, which is what scrolling was verified against). Tests cover the strip and the wheel gate.

Two more, from the codeman agent verbs (README: session-to-session verbs in every CLI mode):

  • spawn readiness. Copilot declared no composerReadyMark, so agent spawn --mode copilot returned before the composer could take a prompt. The mark is the footer hint / commands · ? help, not ❯: the folder-trust dialog draws ❯ 1. Yes, which is the same trap the claude entry's comment describes.
  • agent read. Copilot was missing from the external-CLI list that routes last-response to the pane segmenter, so it fell through to the Claude transcript scan and answered empty. Copilot's stream is absolute cursor moves rather than lines, so the route reads the pane's visible screen (tmux capture-pane) for it and segments with a small per-CLI "pane dialect" row (❯ <prompt> HH:MM, ● <answer>, status/footer/scrollbar chrome; everything before the first prompt is dropped, so a fresh session answers nothing instead of serving its banner). Tool-call rows are not modelled and read as answer text.

How I checked it: a throwaway Codeman instance (own data dir and tmux socket) against a local OpenAI-compatible endpoint, with a real Chromium. A plain drag in a Copilot pane gave a selection that Auto Copy put on the clipboard and the wheel left as ESC[<64;…M reports; a shell pane that enables mouse tracking itself (the old situation) produced no selection, so the check does detect the failure. Over the verbs: ls, spawn (warns instead of claiming ready while the trust dialog is up), send --wait (resolves on idle), read, wait --match, interrupt, rm and the refusal to rm self. --until stop is refused for Copilot like the other hook-less modes.

Full suite (npm test), typecheck, lint and format are clean locally.

@opticon454

Copy link
Copy Markdown
Contributor Author

One more addition since the review fixes: the header plan-usage chip now has a Copilot row (6f89c70b).

What it shows. One mo window with the calendar-month premium-request quota, and the request counts and reset date in the tooltip, beside Claude's and Codex's rows.

Where it comes from. The Copilot CLI has no scriptable usage command (copilot billing exists only inside the TUI), so this reads the quota snapshot the editors use, GET https://api.github.com/copilot_internal/user, with the CLI's own sign-in token. That endpoint is not part of GitHub's documented API, so I kept it narrow and fail-silent. It is the one part of this PR you may want to weigh:

  • It is the first thing in Codeman that makes a request to GitHub on the user's behalf from the server.
  • It polls every 10 minutes (same host-scoped, read-only shape as the Codex poll) and only when the CLI is installed, a token resolves (COPILOT_GITHUB_TOKEN, GH_TOKEN, GITHUB_TOKEN, then authTokens in <COPILOT_HOME or ~/.copilot>/config.json) and plan-usage display is on, so turning the chip off also stops the request.
  • Any failure (offline, 401, a changed shape) or an unlimited quota yields no row. The token is never logged or put in an error, and it is sent only to api.github.com over HTTPS with redirects refused.

premium_interactions is the only metered bucket; chat and completions are unlimited: true on the plan I could check (a Business seat), which I can't speak for beyond that.

Tests. The parser (including overage clamping and the unlimited case), the token lookup (env precedence, the commented config.json, COPILOT_HOME, a non-github.com host), the reader with an injected fetch, and the chip rendering. The Claude/Codex change-detection signatures are byte-identical to before.

Also confirmed since the last comment: touch swipes on a phone reach Copilot as wheel reports in both directions (the same wheel forwarding that fixed drag selection), checked with real touch events in an emulated phone.

If you'd rather not ship the network read in this PR, it is one self-contained commit and easy to drop; say so and I'll split it out.

@Ark0N

Ark0N commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Thanks @opticon454, this round closes all four points from the first review exactly as asked: allowedKeys is empty, Copilot declares its own mcpConfig, a resume value has to start with a letter or digit (the golden test covering --yolo, -n and friends is a nice touch), and the Docker docs say to sign in inside the container. The selection fix, the readiness mark and the agent read reader are all good catches too. Typecheck, lint, format and the full gate pass here, and CI is green.

Two of the newer pieces need a fix before merge:

  1. Copilot Free shows 100% used (src/usage-telemetry.ts:165). A Free account answers premium_interactions: { entitlement: 0, remaining: 0, percent_remaining: 0, unlimited: false, has_quota: false }, so parseCopilotQuotaResponse returns usedPercentage: 100 and the chip shows Copilot mo 100% for a plan with no premium quota. Please return null when entitlement is not positive or has_quota === false, and add that payload as a fixture in test/copilot-plan-usage.test.ts. On that plan chat (200) and completions (2000) are metered too, so the comment about them being unlimited on every plan needs rewording.

  2. agent read is empty when the answer is taller than the pane (src/web/response-viewer-transcript.ts:354). Rows are skipped until a ❯ <prompt> row is seen, and with only the visible screen to read, a long answer pushes that row off the top: a 40-row screen holding a 35-line answer returns "", which a caller cannot tell apart from "no answer yet". When no prompt row is on screen but the session has already been sent a prompt (or there is no banner on screen), please treat the non-chrome rows above the composer as the tail of the last answer, and add a test with the prompt row scrolled off. docs/copilot-integration.md:171 already describes the tail behaviour, so the code just needs to match it.

  3. The PageUp/PageDown fallback (src/web/public/terminal-ui.js:5653). The comment and docs/copilot-integration.md:95 say a stale tracking flag falls back to page keys, but _localScrollbackIsHollow() (line 5751) only covers claude and opencode, so for Copilot the wheel scrolls an empty local buffer in that window. Either measure that Copilot pages on PgUp/PgDn and add it there (and in the TerminalTile twin) with a test that asserts the keys, or drop the claim from the comment, the docs and the test name.

Smaller things I will pick up at merge time unless you get to them first:

  • X-GitHub-Api-Version: 2025-04-01 (src/utils/copilot-plan-usage.ts:86) is not a version GitHub supports on its documented endpoints (it answers 400 there); copilot_internal/user ignores it today. I will switch it to 2022-11-28.
  • Checking readPlanUsageTelemetryEnabled() before isCopilotAvailable() in src/web/server.ts:2871, so a switched-off chip skips the resolver probe as well.
  • Doc wording: Past Sessions resumes Copilot with --continue, not --resume <id>; Copilot has no respawn; only a manually chosen tab name goes out as --name; plus the stale comments at src/types/session.ts:549 and src/config/cli-registry/stock.ts:1574.
  • The wheel and mouse-strip paragraphs in CLAUDE.md and docs/architecture-invariants.md, a missing palette label in panels-ui.js, two small typos in the README and docs/cli-registry.md, and the changeset text.

Once 1 to 3 are in, this is ready to merge. Thanks again for taking Copilot all the way to a complete mode, with measurements to back every choice.

@opticon454

Copy link
Copy Markdown
Contributor Author

Thanks, all three are fixed in 0ec702fa, each with a test. Full suite, typecheck, lint and format are clean locally.

1. Free shows 100% used. parseCopilotQuotaResponse now returns null when has_quota === false or the entitlement is not positive, so a Free account gets no Copilot row. Your payload is a fixture in test/copilot-plan-usage.test.ts (Free: chat 200 and completions 2000 metered, premium_interactions with no quota). The comment, docs/copilot-integration.md, the wiki and CLAUDE.md no longer say chat and completions are unlimited on every plan: they are unlimited on the paid plans, metered on Free, and the chip shows neither.

2. agent read on a tall answer. With no prompt row on screen and no banner or trust dialog either, the rows above the composer are now the tail of the last answer. Tests: prompt scrolled off, a leading ● row still on screen, the banner / trust dialog still reading as nothing, and the prompt row preferred when it is visible. One case I hit while writing them: text typed in the composer is a ❯ <text> row between the two rules and would have counted as "the first prompt", so typing under a tall answer made its tail read as banner. A prompt-shaped row flanked by the rules is now treated as the composer, with a test.

3. PageUp/PageDown fallback. I measured it rather than drop the claim: on 1.0.95 in tmux, three PageUp moved a 140-line answer from lines 109-140 to 010-042 and three PageDown returned, so Copilot does page on those keys (SGR wheel reports also scroll it, 3 lines per report). _localScrollbackIsHollow() now covers copilot, which is the single function the main pane and the TerminalTile twin both call. Tests assert \x1b[5~ / \x1b[6~ for the main pane and for a tile, and that a Copilot pane with real local scrollback is left alone. The comment, the docs and architecture-invariants.md are updated to match.

Smaller things, done so you do not have to:

  • X-GitHub-Api-Version is 2022-11-28 (checked against the live endpoint, still 200).
  • readPlanUsageTelemetryEnabled() is checked before isCopilotAvailable(), so a switched-off chip skips the resolver probe too.
  • 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 goes out as --name. Corrected in copilot-integration.md, the Agent CLIs wiki, cli-registry.md and the PR description.
  • The stale comments in src/types/session.ts and stock.ts, the missing palette label in panels-ui.js, the wheel / mouse-strip paragraphs in CLAUDE.md and architecture-invariants.md, and the changeset text.
  • I read through the README and docs/cli-registry.md additions for the typos you mentioned and did not find any, so if you spotted specific ones I have not fixed them.

@opticon454
opticon454 force-pushed the feat/cli-copilot branch 2 times, most recently from 0557971 to 9ebce8b Compare October 13, 2026 21:02
@opticon454

Copy link
Copy Markdown
Contributor Author

Rebased onto 1.41.1 (docs-only conflicts in CLAUDE.md and README.md, resolved in favour of upstream's text with the Copilot wording re-applied). Copilot now also declares the transcriptPageKeys capability, since the page-key mode list became capability-driven upstream. No other change; ready for another look.

@Ark0N

Ark0N commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Thanks @opticon454, and sorry this one is still waiting. Here is where it stands after 1.42.0.

The run mode is in good shape. I re-checked the two upstream blockers that parked #198 on a current Copilot CLI (1.0.98): tmux send-keys and the raw-PTY repro from github/copilot-cli#4180 both drive the TUI, and a 6 s, 3000-line shell tool call finishes with exit code 0 inside tmux (github/copilot-cli#4223), in a bare pane and in a Codeman pane. Both issues are still open upstream, but neither reproduces for us. I have two fixes ready locally on top of your head, which I'll add as merge-time commits: every COPILOT_* variable that can bypass approval, run code or move the token is now clamped for a non-granted multi-user owner (COPILOT_HOME, COPILOT_ALLOW_ALL and the BYOK trio were not the only ones; 1.0.98 reads about 170 COPILOT_* names), and README.zh-CN got the Copilot rows README.md has.

The quota row is what holds it. Before shipping a request from the server I had a proper security review done of GET api.github.com/copilot_internal/user. The request itself is carefully built (constant HTTPS URL, redirects refused, an honest User-Agent, only four numbers kept, admin-only in multi-user mode), and I found no way to send the token anywhere but GitHub. The problems are about consent and which token gets sent:

  1. Any GH_TOKEN / GITHUB_TOKEN in the server's environment is sent (src/utils/copilot-plan-usage.ts:25, :46-49), including a classic ghp_ PAT that Copilot itself refuses (1.0.98 detects one in those variables and ignores it). A developer who starts codeman web from a shell with a repo-scoped GITHUB_TOKEN for gh or scripts would have it sent to an internal endpoint 144 times a day from boot, with no browser open.
  2. It is on by default with no consent of its own. The gate is showPlanUsageLimits, which reads an absent key as ON and is also the Claude statusline collection switch. Display is per device but collection is server-wide, so a phone-only setup (chip off by default) never shows a Copilot row while the server keeps polling, and a toggle takes effect only at the next 10-minute tick.
  3. An env token always goes to api.github.com, even when COPILOT_GH_HOST / GH_HOST names a GHE.com tenant (Copilot sends it to that host) or COPILOT_OFFLINE is set.
  4. A rejected token is re-sent every 10 minutes forever, silently: no backoff, no Retry-After, no log line (measured: two 401s exactly 10:00 apart).
  5. config.json is read with readFileSync on the event loop with no file-type or size check, so a FIFO at that path (or a home on a hung mount) freezes the whole server (reproduced), which is the fix(cases): bound path probes for linked workspaces and session creation, so an unreachable mount cannot freeze the server #516 rule in CLAUDE.md. And a fast endless body is not bounded by the 10 s timeout under redirect: 'error' (reproduced at 20 MiB/s on Node 22), with no size cap.
  6. The docs overstate the safeguards: the Security page says only Copilot's own sign-in token is used and that hiding the chip stops it.

There is also a documented route that avoids all of this: account.getQuota from the Copilot SDK (https://docs.github.com/en/copilot/how-tos/copilot-sdk/features/usage-and-billing), answered by the user's own copilot binary over stdio. That is how the Codex row already works (requestCodexRateLimits() in src/utils/codex-cli-resolver.ts): Codeman spawns the vendor CLI with a timeout and an output cap and never touches a credential. It is marked experimental, so it would want a version check.

What I'd suggest:

  • Split the quota row out of this PR (9e85d8b plus the quota parts of 9ebce8b and d29baef), so the run mode can merge on its own.
  • If you want to keep the row, redo it over account.getQuota, behind its own synced setting that is off by default and admin-only to change in multi-user mode, only when copilot is enabled in CLI management, and with a test that pins zero requests on every negative path. It is worth checking what the numbers mean on an AI Credits account first, since GitHub now labels the premium-request page "(legacy)".
  • Two small run-mode hardenings: give the model param the same leading-letter-or-digit pattern resumeId got (a value like --yolo would render as copilot --model --yolo), and consider an exact allowedKeys list instead of the COPILOT_ prefix, so a variable added in a later Copilot release is refused by default (1.0.98 also has names like COPILOT_WEB_FETCH_ALLOW_LOCALHOST, COPILOT_BASH_PATH and COPILOT_HOOK_DIR that are not on the privileged list).

With the quota row split out and those two done, the run mode can go into the next release. Thanks again for the depth here: the measurements in the entry comments and the golden tests made it very reviewable.

@opticon454

Copy link
Copy Markdown
Contributor Author

Rebased onto upstream/master (1.42.0) and force-pushed, so the conflict is cleared. The head is now aae60932. The 9 commits are unchanged apart from the rebase.

Two docs-only conflicts, both resolved by keeping upstream's new text and adding the Copilot entry:

  • skills/codeman/reference/verbs.md and the plugin copy: upstream's new "blocked on codex" wording is kept, with copilot added to the list of CLIs.
  • docs/wiki/Agent-CLIs.md: upstream's new Approvals Inbox row is kept; the Plan usage row keeps the Codex and GitHub Copilot entry.

Typecheck is clean and the full npm test passes here (586 files, 11662 tests passed, 12 skipped). No code conflicts, and the PR carries no whats-new.json.

Your two local merge-time commits should apply on top of this head as they are.

@Ark0N

Ark0N commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Thanks @opticon454 for the rebase onto 1.42.0. To recap, this PR adds GitHub Copilot CLI as a full run mode (registry entry, launch options, the multi-user clamp, BYOK endpoints, footer-based working/idle and model detection, the codeman agent verbs) and a Copilot row in the plan-usage chip.

The run mode is ready apart from one small hardening. The head you pushed still has the quota row exactly as before, so the two asks from my last comment stand:

  1. Please split the quota row out of this PR. At aae60932, src/utils/copilot-plan-usage.ts:25 still sends GH_TOKEN/GITHUB_TOKEN from the server's environment (a classic ghp_ PAT included) to copilot_internal/user, the poll runs by default under showPlanUsageLimits (src/web/server.ts:2917), a GHE.com host or COPILOT_OFFLINE is ignored, a 401 is retried every 10 minutes forever, config.json is read with readFileSync on the event loop (copilot-plan-usage.ts:52), the response body has no size cap (:92), and docs/wiki/Security.md:107 says only Copilot's own token is used. Concretely: drop e79e3b0d and cf0fb018, the quota parts of 2eed5245 and aae60932 (the parser, the poll, plan-usage-latest.ts, the src/utils/index.ts export, the chip row in app.js, the quota tests and fixtures, the Plan usage section of docs/copilot-integration.md), and the quota mentions in the README, the wiki, CLAUDE.md and the changeset. If you want the row back later, a separate PR over the Copilot SDK's account.getQuota, behind its own synced setting that is off by default, is the shape I described last time.

  2. model takes a flag-shaped value (src/config/cli-registry/stock.ts:1694, src/web/schemas.ts:447). copilotConfig: { model: '--yolo' } validates and renders copilot --model --yolo. Please require a leading letter or digit in both places, the way resumeId does now (keeping / for model ids), and add --yolo, -n and --allow-all-tools cases to the copilot block of the golden test.

Smaller things, welcome in the same round, otherwise I will take them at merge time:

  1. agent read splits some answers (src/web/response-viewer-transcript.ts:47 and :342). An answer that shows a shell prompt in a code block ( ❯ npm test, indented under the bullet) is read as a new prompt, so read returns only the text above it. A paragraph between two ---------- rows inside an answer is taken for the composer and dropped. Anchoring the prompt pattern to the column Copilot draws a submitted prompt at (/^ ?❯\s+(\S.*)$/, since continuation rows are indented three spaces) fixes the first, and the PR's existing dialect tests still pass with it. Treating only the last rule/❯/rule triple above the footer as the composer fixes the second. A test for each would be good.
  2. Docs. docs/copilot-integration.md:155 says a remote SSH launch carries the same flags and clamp as a local one, but remote and Docker panes run a bare copilot (defaultRemoteCommandForMode, pinned in test/location-overlay-commands.test.ts). Nothing in the UI sets copilotConfig.model, so the model rows in docs/copilot-integration.md:52 and docs/wiki/Agent-CLIs.md:285 should say API callers (or COPILOT_MODEL for a custom endpoint) rather than the Run menu.
  3. wheelForward. The entry still inherits wheelForward: { mode: 'never' } from agentDefaults() while _shouldForwardWheelToApp forwards Copilot's wheel (src/web/public/terminal-ui.js:5860). Please declare what it does, so the registry describes the CLI completely as types.ts asks.

On my side I will add the two commits I mentioned (the wider COPILOT_* clamp and the README.zh-CN rows) on top. Typecheck, lint, format and the full test gate pass on this head. Once 1 and 2 are in, this merges, and thanks again for all the measuring behind it.

@opticon454

Copy link
Copy Markdown
Contributor Author

Thanks for the review. All five points are in; the head is now 1ed3d8d2.

  1. Quota row split out. I dropped e79e3b0d and cf0fb018, removed the quota parts of the Copilot fix commit (the parser, the poll, the chip row, the tests and fixtures) and the quota text from the docs, the changeset and CLAUDE.md (8c597ced). git diff against upstream/master no longer touches usage-telemetry.ts, plan-usage-latest.ts, app.js's chip code or src/utils/copilot-plan-usage.ts. The quota work is kept on my fork as feat/copilot-plan-usage, and I will not open it as a PR unless you want it, in the shape you described (SDK account.getQuota, off by default).
  2. model and flags. New model-plain token pattern (leading letter or digit, / kept) in the registry, and the same rule in CopilotConfigSchema. The golden test and a new test/copilot-config-schema.test.ts cover --yolo, -n, --allow-all-tools, . and /etc/passwd, and still accept openai/gpt-5.
  3. agent read. The prompt pattern is now /^ ?❯\s+(\S.*)$/, and only the last rule/composer/rule triple counts as the composer. One test for each case you described; both fail without the change.
  4. Docs. The Remote SSH section now says remote and Docker panes run a bare copilot, and the model rows say API callers or COPILOT_MODEL. I checked that the only copilotConfig the UI sends is allowAll.
  5. wheelForward. Neither never nor version-gated fit, so I added an always mode (forwards while the CLI tracks the mouse, no version gate) and set it on Copilot. This one is a design choice you may want named differently; the registry test pins claude as version-gated and copilot as always.

Typecheck, lint, format:check and the full npm test pass here (586 files, 11657 tests passed, 12 skipped). Your two merge-time commits should still apply on top.

Copilot (`copilot`, npm @github/copilot) as a stock CLI entry with the same launch options as the other agent CLIs: model (--model), resume (--resume), continue (--continue) and allow-all (--yolo, clamped off for non-granted multi-user owners). Adds a resolver and GET /api/copilot/status probe, the agent-image package, Run-menu/mobile/logo entries, skill docs and tests.
- Drop GH_TOKEN / GITHUB_TOKEN from env.allowedKeys. The allowlist is one global list with no
  mode context, so they were settable on every session. COPILOT_GITHUB_TOKEN is already admitted
  by the COPILOT_ prefix.
- Declare capabilities.mcpConfig (copilot-json, COPILOT_HOME relocation) so Settings does not
  list Copilot as unsupported while the sync-only target also lists it. The sync-only row is
  skipped while the run mode is enabled.
- Require a leading alphanumeric on the resume id (path-segment in the registry, matching regex in
  the schema): --resume's value is optional, so `--resume --yolo` made --yolo its own flag and
  bypassed the allowAll clamp. Golden cases added; "ids only" wording corrected.
- Remove the unread overlays.credStore line and document signing in inside a Docker container.
- Add the zh-CN "Run GitHub Copilot" string, pin the new declarations in tests and update the
  MCP registry expectations.
Copilot's TUI turns mouse tracking on itself. Under `strip-mux-only` those DECSETs reached
xterm, which then reported a plain drag to the TUI instead of selecting (only Shift+drag
selected), so Auto Copy and copy-on-select did nothing. This is the failure opencode had and
`strip-mux-and-mouse` was added for: the DECSETs are stripped, a drag is a local selection and
clicks still reach the CLI through the browser's hand-encoded tap (`cliMouseTracking`).

Stripping them also stops xterm from encoding the wheel for the CLI, so
`_shouldForwardWheelToApp` now forwards it as SGR reports while Copilot is tracking the mouse
(the same bytes xterm sent natively, which is what scrolling was verified against). Shift+wheel
stays on local scrollback and a stale flag falls back to PageUp/PageDown.

Tests: the strip flavor for copilot (stripped DECSETs, 3J kept, cliMouseTracking, direct-PTY
untouched) and the wheel gate (forwards while tracking, no other non-claude mode changes).
…its for it

Copilot had no composerReadyMark, so `codeman agent spawn --mode copilot` returned as soon as
the session existed, before the composer could take a prompt (and before folder trust was
answered). The mark is the composer's footer hint, not the glyph: the trust dialog draws
`❯ 1. Yes`, so `❯` would call a worker ready while it still asks for trust. Measured on 1.0.95:
the dialog footer reads `↑/↓ to navigate`, the composer's reads `/ commands · ? help`.
`/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.
- New docs/copilot-integration.md in the shape of the other CLI guides: install and identity
  probe, authentication (keychain vs plain-text config.json, COPILOT_GITHUB_TOKEN), what Codeman
  wires up (Run button --yolo and its clamp, model, resume, continue, --name rule), BYOK custom
  endpoints, terminal behaviour (drag selection, wheel and touch), the codeman agent verbs
  (ready mark, read), plan usage, Docker, Remote SSH, what is deliberately not wired, known gaps.
- Agent-CLIs wiki: the eleven-modes table, a GitHub Copilot CLI section, the env prefix row, the
  feature-comparison rows and the status endpoint list.
- README, Home, FAQ, Core Concepts, Docker Cases, Remote SSH, Contributing, The Dashboard and
  Custom Model Endpoints: mode lists and counts; CLAUDE.md: stack line, SessionMode, prefixes,
  resolver list, external-CLI notes and the plan-usage source; api-reference MCP target wording;
  the env allowlist error message names COPILOT_*.
…allback, review nits

Review of 8659ba2..be987fb:

- 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.
The Copilot quota chip row moves to its own PR (branch feat/copilot-plan-usage on the fork). This removes its docs, changeset line and CLAUDE.md note.
Like resumeSessionId, model follows a flag, so --model --yolo made --yolo its own flag and bypassed the allowAll clamp. New model-plain token pattern, the same rule in the create schema, and golden and schema tests.
…ide an answer

A submitted prompt is only a prompt at column 0 or 1, so an indented shell prompt in a code block stays answer text. Only the last rule, composer row, rule triple is the composer, so a paragraph between two rules in an answer is no longer dropped. A test for each.
…PI or COPILOT_MODEL only

The Remote SSH section claimed the same flags and clamp as a local launch, but defaultRemoteCommandForMode starts a bare copilot (pinned in test/location-overlay-commands.test.ts). Nothing in the UI sets copilotConfig.model, so the model rows now say API callers, or COPILOT_MODEL for a custom endpoint.
The entry inherited wheelForward never from agentDefaults() while _shouldForwardWheelToApp forwards Copilot's wheel whenever the CLI tracks the mouse, at any version. New always mode, set on copilot, pinned by a registry test.
@opticon454

Copy link
Copy Markdown
Contributor Author

Rebased onto the current master (de99a6d5); the head is now ebfa4a01, still 12 commits. Two conflicts, both in docs that master reworded since the last rebase:

  • skills/codeman/SKILL.md (and its plugins/ copy): master now splits codex out of the "have neither" list, so I kept that wording and added copilot to the "have neither" group.
  • docs/wiki/Agent-CLIs.md: kept master's reworded "Precise idle detection" row and added Copilot to the screen-check list.

No code changes beyond that. Re-checked on the rebased head: tsc, lint and the Prettier check are clean, and the 52 test files that touch Copilot or the CLI registry pass (1483 tests). I have not re-run the full suite.

@Ark0N

Ark0N commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Thanks @opticon454 for taking this all the way. This PR adds GitHub Copilot CLI as a full run mode: registry entry, launch options with the multi-user clamp, BYOK endpoints, footer-based working/idle and model detection, the selection and wheel strip, and the codeman agent verbs. Every point from the last round is in: the quota row is split out, model and resumeId refuse flag-shaped values (with golden and schema tests), the agent read prompt and composer fixes have their tests, the remote/Docker docs say a bare copilot, and wheelForward is declared. Typecheck, lint, format, the catalogue and plugin drift checks and the full gate pass here.

A few small things remain. I will take them at merge time unless you would rather push them yourself:

  1. Docker sign-in sentence (docs/wiki/Docker-Cases.md:123, docs/copilot-integration.md:129). "or set COPILOT_GITHUB_TOKEN for the session" cannot work: a docker quick-start refuses envOverrides (src/web/routes/session-routes.ts:3879), and Copilot's dockerExecEnvNames is empty (stock.ts:1748), so a server-env token is not forwarded either. I will drop that half-sentence and leave "sign in inside the container".
  2. agent read and rule rows (src/web/response-viewer-transcript.ts:374). A divider row inside an answer closes the response block, so an answer like Summary / ──── / Details reads as Details only. The test at test/response-viewer-transcript.test.ts:544 pins that (it drops "Here it is:"). Inside an open response block a divider should stay part of the answer, with only the composer triple as a boundary, and the test should expect both parts.
  3. Route test for the screen read (src/web/routes/session-routes.ts:2826). An app.inject test with a mux stub, one case where capturePaneText returns a Copilot screen and one where it returns null, so copilot never falls back to the stream unnoticed.
  4. Small doc drift. docs/cli-registry.md:353 says Past Sessions uses resumeSessionId (it sends only continueSession); the wheelForward JSDoc in src/config/cli-registry/types.ts:547 should say the value is declared only (the browser gate is terminal-ui.js); and COPILOT_ is missing from the prefix lists in the CLAUDE.md Security table and the allowedEnvPrefixes() comment in src/web/schemas.ts.

On my side I will add the two commits I mentioned earlier, the wider COPILOT_* clamp and the README.zh-CN rows, and squash with a message based on the changeset (the PR body still describes the plan-usage row). Then this goes into the next release. Thanks again for all the measuring behind it.

@Ark0N
Ark0N merged commit ebfa4a0 into Ark0N:master Oct 18, 2026
2 checks passed
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
…de or move the token

The COPILOT_ prefix admits every variable Copilot reads, and the 1.0.98
bundle reads far more than `copilot help environment` lists. Only
COPILOT_HOME, COPILOT_ALLOW_ALL and the BYOK trio were privileged, so a
non-granted multi-user owner could still hand back what the allowAll
clamp removed:

- approval: COPILOT_PLAN_THEN_AUTOPILOT, COPILOT_ASSISTED_APPROVAL
- code with no approval row: COPILOT_CLI_DIST_DIR runs <dir>/index.js
  in place of the CLI, the package caches choose the CLI build,
  COPILOT_EDITOR, COPILOT_PROVIDER_API_KEY_COMMAND and
  COPILOT_AHP_HOST_BIN are executed, skills, provider configs and hooks
  are loaded from the named places (the DSH_HOME class)
- token redirects: COPILOT_API_URL and the other API bases receive the
  CLI's sign-in token, which in multi-user mode is the operator's (all
  users share the OS account and ~/.copilot), and the rest of the BYOK
  provider family re-aims a Codeman custom endpoint (the
  DEEPSEEK_BASE_URL / OMP_AUTH_BROKER_URL class)

All are now privilegedEnvKeys. The registry schema capped that list at 8;
the cap is now 48, since a longer deny list only takes more away from
non-granted owners. The test sends one of each class through the real
schema (they all pass it) and the real clamp. The docs say the list is
read from the 1.0.98 bundle and that a variable a later release adds is
not covered until it is listed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
…PILOT_* in the allowlist line

The PR names GitHub Copilot CLI throughout README.md (ten CLIs, the
install list, the run-mode table, the multi-CLI prefixes,
the agent-read note and the architecture diagram), and README.zh-CN.md,
which is kept in parity with it since 1a692f5, still said nine CLIs and
never mentioned Copilot. The same spots are translated here (not the plan-usage line: the quota
row was split out of the PR, so neither README names Copilot there).

Both READMEs' "Schema-validated inputs" bullet lists every admitted
env prefix and missed the new COPILOT_* one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
… by token

Both Docker sections said "or set COPILOT_GITHUB_TOKEN for the session",
which cannot work: a docker quick-start refuses envOverrides
(session-routes.ts), and Copilot's dockerExecEnvNames is empty, so a token
in the server environment is not forwarded into the container either. The
sentence now says to sign in inside the container and why the token route
does not exist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
The pane segmenter ended the response block at every full-width rule, so
an answer that draws one (a markdown `---`, a passage quoted between two
rules) read as only its last section: `Summary / ──── / Details` came
back as "Details", and the existing test pinned that by expecting the
quoted passage without its "Here it is:" lead-in.

Inside an open response a rule is now held and written into the text as
`---` once more of the same answer follows, so a rule that only separates
the answer from the footer never trails it. The composer's own two rules
(the last rule/❯/rule triple) are still boundaries. The old test now
expects both parts, and a new one pins a Summary/rule/Details answer as
one block; both fail without the change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
GET /api/sessions/:id/last-response reads a copilot pane through
capturePaneText, since a repainting full-screen TUI streams cursor moves
rather than lines, and nothing pinned that at the route: had the branch
stopped capturing, copilot would quietly have been segmented off the
fused stream again. Three app.inject cases with a stubbed mux: the
captured screen is what gets segmented (the stream text never shows),
a null capture falls back to the buffer, and a line-streaming mode
(opencode) never asks for the screen. The first two fail with the
capture switched off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
…v prefixes

- docs/cli-registry.md said Session history resume goes through
  copilotConfig.resumeSessionId / continueSession. Past Sessions sends only
  `continueSession: true` (terminal-ui.js, the modeConfigKey table);
  resumeSessionId is for API callers.
- The wheelForward JSDoc in cli-registry/types.ts read as if the value had
  effect. Nothing reads it (it is in the DECLARED-FOR-LATER list); the
  browser gate is _shouldForwardWheelToApp in terminal-ui.js with its own
  per-CLI rules, which the comment now says.
- The env-prefix lists in the CLAUDE.md Security table and the
  allowedEnvPrefixes() comment in schemas.ts stopped at DEEPSEEK_, missing
  both OMP_ and the new COPILOT_; the Multi-CLI prefix rule missed OMP_.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
…run mode

GitHub Copilot CLI (`copilot`) becomes a run mode: Run menu, welcome
screen, cron and CLI management once it is installed, tmux-backed like
the other agent CLIs, working/idle and the model read off its footer,
launch options through `copilotConfig` (model, resume, continue, the
multi-user-clamped allowAll), Custom Model Endpoints via Copilot's BYOK
variables, MCP sync of its mcp-config.json, a status probe, and the
`codeman agent` verbs (spawn waits for its composer, read returns its
last answer).

The plan-usage quota row that earlier rounds carried was split out on
review and is not part of this merge.

Merge-time commits on top: the wider COPILOT_* clamp and the zh-CN
README rows (maintainer), the Docker sign-in docs, divider rows inside
an answer kept by `agent read`, route tests for the screen-read
last-response branch, and doc drift (Past Sessions, wheelForward JSDoc,
COPILOT_/OMP_ in the env prefix lists).

Verified before merge on Copilot CLI 1.0.98: injected keystrokes,
bracketed paste and Enter reach the TUI in tmux (github/copilot-cli#4180
does not reproduce), a shell tool call completes inside tmux (#4223 does
not reproduce), and a session driven through this branch answered over
send-and-wait.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Ark0N

Ark0N commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Merged and shipped in 1.43.0. Thank you @opticon454, this one took five review rounds and you turned every one around fast, with measurements instead of guesses each time.

Before merging I kept the promise from #198 and checked a current Copilot CLI (1.0.98) myself: injected keystrokes, a bracketed paste and Enter all reach the TUI in tmux, and a shell tool call completes inside tmux, so neither github/copilot-cli#4180 nor #4223 reproduces. A Copilot session driven through this branch answered over send-and-wait end to end.

What I added at merge time, as separate commits so you can see them: the wider COPILOT_* clamp and the zh-CN README rows (mine, from the earlier round), the Docker sign-in sentence, divider rows inside an answer kept by agent read (with your test now expecting both parts), route tests for the screen-read last-response branch, and the doc drift. I merged with a written merge message instead of squashing, so the PR's history stays yours and the old plan-usage description stays out of the log.

One follow-up worth a small PR if you feel like it: Copilot's folder-trust dialog is not answered automatically, so a worker started through the API waits on it until someone presses Enter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants