OpenAgents supports GitHub's official Copilot CLI — the standalone
executable copilot, distributed as the npm package
@github/copilot.
This is not the retired
gh copilotextension. The oldgh extension install github/gh-copilotflow (binarygh, subcommandgh copilot) is deprecated. OpenAgents detects and launches thecopilotexecutable only and never invokesgh.
- Internal agent id:
copilot - User-facing name: GitHub Copilot CLI
- Adapter:
packages/agent-connector/src/adapters/copilot.js - Stream parser:
packages/agent-connector/src/adapters/copilot-stream-parser.js
Install from any official source — OpenAgents auto-detects the copilot
executable regardless of how it got there:
| Method | Command | Notes |
|---|---|---|
| npm (default) | npm install -g @github/copilot |
Requires Node.js. This is what the launcher's installer runs. |
| Homebrew | brew install copilot (if/when published) |
No Node.js requirement. |
| WinGet | winget install GitHub.Copilot (if/when published) |
No Node.js requirement; resolves via %LOCALAPPDATA%\Microsoft\WinGet\Links. |
| Standalone binary | per GitHub's instructions | No Node.js requirement. |
The npm path requires Node.js. Homebrew / WinGet / standalone-binary installs do not require Node.js, and OpenAgents does not force a Node.js requirement on those — only the npm install command is Node-gated.
- OpenAgents looks for
copilot(copilot.cmd/copilot.exeon Windows) across: the isolated runtime prefix (~/.openagents/runtimes/copilot), the enrichedPATH, the npm global prefix, and common install locations (Homebrew,~/.local/bin, WinGet Links). - An install marker is written only after a successful install and never overrides real binary detection.
- A binary on
PATHalways wins over a stale marker.
1.0.0 — declared in registry.json (install.min_version), mirrored in
the adapter (CopilotAdapter.MIN_VERSION) and asserted equal by a test.
Basis. The adapter requires --output-format=json (JSONL) — which per
GitHub's investigation was introduced in 0.0.422 — plus non-interactive -p
with --no-ask-user, granular --allow-tool/--add-dir, --resume/--name
session control, and --secret-env-vars/--no-remote. Per the spec
("采用所有必需能力中的最高版本"), the floor is the GA 1.0 line, which is the
first release series where the full set is present and stable and the documented
CLI Reference applies. The integration was verified end-to-end against
1.0.63 (the current latest; published range is 0.0.326 … 1.0.63).
The gate is generic — any registry entry may declare
install.min_version(seepackages/agent-connector/src/installer.js). If GitHub's changelog later pins an exact earlier 0.0.x build that has all required flags, lower this one field (and the adapter constant) accordingly.
Two enforcement points share the same floor:
installer.healthCheck('copilot')for the launcher UI (table below).CopilotAdapter._checkVersionGate()runscopilot --versionbefore spawning a turn and refuses (with an upgrade message) when below the floor, so an old CLI never gets a turn that would fail withunknown option.
Health-check behaviour (installer.healthCheck('copilot')):
| Detected version | installed |
compatible |
ready |
Behaviour |
|---|---|---|---|---|
≥ min_version |
true |
true |
per auth | Normal. |
< min_version |
true |
false |
false |
Launch blocked, upgrade prompt. Never shown as "not installed". |
| unparseable | true |
null (unknown) |
per auth | Not blocked, not falsely "compatible". |
| not found | false |
null |
false |
Offer install. |
Version results are cached briefly and the cache is cleared on install/uninstall.
The Copilot CLI authenticates against GitHub, not an OpenAgents-collected
API key. Verified token precedence (from copilot login --help /
copilot help environment, v1.0.63):
COPILOT_GITHUB_TOKENGH_TOKENGITHUB_TOKEN
If none is set, the CLI uses a token from a prior copilot /login (stored in
the system credential store, else plaintext under ~/.copilot/), or a
gh auth session. Supported token types: fine-grained v2 PATs
(github_pat_…) with the "Copilot Requests" permission, OAuth tokens from
the Copilot CLI app, and OAuth tokens from gh. Classic ghp_ PATs are NOT
supported. GitHub Enterprise (data residency): GH_HOST / COPILOT_GH_HOST
or copilot login --host. BYOK custom providers: COPILOT_PROVIDER_*.
You can either:
- Sign in interactively: run
copilotonce and use/login(browser device flow), or - Provide a token: set
COPILOT_GITHUB_TOKEN(the launcher offers an optional, password-masked field for this).
There is no side-effect-free, non-interactive "am I authed?" command
(verified: an unauthenticated -p … --output-format json run prints the auth
error to stderr, leaves stdout empty, and exits 1). So OpenAgents reports a
four-state auth_status rather than a binary verdict:
auth_status |
Meaning |
|---|---|
ready |
A token env var is present (positive signal). |
unknown |
No token env var detected — but you may already be signed in via copilot /login, the keychain, or gh. Not a claim of "no credentials**; does not block a launch attempt. |
no_credentials |
(Other agents) creds definitively absent. |
| — incompatible — | Version below the floor (separate compatible:false). |
Copilot is marked unverifiable in the registry, so absence of a token yields
unknown, never a false "not signed in". The final authority is the CLI's own
run result — auth/authorization failures are classified from live stderr (see
Common errors).
The registry flag above is about the core, which has no per-platform creds path for Copilot. The launcher does have one, so a sign-in completed in the terminal flips the agent to Ready without the user confirming anything.
It reads ~/.copilot/config.json, which the CLI maintains itself and where a
completed login records loggedInUsers: [{ host, login }] (plus a
lastLoggedInUser). This is an identity list, not a credential — the token
is in the OS credential store, never in this file, and the probe reads nothing
out of it beyond whether the list is non-empty.
Two properties of that file are worth knowing if you touch this:
- It is JSONC. A two-line
//header explains that user settings belong insettings.json. A strictJSON.parsethrows on it, which is exactly the bug this probe was written against: the launcher reported "sign-in not confirmed" to a user the terminal had just greeted by name.credsVerdictnow retries with a string-aware comment strip — necessary because the same file stores"host": "https://github.com", and a naive regex would cut that value at its//and corrupt the parse it was meant to rescue. - The field is an array.
![]isfalsein JavaScript, so an empty list would have read as signed in. Empty containers now count as no value.
There is deliberately no host guard. Entries carry a host, so a GHE
(GH_HOST / COPILOT_GH_HOST) session for a different host is conceivable —
but that behaviour is unverified, and a guard written on a guess would report
signed-out for working setups. The milder failure is preferred: a wrong-host
session reads as signed in, and the CLI's own run result corrects it.
OpenAgents never reads, prints, or forwards your token: it does not run commands that echo a token, never logs env-var values, never reads the system keychain contents, and never sends any token to the workspace frontend.
- Install GitHub Copilot CLI from the launcher (or
npm install -g @github/copilot). - Sign in (run
copilot, or setCOPILOT_GITHUB_TOKEN). - Create a "GitHub Copilot CLI" agent and pick a project working directory. The directory must exist — the agent will not silently fall back to another folder.
- Send a task in the workspace. You'll receive real-time:
- assistant text, thinking/status narration,
- tool calls, shell commands (with exit codes), file edits,
- and a single final answer per turn.
- Stop a running task from the workspace at any time.
| OpenAgents mode | Copilot CLI behaviour |
|---|---|
| plan | Launched with --plan — analysis/planning only; no write tools granted. |
| act (execute) | Read/write/shell granted (least-privilege), scoped to the working directory via --add-dir. |
- Filesystem access is scoped to the working directory (
--add-dir <wd>). We do not pass--allow-all-paths. - Interactive prompts are disabled (
--no-ask-user) because the workspace cannot answer them. If the CLI still asks, the turn fails with a clear message instead of hanging. - In act mode a minimal tool set is pre-authorized:
--allow-tool=shelland--allow-tool=write(tool IDs verified againstcopilot help permissions, v1.0.63). We do not default to--allow-all/--yolo, nor grant network/URL access (--allow-url/--allow-all-urls). --secret-env-vars=<names>is passed (only when a secret var is actually set) so the CLI strips/redacts those env values from shell/MCP environments and its own output — in addition to OpenAgents' own redaction. Only variable NAMES are passed; token values never enter argv.- Optional-value flags use the
=form (--allow-tool=…,--resume=…,--secret-env-vars=…) as required by the CLI's argument parser.
Tool IDs are centralized in
ACT_ALLOW_TOOLSin the adapter.write= file create/modify;shell= all shell commands (Copilot also supports finer matchers likeshell(git:*)).
Verified session semantics (copilot --help, v1.0.63):
-r, --resume[=value]resumes by session ID, task ID, ID prefix (7+ hex), or name (name match is exact, case-insensitive).-n, --name <name>sets a name for a new session;--session-id <uuid>resumes by ID or fixes a new session's UUID.- Session/state files live under
~/.copilot(override:COPILOT_HOME).
How OpenAgents uses this:
- It persists the real Copilot session id emitted in the JSONL
sessionevent (per channel, under~/.openagents/sessions/) and resumes via--resume=<id>. It never impersonates a session with the OpenAgents agent id. - The first turn seeds a stable, working-directory-bound
--name(a hash of working dir + workspace + channel), so a session for one project is never resumed against another, and concurrent agents in the same dir don't cross sessions. - A resume against a vanished session — verified real error
Error: No session, task, or name matched '…'(stderr, exit 1, empty stdout) — is detected and transparently retried as a fresh session.
Verification status. Session flags are verified against the real CLI. Whether a non-interactive
-prun emits asessionid on stdout could not be confirmed (no Copilot subscription in CI — auth blocks before any model output). If, in practice, no session id is returned, resume-by-name still works via the seeded--name; if neither proves reliable, the adapter degrades to one-session-per-task and never fakes a resumed conversation.
Stopping a task sends an escalating interrupt — SIGINT → SIGTERM →
SIGKILL — to the whole process group (Unix) or via taskkill /T
(Windows), so any shell/MCP children Copilot spawned are cleaned up too. After a
stop the thread settles to Idle/Stopped and no further messages are pushed.
- Tokens (
COPILOT_GITHUB_TOKEN,GH_TOKEN,GITHUB_TOKEN, OAuth/fine-grained PATs),Authorization/x-api-keyheaders, and secret env values are redacted from all OpenAgents logs and diagnostics. Tool args and error text are redacted too. - The full prompt is never logged (the spawn log shows
<prompt>); raw JSONL is not dumped — unknown events log only a short redacted diagnostic. - The prompt is a discrete
-pargv element, never concatenated into a shell string — no command/argument injection.
These are Copilot CLI behaviours, not OpenAgents'. We distinguish them precisely rather than claiming "nothing is ever uploaded":
| Mechanism | Default | What OpenAgents does |
|---|---|---|
--share-gist (upload session to a secret gist) |
opt-in only | Never passed. |
--share[=path] (write a session markdown file) |
opt-in only | Never passed. |
Remote control (--remote: drive the session from GitHub web/mobile) |
CLI default unspecified | We pass --no-remote to disable it for workspace runs. |
Session history stored locally under ~/.copilot (COPILOT_HOME) |
on | Not modified — uses your existing Copilot config. |
Agentic memory (memory, cross-session fact recall) |
on (per CLI) | Not modified — uses your existing Copilot config. |
| Telemetry / OpenTelemetry | per your Copilot config | Not enabled or configured by us. |
OpenAgents does not modify your global Copilot configuration. Where the CLI
supports a disable flag we use it for the run (--no-remote); session/memory
storage remains governed by your Copilot setup.
- A prompt passed as a process argument may be visible to the OS process list / diagnostic tools while the task runs.
- Copilot's local session history (under
~/.copilot) may contain prompts, replies, tool output, and file changes — it is local but not OpenAgents- managed; clear it via Copilot if needed.
| Platform | Status |
|---|---|
| Linux | Real CLI 1.0.63 verified: install, --version, --help, help environment/permissions, login --help, and unauthenticated / invalid-token / stale-resume runs (stderr + exit codes). Adapter unit-tested with a mock CLI. A successful task (real subscription) was not run — see below. |
| macOS | Implemented (process-group interrupt, Homebrew detection). Real-CLI run not verified in this environment. |
| Windows | Implemented (copilot.cmd/.exe, npm-shim → node resolution, taskkill /T). Real-CLI run not verified in this environment. |
Captured real-CLI output (de-identified) lives in
packages/agent-connector/test/fixtures/copilot-cli-real-samples.md.
| Symptom | Likely cause | Action |
|---|---|---|
| "GitHub Copilot CLI not found" | copilot not installed / not on PATH |
Install via npm/Homebrew/WinGet. |
| "…is too old — upgrade…" | Below min_version |
Upgrade the CLI. |
| "Not signed in to GitHub Copilot…" | No credentials (real: No authentication information found) |
Run copilot /login, gh auth login, or set COPILOT_GITHUB_TOKEN. |
| "token is invalid, expired, or revoked (401)…" | Token rejected (real: could not be validated … 401 … Bad credentials) |
Use a fine-grained token with "Copilot Requests" (classic ghp_ not supported) or /login. |
| "Access denied (403)… SAML/SSO…" | SSO not authorized / insufficient scope | Authorize the org's SSO for your token. |
| "blocked by an organization/enterprise policy" | Org disabled Copilot CLI | Contact your GitHub org admin. |
| "No active GitHub Copilot subscription/seat" | No Copilot entitlement | Obtain a Copilot subscription/seat. |
| "GitHub host configuration error…" | Bad GH_HOST/COPILOT_GH_HOST |
Fix the GHE data-residency hostname. |
| "Custom model provider (BYOK) is misconfigured…" | Bad COPILOT_PROVIDER_* |
Fix provider base URL / key / type. |
| "model is unavailable" | COPILOT_MODEL not allowed |
Clear COPILOT_MODEL or pick a supported model. |
| "requested interactive input…" | CLI hit an ask_user prompt |
Re-run with a more specific task. |
| "Working directory does not exist" | Bad project path | Pick an existing directory. |
-
Verified against real CLI
1.0.63: the full flag set the adapter uses (-p,--output-format json,--stream,--model,--add-dir,--no-ask-user,--plan,--allow-tool=shell|write,--resume=,--name,--secret-env-vars=,--no-remote), the tool IDsshell/write, the token env vars, and error/stderr/exit-code behaviour for unauth/invalid-token/stale-resume. -
Success-path JSONL schema — now captured from a live authenticated run (CLI v1.0.83), and nothing the original guess predicted was right. Real events are
namespace.verband carry their payload underdata:Real event Mapped to Field assistant.message_deltatext_deltadata.deltaContentassistant.reasoning_deltareasoningdata.deltaContentresult(flat, not underdata)donesessionId,exitCode,usagesession.usage_checkpoint,session.auto_mode_resolved,model.call_finishedusage— The guessed names (
text.delta,message.delta,content_block_delta,assistant.message, …) matched nothing, and the envelope was never unwrapped — so every answer parsed as empty and the user was told "No response generated" for turns the CLI had completed successfully ("outcome":"success", exit 0, one premium request billed).assistant.reasoningis deliberately not mapped: it repeats the deltas as one block afterwards, and mapping both narrates the thinking twice.Full observed type list:
assistant.turn_start,assistant.reasoning_delta,assistant.reasoning,assistant.message_start,assistant.message_delta,assistant.turn_end,assistant.idle,model.call_start,model.call_finished,session.auto_mode_resolved,session.usage_checkpoint,session.tools_updated,session.skills_loaded,session.mcp_servers_loaded,session.mcp_server_status_changed,session.custom_agents_updated,user.message,result. -
Still NOT captured: tool / file / shell / permission events. The captured turn was a plain greeting, so none appeared. The inferred names for those remain in the table as a net and are marked as such — an unrecognized event is logged and ignored, never rendered. Replace them from a real tool-using turn rather than adding more guesses.
-
Session id: resolved. A non-interactive run does emit one — but only in the terminal
resultframe, with no session event earlier in the stream. The adapter captures it there, which is what makes--resumework at all. -
No Python SDK adapter ships (the Python daemon was removed; the Node.js agent-connector is the runtime). The Python registry entry is catalog-only.
-
The launcher offers a "Login" button (it opens a terminal on the CLI, where
/loginis run) with the token field kept as an optional alternative — seeKEY_OPTIONAL_LOGIN_AGENTSandDUAL_LOGIN_AGENTSin the launcher'sauth-specs.ts. It reads the sign-in back off disk, so a completed login turns the agent Ready on its own; see the section below. -
Copilot Free permits auto model selection only. Leaving the model empty is the default on both surfaces and the adapter passes
--modelonly whenCOPILOT_MODELis set, so Free accounts work unchanged — but naming a concrete model is refused by the service. For that reason the registry's model list carries noautoentry: the workspace form already renders its own "Auto" option meaning unset, and a secondautothat resolved to--model autowas both redundant and the choice most likely to be picked.