Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
166 commits
Select commit Hold shift + click to select a range
011b11d
fix(commandcode): share reasoning-facts table and fix GLM slug decodi…
youngchangjo Aug 15, 2026
39dd296
refactor(fastwire): land FastWire policy resolution behind byte-ident…
olddonkey Aug 17, 2026
33e9b41
fix(fastwire): address A1 review blockers
olddonkey Aug 17, 2026
c603dcd
fix(chat): forward caller service_tier through the chat-to-responses …
olddonkey Aug 17, 2026
39b0eee
devlog: open the Windows stability program unit
lidge-jun Aug 17, 2026
9fa762c
devlog: fold the r1 plan audit back into the Windows unit
lidge-jun Aug 17, 2026
39fcb1a
devlog: close the r2 blockers on the Windows unit
lidge-jun Aug 17, 2026
ba7e90a
devlog: close the two r3 near-pass points
lidge-jun Aug 17, 2026
81d1689
fix(release): parse changelog commits with NUL delimiters
luvs01 Aug 16, 2026
6e9f9cd
test(release): exercise NUL-delimited git log
luvs01 Aug 16, 2026
97f506b
fix(release): validate changelog record framing
luvs01 Aug 16, 2026
038fbad
devlog: close the r5 blockers on the Windows unit
lidge-jun Aug 17, 2026
cc5ba67
devlog: correct the invalid-config backup chmod line
lidge-jun Aug 17, 2026
6a4101f
devlog: fix the remaining 3937 reference in the 040 seed list
lidge-jun Aug 17, 2026
f9cb0fc
devlog: record the seven audit rounds and what they corrected
lidge-jun Aug 17, 2026
70784d2
Allow Codex Work desktop agent task recovery
Aug 17, 2026
817eefd
fix(chat): preserve OpenRouter routing in native passthrough
luvs01 Aug 16, 2026
0ab5f1b
test(chat): cover native OpenRouter routing
luvs01 Aug 16, 2026
660c8ee
test(chat): isolate OpenRouter key-rotation persistence
luvs01 Aug 17, 2026
393d72a
fix(windows): stop the service wrapper killer using the argv Bun rejects
lidge-jun Aug 17, 2026
936aef8
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-chat-…
olddonkey Aug 18, 2026
5a75e57
fix(grok): switch to Responses backend and backfill required annotations
bet4it Aug 18, 2026
4b74f63
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-chat-…
olddonkey Aug 18, 2026
a3169db
fix(windows): one scheduler-wrapper killer, scoped to one installation
lidge-jun Aug 18, 2026
82311ba
docs(test): reflect comment-line keep-alive and responses backend
bet4it Aug 18, 2026
c5c6644
refactor(windows): share the atomic-replace retry instead of one writ…
lidge-jun Aug 18, 2026
fcc9e50
feat(windows): count atomic-replace retries so the envelope can be ar…
lidge-jun Aug 18, 2026
5ca4ffe
devlog: record what the implementation review changed
lidge-jun Aug 18, 2026
e9d879b
fix(minimax): pin bridge traffic to loopback
luvs01 Aug 16, 2026
d7da730
test(minimax): cover lowercase proxy variables
luvs01 Aug 16, 2026
66813eb
feat(fastwire): record per-attempt tier outcomes and price from them
olddonkey Aug 18, 2026
a424413
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
7e8b300
feat(fastwire): separate Fast capability from caller-tier forwarding
olddonkey Aug 18, 2026
d7553d4
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b1-ca…
olddonkey Aug 18, 2026
d7e6c11
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
330350d
fix(fastwire): address B0 review findings
olddonkey Aug 18, 2026
93bd90d
Merge branch 'codex/fastwire-b0-observability' into codex/fastwire-b1…
olddonkey Aug 18, 2026
2496c32
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-chat-…
olddonkey Aug 18, 2026
9902a17
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-chat-…
olddonkey Aug 18, 2026
f4d43c2
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
3c8c633
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b1-ca…
olddonkey Aug 18, 2026
0046816
fix(cursor): pin Connect x-session-id to the client conversation
Aug 17, 2026
4d5850e
fix(cursor): drive external tool-result continuations as userMessageA…
Aug 17, 2026
e3c9965
fix(cursor): split shell owner from Connect session id
Aug 18, 2026
02c7e08
fix(codex): re-derive pool plan from JWT chatgpt_plan_type between WH…
Yuxin-Qiao Aug 18, 2026
bdb4f7f
fix(cursor): stop hide-from-user policy and user-role tool-result replay
Yuxin-Qiao Aug 18, 2026
cffd67a
fix(responses): synthesize placeholder results for orphaned stateless…
harryzhou2000 Aug 17, 2026
bdfc33d
test(responses): pin forward-mode fail-closed behavior for dangling c…
harryzhou2000 Aug 17, 2026
26fa66b
fix(responses): exclude forward-auth replay from stateless placeholde…
harryzhou2000 Aug 17, 2026
6cda90c
fix(responses): keep parallel call batches intact when synthesizing m…
harryzhou2000 Aug 18, 2026
90c0bd2
fix(responses): emit synthetic outputs in call order for parallel bat…
harryzhou2000 Aug 18, 2026
91979cf
fix(sync): refresh catalog for side profiles when Codex injection is OFF
harryzhou2000 Aug 17, 2026
086a950
fix(sync): never inject or touch the journal in external-provider cat…
harryzhou2000 Aug 18, 2026
1f011bd
Use context cap as window when upstream omits it
Aug 18, 2026
6136860
Align unknown-window copy and reject zero floors
Aug 18, 2026
8a9ed4d
Add Models page screenshot for unknown-window copy
Aug 18, 2026
b79e7cf
fix(codex): keep JWT plan recovery off sponsored auth surfaces
Yuxin-Qiao Aug 18, 2026
19afd9e
fix(cursor): keep native-exec recovery guidance host-shell-neutral
Yuxin-Qiao Aug 18, 2026
0c0359f
Replace Models screenshot with cropped UI crop
Aug 18, 2026
fdf1d0a
merge dev into fix/issue-1992-cursor-prompt-injection
Yuxin-Qiao Aug 18, 2026
28b0b5b
merge dev into fix/issue-1989-jwt-chatgpt-plan
Yuxin-Qiao Aug 18, 2026
8b6f217
docs(devlog): record the v2.25.0 release that 060 only prepared
lidge-jun Aug 18, 2026
4b24c9e
Merge pull request #2004 from lidge-jun/codex/release-2250-record
lidge-jun Aug 18, 2026
ef4e768
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
4d87bce
fix(fastwire): address B0 follow-up findings
olddonkey Aug 18, 2026
d2fe94a
Merge branch 'codex/fastwire-b0-observability' into codex/fastwire-b1…
olddonkey Aug 18, 2026
c9dcb47
Merge pull request #1997 from Yuxin-Qiao/fix/issue-1992-cursor-prompt…
lidge-jun Aug 18, 2026
cc54279
fix(tools): repair bare integers in string-declared tool arguments
lidge-jun Aug 18, 2026
5f748cf
fix(fastwire): address B1 review findings
olddonkey Aug 18, 2026
e2fb0b2
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b1-ca…
olddonkey Aug 18, 2026
8afca6e
Merge pull request #1998 from Yuxin-Qiao/fix/issue-1989-jwt-chatgpt-plan
lidge-jun Aug 18, 2026
781ee97
docs(devlog): close eight shipped units to _fin and open the merge ca…
lidge-jun Aug 18, 2026
2bb02c2
Merge pull request #2006 from lidge-jun/codex/devlog-fin-merge-campaign
lidge-jun Aug 18, 2026
af1229d
Merge pull request #1944 from lidge-jun/codex/win-010-powershell-argv
lidge-jun Aug 18, 2026
bd5d6ad
Merge pull request #1945 from lidge-jun/codex/win-020-wrapper-killer
lidge-jun Aug 18, 2026
88e85f2
fix(fastwire): address follow-up review findings
olddonkey Aug 18, 2026
c140758
Merge pull request #1946 from lidge-jun/codex/win-030-atomic-replace
lidge-jun Aug 18, 2026
ac39093
Merge pull request #1947 from lidge-jun/codex/win-031-retry-counters
lidge-jun Aug 18, 2026
ca32042
Merge pull request #1949 from lidge-jun/codex/windows-stability-program
lidge-jun Aug 18, 2026
c0b556a
Merge pull request #1893 from olddonkey/codex/fastwire-a1-refactor
lidge-jun Aug 18, 2026
0ceb061
Merge remote-tracking branch 'origin/dev' into codex/fastwire-b1-capa…
lidge-jun Aug 18, 2026
c78f811
Merge pull request #1965 from olddonkey/codex/fastwire-b1-capability-…
lidge-jun Aug 18, 2026
237f8c0
Merge pull request #1904 from olddonkey/codex/fastwire-chat-tier-copy
lidge-jun Aug 18, 2026
312602c
docs(devlog): close the 260818 merge campaign with its outcome ledger
lidge-jun Aug 18, 2026
e87a5b0
Merge pull request #2008 from lidge-jun/codex/merge-campaign-closeout
lidge-jun Aug 18, 2026
56752d7
fix(responses): route raw reasoning through the expandable summary ch…
AlinJiang Aug 18, 2026
96c2c04
fix(responses): preserve existing summary when content channel is empty
AlinJiang Aug 18, 2026
2d5dc2a
fix(responses): cover non-streaming passthrough and case-insensitive …
AlinJiang Aug 18, 2026
6f83963
fix(responses): keep hideThinkingSummary effective for passthrough re…
AlinJiang Aug 18, 2026
ea2aff0
Merge pull request #2005 from lidge-jun/codex/issue-1938-string-coercion
lidge-jun Aug 18, 2026
9d25b23
Merge pull request #1941 from bet4it/fix/grok-responses-backend
lidge-jun Aug 18, 2026
b04cd26
Merge pull request #1928 from jakemcallister/codex/allow-work-desktop…
lidge-jun Aug 18, 2026
b5a98d6
fix: close three release-audit regressions from today's merge train
lidge-jun Aug 18, 2026
fe3bbad
Merge pull request #2010 from lidge-jun/codex/release-audit-fixes
lidge-jun Aug 18, 2026
4d89cef
docs(devlog): release-readiness record for the next train (2.26.0 rec…
lidge-jun Aug 18, 2026
80fd08f
Merge pull request #2011 from lidge-jun/codex/release-readiness-2260
lidge-jun Aug 18, 2026
709f2c8
docs(devlog): close the release-readiness unit to _fin
lidge-jun Aug 18, 2026
0f5ccf9
Merge pull request #2012 from lidge-jun/codex/fin-release-readiness
lidge-jun Aug 18, 2026
4c82763
docs(devlog): open the bug-PR resolution campaign with its dispositio…
lidge-jun Aug 18, 2026
175e71b
Merge pull request #2013 from lidge-jun/codex/bugpr-campaign-unit
lidge-jun Aug 18, 2026
11c320e
docs(devlog): correct the disposition tally the plan reviewer caught …
lidge-jun Aug 18, 2026
a8355e1
Merge pull request #2014 from lidge-jun/codex/bugpr-tally-fix
lidge-jun Aug 18, 2026
263f8ca
Merge pull request #1991 from EricFeng9/agent/unknown-context-window-…
lidge-jun Aug 18, 2026
e529927
Merge pull request #1931 from harryzhou2000/fix/sync-catalog-side-pro…
lidge-jun Aug 18, 2026
f8b4b78
Merge pull request #1912 from harryzhou2000/fix/responses-stateless-o…
lidge-jun Aug 18, 2026
d06b99d
Merge pull request #1859 from luvs01/agent/preserve-openrouter-native…
lidge-jun Aug 18, 2026
4d07a3d
Merge pull request #1847 from luvs01/agent/nul-delimit-release-changelog
lidge-jun Aug 18, 2026
af24e47
Merge pull request #1845 from luvs01/agent/pin-mmx-bridge-loopback
lidge-jun Aug 18, 2026
6779edb
fix(gui): repair log tooltip encoding (#1935)
olddonkey Aug 18, 2026
991074e
fix(codex): bound warmup response bodies (#1725)
luvs01 Aug 18, 2026
444131e
fix(google): retry transient 429/5xx for AI Studio direct requests (#…
chilung-cgu Aug 18, 2026
9f9f691
Merge branch 'vpr-1800' into codex/land-1800
lidge-jun Aug 18, 2026
16b6f6f
fix: balance the merged slug-codec test blocks
lidge-jun Aug 18, 2026
3617e1c
Merge pull request #2015 from lidge-jun/codex/land-1800
lidge-jun Aug 18, 2026
5ee5109
Merge branch 'vpr-2007' into codex/land-2007
lidge-jun Aug 18, 2026
891c828
Merge pull request #2016 from lidge-jun/codex/land-2007
lidge-jun Aug 18, 2026
c6b8d7e
Merge branch 'vpr-1990' into codex/land-1990
lidge-jun Aug 18, 2026
394b59b
Merge pull request #2017 from lidge-jun/codex/land-1990
lidge-jun Aug 18, 2026
ea16f86
fix(antigravity): drop synthetic x-goog-api-client header on onboarding
lidge-jun Aug 18, 2026
b1ca789
fix(ci): stream Copilot inference prompts over stdin (#1883)
Wibias Aug 18, 2026
542ea37
Merge pull request #2018 from lidge-jun/codex/land-1889
lidge-jun Aug 18, 2026
f2b507f
fix(codex-auth): treat bare WHAM 401 as transient only for a verifiab…
lidge-jun Aug 18, 2026
5f2b939
responses: flatten Codex 0.147 built-in functions namespace in parser
lidge-jun Aug 18, 2026
0128e4d
Merge pull request #2020 from lidge-jun/codex/land-1896
lidge-jun Aug 18, 2026
aaf0469
Merge pull request #2021 from lidge-jun/codex/land-1932
lidge-jun Aug 18, 2026
1ec6c8d
feat(clients): ZCode integration client — managed provider.opencodex …
lidge-jun Aug 18, 2026
6c0bde4
fix: stabilize dev head after the 260818 merge train
lidge-jun Aug 18, 2026
7668adf
fix(zcode): reviewer findings — GUI registration, no guessed output b…
lidge-jun Aug 18, 2026
11f9c6b
docs(devlog): zcode client plan unit with audit/review record and liv…
lidge-jun Aug 18, 2026
38ef999
test: accept both OFF-sync refresh outcomes (CI has no catalog source)
lidge-jun Aug 18, 2026
69650fa
Merge pull request #2026 from lidge-jun/codex/wpv-stabilization-fixes
lidge-jun Aug 18, 2026
ffa6d0e
test(gui): register zcode in the client-list and locale-allowlist suites
lidge-jun Aug 18, 2026
e3bbf53
feat(outbound): route Clash fake-IP DNS answers through the configure…
lidge-jun Aug 18, 2026
34b1673
fix(cursor): normalize empty and failure-state Computer Use tool resu…
lidge-jun Aug 18, 2026
8b9277f
Merge pull request #2037 from lidge-jun/codex/redesign-1748-fakeip
lidge-jun Aug 18, 2026
c42d1eb
Merge pull request #2038 from lidge-jun/codex/redesign-1920-toolresult
lidge-jun Aug 18, 2026
e910919
fix(oauth): redact public authentication errors
luvs01 Aug 16, 2026
47f4c1a
fix(oauth): redact remaining public auth errors
luvs01 Aug 17, 2026
1656da5
fix(oauth): preserve actionable async login errors
luvs01 Aug 17, 2026
3eb47d2
fix(codex): reuse public OAuth error projection
luvs01 Aug 17, 2026
5b8c024
fix(oauth): preserve bounded mutation busy errors
luvs01 Aug 17, 2026
d5dbb28
fix(oauth): preserve terminal login outcomes
luvs01 Aug 17, 2026
e1e4313
fix(oauth): preserve typed reauth identity errors in public projection
lidge-jun Aug 18, 2026
e446607
Merge pull request #2043 from lidge-jun/codex/land-1842-v2
lidge-jun Aug 18, 2026
383d959
docs(structure): scope canonical Fast injection to the bridged Chat path
lidge-jun Aug 18, 2026
0da9e20
Merge pull request #2049 from lidge-jun/codex/wp9-structure04-drift
lidge-jun Aug 18, 2026
8ddb046
docs(devlog): wp10 follow-up designs + campaign phase records
lidge-jun Aug 18, 2026
a5ec641
Merge pull request #2052 from lidge-jun/codex/wp10-followup-designs
lidge-jun Aug 18, 2026
caca898
docs(devlog): close the 260818 bug-PR resolution campaign to _fin
lidge-jun Aug 18, 2026
bcc77c0
Merge pull request #2058 from lidge-jun/codex/campaign-fin
lidge-jun Aug 18, 2026
0cd85fb
Merge pull request #2034 from lidge-jun/codex/zcode-client
lidge-jun Aug 18, 2026
06f8e7a
fix(antigravity): restore collapsed tier rows and effort routing
lidge-jun Aug 18, 2026
2648ffa
fix(codex): classify detail-scoped workspace denials (#2055)
Ingwannu Aug 19, 2026
82b8829
fix(codex): strip app-rewritten provider sub-tables so the provider n…
kinsolee Aug 19, 2026
9636998
fix(google): append continue nudge for Claude-on-Antigravity prefill …
jeongjin0 Aug 19, 2026
0161a66
fix(outbound): keep fake-IP opt-in off NO_PROXY routes (#2045)
Ingwannu Aug 19, 2026
c472ad0
fix(chat): keep the structured-output opt-out exact on the native cha…
ntdatt812 Aug 19, 2026
bd3aa31
fix(lab): report the model gates the way the adapters actually match …
ntdatt812 Aug 19, 2026
bca251c
fix(cursor): normalize text-part tool results (#2044)
Ingwannu Aug 19, 2026
fd85c82
feat(cursor): add HTTP/1.1 compatibility transport (#1903)
lilinxiong Aug 19, 2026
abaa75a
docs(providers): document OpenCode Go quota probe (#2057)
Ingwannu Aug 19, 2026
59964ad
fix(codex): emit modern env_key admission line on non-loopback inject…
lidge-jun Aug 19, 2026
11e03eb
fix(replay): scope durable thought signatures per credential and boun…
lidge-jun Aug 19, 2026
1ad131a
fix(update): stage, verify, and swap npm self-updates with rollback (…
lidge-jun Aug 19, 2026
7535186
test(update): re-pin launcher invariants to the transactional install…
lidge-jun Aug 19, 2026
f2e5e76
fix(update): stage global-style so dependencies land inside the packa…
lidge-jun Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
27 changes: 18 additions & 9 deletions .github/scripts/copilot-workflows.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ const fs = require('node:fs');
const path = require('node:path');

const ROOT = path.resolve(__dirname, '..', '..');
const AI_ACTION = 'actions/ai-inference@2c43c91ae16266ca159d311430343c67a5ffa222';
const CLI_INSTALL = 'npm install --global @github/copilot@1.0.74';
const COPILOT_RUNNER = 'node .github/scripts/run-copilot-inference.cjs';
const CLI_INSTALL = 'bash .github/scripts/install-copilot-cli.sh';
const SETUP_NODE = 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e';
const TOKEN_FALLBACK = 'COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN || github.token }}';
const COPILOT_VERSION = 'COPILOT_VERSION="v1.0.74"';
const COPILOT_SHA256 = 'COPILOT_SHA256="4a708b0a1cbaef4c2ca5c546a622f887a3b70e8a0432bc3cee0d386704816650"';

function readWorkflow(name) {
return fs.readFileSync(path.join(ROOT, '.github', 'workflows', name), 'utf8');
Expand All @@ -16,24 +19,30 @@ function count(text, fragment) {
return text.split(fragment).length - 1;
}

test('issue automation uses pinned Copilot inference without tool access', () => {
test('issue automation streams prompts through the digest-pinned Copilot CLI without tool access', () => {
const quality = readWorkflow('enforce-issue-quality.yml');
const triage = readWorkflow('issue-triage.yml');
const combined = quality + '\n' + triage;
const installer = fs.readFileSync(path.join(ROOT, '.github', 'scripts', 'install-copilot-cli.sh'), 'utf8');

assert.equal(count(quality, AI_ACTION), 2);
assert.equal(count(triage, AI_ACTION), 1);
assert.equal(count(quality, COPILOT_RUNNER), 2);
assert.equal(count(triage, COPILOT_RUNNER), 1);
assert.equal(count(quality, SETUP_NODE), 2);
assert.equal(count(triage, SETUP_NODE), 1);
assert.equal(count(quality, CLI_INSTALL), 2);
assert.equal(count(triage, CLI_INSTALL), 1);
assert.equal(count(quality, 'copilot-requests: write'), 2);
assert.equal(count(triage, 'copilot-requests: write'), 1);
assert.equal(count(quality, 'GITHUB_TOKEN: ${{ github.token }}'), 2);
assert.equal(count(triage, 'GITHUB_TOKEN: ${{ github.token }}'), 1);
assert.equal(count(quality, 'model: ""'), 2);
assert.equal(count(triage, 'model: ""'), 1);
assert.equal(count(quality, TOKEN_FALLBACK), 2);
assert.equal(count(triage, TOKEN_FALLBACK), 1);

assert.match(installer, new RegExp(COPILOT_VERSION.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
assert.match(installer, new RegExp(COPILOT_SHA256.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
assert.match(installer, /sha256sum --check --status/);
assert.match(installer, /releases\/download\/\$\{COPILOT_VERSION\}\/\$\{COPILOT_ASSET\}/);

assert.doesNotMatch(combined, /npm install --global @github\/copilot/);
assert.doesNotMatch(combined, /actions\/ai-inference@/);
assert.doesNotMatch(combined, /\bmodels:\s*read\b/);
assert.doesNotMatch(combined, /max-tokens:/);
assert.doesNotMatch(combined, /copilot-allow-tools:/);
Expand Down
33 changes: 33 additions & 0 deletions .github/scripts/install-copilot-cli.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail

COPILOT_VERSION="v1.0.74"
COPILOT_ASSET="copilot-linux-x64.tar.gz"
COPILOT_SHA256="4a708b0a1cbaef4c2ca5c546a622f887a3b70e8a0432bc3cee0d386704816650"
COPILOT_URL="https://github.com/github/copilot-cli/releases/download/${COPILOT_VERSION}/${COPILOT_ASSET}"

install_root="${RUNNER_TEMP:?RUNNER_TEMP is required}/copilot-cli-${COPILOT_VERSION}"
archive="${install_root}/${COPILOT_ASSET}"
bin_dir="${install_root}/bin"

rm -rf -- "$install_root"
mkdir -p "$bin_dir"

curl \
--proto '=https' \
--tlsv1.2 \
--fail \
--silent \
--show-error \
--location \
--retry 3 \
"$COPILOT_URL" \
--output "$archive"

printf '%s %s\n' "$COPILOT_SHA256" "$archive" | sha256sum --check --status

tar -xzf "$archive" -C "$bin_dir"
chmod +x "$bin_dir/copilot"
"$bin_dir/copilot" --version

printf '%s\n' "$bin_dir" >> "${GITHUB_PATH:?GITHUB_PATH is required}"
73 changes: 73 additions & 0 deletions .github/scripts/run-copilot-inference.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const fs = require('node:fs');
const crypto = require('node:crypto');
const { spawnSync } = require('node:child_process');

function fail(message, code = 1) {
process.stderr.write(`${message}\n`);
process.exit(code);
}

const promptPath = process.argv[2];
let userPrompt;
try {
userPrompt = promptPath
? fs.readFileSync(promptPath, 'utf8')
: fs.readFileSync(0, 'utf8');
} catch (error) {
fail(`Unable to read Copilot prompt: ${error instanceof Error ? error.message : String(error)}`);
}

const systemPrompt = String(process.env.COPILOT_SYSTEM_PROMPT || '').trim();
const prompt = systemPrompt
? `${systemPrompt}\n\n${userPrompt}`
: userPrompt;

const rawTimeout = Number(process.env.COPILOT_TIMEOUT_MS || 120_000);
const timeout = Number.isFinite(rawTimeout) && rawTimeout > 0
? Math.floor(rawTimeout)
: 120_000;

const args = [
'-s',
'--no-ask-user',
'--no-custom-instructions',
'--no-auto-update',
];

const copilotEnv = { ...process.env };
if (copilotEnv.COPILOT_GITHUB_TOKEN) {
// Copilot CLI v1.0.74 authenticates from GH_TOKEN or GITHUB_TOKEN.
copilotEnv.GITHUB_TOKEN = copilotEnv.COPILOT_GITHUB_TOKEN;
}

const result = spawnSync('copilot', args, {
input: prompt,
encoding: 'utf8',
env: copilotEnv,
maxBuffer: 16 * 1024 * 1024,
timeout,
killSignal: 'SIGKILL',
});

if (result.stderr) {
process.stderr.write(result.stderr);
}

if (result.error) {
const errorCode = result.error.code || 'spawn_error';
const signal = result.signal || 'none';
fail(`Copilot CLI execution failed (${errorCode}; signal=${signal}): ${result.error.message}`);
}

if (result.status !== 0) {
process.exit(Number.isInteger(result.status) ? result.status : 1);
}

const outputFile = process.env.GITHUB_OUTPUT;
if (!outputFile) {
fail('GITHUB_OUTPUT is not set.');
}

const response = String(result.stdout || '').trimEnd();
const delimiter = `COPILOT_RESPONSE_${crypto.randomBytes(12).toString('hex')}`;
fs.appendFileSync(outputFile, `response<<${delimiter}\n${response}\n${delimiter}\n`);
123 changes: 123 additions & 0 deletions .github/scripts/run-copilot-inference.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');

const RUNNER = path.join(__dirname, 'run-copilot-inference.cjs');

function makeFakeCopilot(source) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'fake-copilot-'));
const file = path.join(dir, 'copilot');
fs.writeFileSync(file, `#!/usr/bin/env node\n${source}\n`, { mode: 0o755 });
return { dir, file };
}

function outputValue(file, key) {
const text = fs.readFileSync(file, 'utf8');
const match = text.match(new RegExp(`${key}<<([^\\n]+)\\n([\\s\\S]*?)\\n\\1(?:\\n|$)`));
assert.ok(match, `missing ${key} output in ${text}`);
return match[2];
}

test('streams a large prompt over stdin and maps the Copilot token to GITHUB_TOKEN', () => {
const fake = makeFakeCopilot(`
const fs = require('node:fs');
const input = fs.readFileSync(0, 'utf8');
const argvBytes = Buffer.byteLength(process.argv.slice(2).join(' '));
if (argvBytes > 8192) {
console.error('prompt leaked into argv');
process.exit(91);
}
process.stdout.write(JSON.stringify({
inputBytes: Buffer.byteLength(input),
argv: process.argv.slice(2),
githubToken: process.env.GITHUB_TOKEN || '',
}));
`);
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'copilot-runner-test-'));
const promptFile = path.join(dir, 'prompt.txt');
const outputFile = path.join(dir, 'output.txt');
const prompt = 'x'.repeat(512 * 1024);
fs.writeFileSync(promptFile, prompt);
fs.writeFileSync(outputFile, '');

const result = spawnSync(process.execPath, [RUNNER, promptFile], {
encoding: 'utf8',
env: {
...process.env,
PATH: `${fake.dir}${path.delimiter}${process.env.PATH}`,
GITHUB_OUTPUT: outputFile,
COPILOT_SYSTEM_PROMPT: 'system instruction',
COPILOT_GITHUB_TOKEN: 'test-token',
GITHUB_TOKEN: '',
},
});

assert.equal(result.status, 0, result.stderr);
const response = JSON.parse(outputValue(outputFile, 'response'));
assert.ok(response.inputBytes > Buffer.byteLength(prompt));
assert.deepEqual(response.argv, ['-s', '--no-ask-user', '--no-custom-instructions', '--no-auto-update']);
assert.equal(response.githubToken, 'test-token');
});

test('surfaces Copilot stderr and preserves a non-zero exit code', () => {
const fake = makeFakeCopilot(`
process.stdin.resume();
process.stdin.on('end', () => {
console.error('copilot auth failed: test diagnostic');
process.exit(7);
});
`);
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'copilot-runner-test-'));
const promptFile = path.join(dir, 'prompt.txt');
const outputFile = path.join(dir, 'output.txt');
fs.writeFileSync(promptFile, 'hello');
fs.writeFileSync(outputFile, '');

const result = spawnSync(process.execPath, [RUNNER, promptFile], {
encoding: 'utf8',
env: {
...process.env,
PATH: `${fake.dir}${path.delimiter}${process.env.PATH}`,
GITHUB_OUTPUT: outputFile,
COPILOT_SYSTEM_PROMPT: 'system instruction',
COPILOT_GITHUB_TOKEN: 'test-token',
},
});

assert.equal(result.status, 7);
assert.match(result.stderr, /copilot auth failed: test diagnostic/);
assert.equal(fs.readFileSync(outputFile, 'utf8'), '');
});

test('kills a hung Copilot process at the configured timeout', () => {
const fake = makeFakeCopilot(`
process.stderr.write('copilot started\\n');
setInterval(() => {}, 1000);
`);
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'copilot-runner-test-'));
const promptFile = path.join(dir, 'prompt.txt');
const outputFile = path.join(dir, 'output.txt');
fs.writeFileSync(promptFile, 'hello');
fs.writeFileSync(outputFile, '');

const result = spawnSync(process.execPath, [RUNNER, promptFile], {
encoding: 'utf8',
timeout: 5000,
env: {
...process.env,
PATH: `${fake.dir}${path.delimiter}${process.env.PATH}`,
GITHUB_OUTPUT: outputFile,
COPILOT_SYSTEM_PROMPT: 'system instruction',
COPILOT_GITHUB_TOKEN: 'test-token',
COPILOT_TIMEOUT_MS: '75',
},
});

assert.notEqual(result.status, 0);
assert.match(result.stderr, /ETIMEDOUT/);
assert.match(result.stderr, /SIGKILL/);
assert.equal(fs.readFileSync(outputFile, 'utf8'), '');
});
Loading
Loading