Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5b74943
add codex app-server json-rpc client
charlesvien Jun 16, 2026
45b9273
map app-server agent deltas to acp updates
charlesvien Jun 16, 2026
75a8cf4
match documented codex app-server protocol
charlesvien Jun 17, 2026
3a39874
bundle codex cli binary for app-server
charlesvien Jun 17, 2026
0e63f9b
default codex to native app-server harness
charlesvien Jun 17, 2026
589557c
harden codex app-server turn lifecycle
charlesvien Jun 17, 2026
19e823c
feat(codex): add codex app server adapter + e2e tests (#2975)
joshsny Jul 6, 2026
ac10fcd
test(ui): await deferred onModelChange in UnifiedModelSelector test
joshsny Jul 6, 2026
643f956
ci(agent-e2e): gate live e2e on packages/ changes instead of a featur…
joshsny Jul 6, 2026
d8ceabd
ci(agent-e2e): keep e2e dormant until the gateway URL var is set
joshsny Jul 6, 2026
77f8ebc
ci(agent-e2e): source e2e config from POSTHOG_CODE_E2E_* org secrets/…
joshsny Jul 6, 2026
d06de2f
ci(agent-e2e): rename token secret to POSTHOG_CODE_E2E_PERSONAL_API_KEY
joshsny Jul 6, 2026
174a4de
ci(agent-e2e): always run e2e on packages/ changes, fail loud if unco…
joshsny Jul 6, 2026
b767f90
ci(agent-e2e): unify env var names to POSTHOG_CODE_E2E_* end-to-end
joshsny Jul 6, 2026
01f92c8
ci(agent-e2e): reformat config.ts after env var rename
joshsny Jul 6, 2026
a748153
ci(agent-e2e): rename token to POSTHOG_CODE_E2E_GATEWAY_PERSONAL_API_KEY
joshsny Jul 6, 2026
af30578
ci(agent-e2e): fail loud when CI has a token but no gateway url
joshsny Jul 6, 2026
4cd9a00
ci(agent-e2e): read the gateway url from an org var or secret
joshsny Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
outputs:
code: ${{ steps.filter.outputs.code }}
workflow: ${{ steps.filter.outputs.workflow }}
packages: ${{ steps.filter.outputs.packages }}
steps:
- name: Detect relevant changes
id: filter
Expand All @@ -36,6 +37,8 @@ jobs:
- "!.env.example"
workflow:
- ".github/workflows/test.yml"
packages:
- "packages/**"

unit-test:
needs: changes
Expand Down Expand Up @@ -147,3 +150,72 @@ jobs:
name: playwright-report
path: apps/code/playwright-report/
retention-days: 7

e2e:
# Live-model e2e for the @posthog/agent adapters (claude + codex). Runs only
# after the unit + integration jobs pass — a red tree never reaches the
# gateway — and only when a packages/ file changed. Each arm still self-skips
# unless the POSTHOG_CODE_E2E_GATEWAY_PERSONAL_API_KEY secret is present (fork PRs never
# see it) and POSTHOG_CODE_E2E_GATEWAY_URL points at a runner-reachable gateway.
# Drives cheap models (claude-haiku-4-5 / gpt-5-mini), so a run is a handful of
# short turns.
needs: [changes, unit-test, integration-test]
# Always run on any packages/ change (or a change to this workflow), once
# unit-test + integration-test have passed. There is intentionally NO gate on
# the gateway config being present: if the POSTHOG_CODE_E2E_* secret/vars are
# missing, the fail-loud guard (packages/agent/e2e/guard.e2e.test.ts) REDS the
# run rather than skipping to green. Only fork PRs are skipped, since they never
# receive the gateway secret and would red spuriously.
if: ${{ (needs.changes.outputs.packages == 'true' || needs.changes.outputs.workflow == 'true') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build agent dependencies
run: |
pnpm --filter @posthog/shared run build
pnpm --filter @posthog/git run build
pnpm --filter @posthog/enricher run build

- name: Download native codex binary
# Non-fatal at the STEP so a failure surfaces as the fail-loud binary guard
# (guard.e2e.test.ts) with a clear message rather than an opaque download
# error. A missing binary then REDS the run (the guard fails when a token is
# set) instead of letting the codex arm silently skip to green.
run: node apps/code/scripts/download-binaries.mjs || echo "codex binary download failed; the binary guard test will red the run"

- name: Run live e2e (both adapters)
run: pnpm --filter agent run test:e2e
# The suite reads these POSTHOG_CODE_E2E_* names directly
# (packages/agent/e2e/config.ts). The personal API key is an org secret; the
# URL and model/environment overrides are org vars.
env:
POSTHOG_CODE_E2E_GATEWAY_PERSONAL_API_KEY: ${{ secrets.POSTHOG_CODE_E2E_GATEWAY_PERSONAL_API_KEY }}
# Accept the URL from either an org var or an org secret — it has ended
# up in both places, and vars.* can't see secrets.
POSTHOG_CODE_E2E_GATEWAY_URL: ${{ vars.POSTHOG_CODE_E2E_GATEWAY_URL || secrets.POSTHOG_CODE_E2E_GATEWAY_URL }}
POSTHOG_CODE_E2E_CLAUDE_MODEL: ${{ vars.POSTHOG_CODE_E2E_CLAUDE_MODEL }}
POSTHOG_CODE_E2E_CODEX_MODEL: ${{ vars.POSTHOG_CODE_E2E_CODEX_MODEL }}
# Optional: set vars.POSTHOG_CODE_E2E_ENVIRONMENT=cloud to exercise the
# cloud code path (sandbox/permission-profile gating). Unset = local. The
# OS-sandbox enforcement test is macOS-gated, so it doesn't red this linux
# runner.
POSTHOG_CODE_E2E_ENVIRONMENT: ${{ vars.POSTHOG_CODE_E2E_ENVIRONMENT }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ bin/

# tsup bundled config artifacts (temporary files left behind when bundling TS configs)
*.config.bundled_*.mjs
# vite bundled config artifacts (left behind when a vitest run is interrupted)
*.config.ts.timestamp-*.mjs

# Environment
.env
Expand Down
42 changes: 42 additions & 0 deletions apps/code/scripts/download-binaries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
existsSync,
mkdirSync,
realpathSync,
renameSync,
rmSync,
} from "node:fs";
import { dirname, join } from "node:path";
Expand Down Expand Up @@ -47,6 +48,40 @@ const BINARIES = [
return target;
},
},
{
name: "codex",
version: "0.140.0",
getUrl: (version, target) => {
if (target.includes("windows")) {
return `https://github.com/openai/codex/releases/download/rust-v${version}/codex-${target}.exe.zip`;
}
return `https://github.com/openai/codex/releases/download/rust-v${version}/codex-${target}.tar.gz`;
},
getTarget: () => {
const { platform, arch } = process;
const targets = {
darwin: { arm64: "aarch64-apple-darwin", x64: "x86_64-apple-darwin" },
linux: {
arm64: "aarch64-unknown-linux-musl",
x64: "x86_64-unknown-linux-musl",
},
win32: {
arm64: "aarch64-pc-windows-msvc",
x64: "x86_64-pc-windows-msvc",
},
};
const platformTargets = targets[platform];
if (!platformTargets)
throw new Error(`Unsupported platform: ${platform}`);
const target = platformTargets[arch];
if (!target) throw new Error(`Unsupported arch: ${arch}`);
return target;
},
// The codex release archive contains a target-suffixed binary
// (e.g. `codex-aarch64-apple-darwin`); rename it to `codex` after extract.
archiveBinaryName: (target) =>
process.platform === "win32" ? `codex-${target}.exe` : `codex-${target}`,
},
{
name: "rg",
version: "15.0.0",
Expand Down Expand Up @@ -159,6 +194,13 @@ async function downloadBinary(binary) {
await extractArchive(archivePath, DEST_DIR);
rmSync(archivePath);

if (binary.archiveBinaryName) {
const extractedPath = join(DEST_DIR, binary.archiveBinaryName(target));
if (extractedPath !== binaryPath && existsSync(extractedPath)) {
renameSync(extractedPath, binaryPath);
}
}

if (!existsSync(binaryPath)) {
throw new Error(`Binary not found after extraction: ${binaryPath}`);
}
Expand Down
Loading
Loading