Skip to content

refactor: 2.0 parity — monorepo split, TUI sidebar, standalone CLI#6

Draft
iceteaSA wants to merge 36 commits into
cortexkit:mainfrom
iceteaSA:refactor/parity-v2
Draft

refactor: 2.0 parity — monorepo split, TUI sidebar, standalone CLI#6
iceteaSA wants to merge 36 commits into
cortexkit:mainfrom
iceteaSA:refactor/parity-v2

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Jul 23, 2026

Copy link
Copy Markdown

What this is

Brings the plugin to structural and functional parity with the sibling OpenAI/Anthropic auth plugins. The monolithic plugin.ts (~2,750 lines) is decomposed into a harness-agnostic core library and a thin OpenCode composition root, plus a Pi extension and a deterministic e2e workspace.

Large diff (291 files, +58k/−28k), but the 30 commits are atomic and meant to be read in order — each builds and tests green on its own.

Structure

Four packages under packages/:

  • core — harness-agnostic: OAuth, transport, account pool, rotation, quota, persistence, transforms. Node built-ins only; no host-runtime imports.
  • opencode — the server plugin (fetch interceptor, account runtime, slash commands, RPC) and the OpenTUI sidebar.
  • pi — Pi extension bridging registerProvider to the core transport.
  • e2e-tests — private, deterministic flows against a mock loopback server.

Dependency direction is one-way: opencode and pi depend on core; core depends on nothing in the workspace.

Notable changes

  • Monorepo on Bun workspaces; Biome + lefthook + mise for gates.
  • Host SDK migrated to @opencode-ai/plugin v1.
  • OpenTUI sidebar — a read-only Solid renderer that polls a redacted on-disk snapshot. Tokens, project IDs, and fingerprints never enter the render path.
  • Authenticated loopback RPC — binds 127.0.0.1 only, per-boot 32-byte bearer token, pid-scoped port file.
  • Standalone antigravity-auth CLI — login/list/quota without the host.
  • Six slash commands: /antigravity-accounts, -status, -quota, -routing, -killswitch, -dump.
  • Freshness-merged sidebar state behind a fenced file lock — replaces the proper-lockfile dependency with a dependency-free renewable lock.
  • Model-aware killswitch: a gemini-pro request checks only the pro quota group, not the max of pro+flash.

Breaking changes

  • Version 2.0.0. authorizeAntigravity / exchangeAntigravity now import from @cortexkit/antigravity-auth-core, not the opencode package.
  • Requires an @opencode-ai/plugin v1 host (>=1.17.13 <2).

Verification

build          all packages + TUI compile
unit           1642 pass, 0 fail (95 files)
e2e            28 pass, 0 fail (4 files)
typecheck      clean
format + lint  clean

E2E runs against a mock server behind a loopback-only fetch guard — a non-loopback URL throws instead of reaching the network.

Docs

ARCHITECTURE.md, STRUCTURE.md, and both READMEs are rewritten against the final tree.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Brings the plugin to v2.0 parity by splitting into a harness‑agnostic core, an OpenCode plugin with a TUI sidebar, and a Pi extension, plus a standalone CLI. Moves the repo to Bun, adds a fenced file lock, atomic writes, fail‑closed account storage, a model‑aware killswitch, and a 15s active fetch‑header timeout for more reliable and secure operation.

  • New Features

    • Monorepo split: packages/core, packages/opencode, packages/pi, packages/e2e-tests.
    • Precompiled OpenTUI sidebar with authenticated loopback RPC, a redacted on‑disk snapshot, and 0600 log files.
    • Standalone antigravity-auth CLI (login, list, quota).
    • Model‑aware killswitch; new account rotation and attributed quota manager with backoff.
    • Fenced file lock and atomic JSON writes; fail‑closed on unreadable account storage with timestamped backup; 15s active fetch‑header timeout.
    • CI on Bun with a TUI smoke test and a loopback‑only e2e suite; Biome + lefthook gates; removed .gitignore self‑ignore drift.
    • Docs: clarified refresh token is plaintext under 0600, Pi surface parity (no pool/rotation/killswitch), killswitch default (unset), and fixed table rendering in routing/killswitch docs.
  • Migration

    • v2.0.0: import authorizeAntigravity and exchangeAntigravity from @cortexkit/antigravity-auth-core.
    • Host must use @opencode-ai/plugin v1 (>=1.17.13 <2).
    • Use Bun 1.3+: bun install, bun run build, bun test.

Written for commit af65acf. Summary will update on new commits.

Review in cubic

@socket-security

socket-security Bot commented Jul 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm json-schema is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@opencode-ai/plugin@1.17.13npm/json-schema@0.4.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/json-schema@0.4.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm seroval is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/solid-js@1.9.12npm/seroval@1.5.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/seroval@1.5.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@iceteaSA
iceteaSA marked this pull request as draft July 23, 2026 15:29
@ualtinok

Copy link
Copy Markdown
Contributor

Thanks for putting this together. I reviewed the full diff and ran the Bun migration, package, E2E, OpenCode-loading, and storage paths locally. I don't think this is merge-ready yet.

Blocking findings

1. Account storage can be destructively overwritten

packages/core/src/account-storage.ts currently conflates every read failure with a missing account file:

  • readAndNormalizeV4() returns null for ENOENT, malformed JSON, invalid storage shape, unknown versions, and read failures.
  • mutateAccountStorage() interprets that null as an empty v4 account pool and writes the mutation result over the original file.

I reproduced this with a malformed existing account file: persisting one newly authenticated account replaced the malformed file with a one-account pool. This is the same failure class described in packages/opencode/src/plugin/persist-account-pool.test.ts, but the fail-closed cases at lines 339-347 are it.todo() rather than implemented regressions. The referenced CortexKit issue #89 also does not exist.

The storage API needs to distinguish at least missing from unreadable/corrupt/unsupported, and mutations must fail closed for the latter states rather than initializing an empty pool.

2. CI and release run the wrong test command

Both workflows invoke raw bun test:

  • .github/workflows/ci.yml:40-44
  • .github/workflows/release.yml:54-58

The repository's intended unit gate is the scoped bun run test script. On this branch:

  • bun run test: 1,642 passed, 23 todo, 0 failed
  • bun test: 54 failed

The raw command discovers E2E and support files without the intended setup. Among the failures, the fetch-guard test reached the live network instead of being blocked. Both workflows should call bun run test.

There is currently no authoritative GitHub Actions CI check attached to the PR; only Socket checks and the skipped Cubic review are visible.

3. The E2E suite has a cross-file cleanup race

packages/e2e-tests/src/setup.ts:140-153 performs an afterAll sweep that deletes every directory in the system temp directory whose name starts with agy-e2e-.

The E2E files run in parallel and use that same prefix, so one file can delete another file's active test root. I reproduced this with the supported command:

  • bun run test:e2e: 27 passed, 1 failed
  • failure: ENOENT while the RPC/TUI flow was writing under its root

Cleanup must be scoped to roots owned by the current test file/process. A process-global prefix sweep is also unsafe when two checkouts or CI jobs run concurrently.

4. The documented installation does not load the TUI plugin

The root and OpenCode READMEs say that adding the package only to opencode.json causes OpenCode 1.17.13+ to load the server and TUI automatically through the package's oc-plugin field.

That does not match current OpenCode behavior. OpenCode detects package targets from exports["./server"]/main and exports["./tui"]; it does not read a package-level oc-plugin field. Server and TUI registrations are stored separately in opencode.json and tui.json.

I verified this against OpenCode 1.18.3:

  • manually adding the package only to opencode.json loads the server side but does not configure the TUI side;
  • opencode plugin file:///path/to/packages/opencode detects both targets and writes the package to both configuration files.

The supported installation instructions should use:

opencode plugin @cortexkit/opencode-antigravity-auth

The oc-plugin claims should be removed unless the package targets a host implementation that actually supports that field.

5. The branch needs integration with the latest user-turn fix

A trial merge against current main (5bb1bcb), which contains the verified Antigravity final-user-turn fix, produced conflicts in:

  • docs/ARCHITECTURE.md
  • docs/STRUCTURE.md
  • packages/opencode/src/plugin/request.test.ts

The PR also does not contain that runtime fix, which is required to prevent Requests ending with a model turn are not supported. failures. This should be rebased onto current main, with the final-wire invariant preserved.

Additional findings

  • packages/opencode/package.json documents OpenCode >=1.17.13 <2 but only declares engines.node. Current OpenCode enforces plugin compatibility through engines.opencode, so older hosts will still attempt to load this package.
  • The green unit result includes 23 it.todo() tests, all in persist-account-pool.test.ts; several describe the account-loss behavior above and should not be deferred in this refactor.
  • bun run lint exits successfully while reporting 311 warnings and 58 infos. The new lint gate is therefore not clean or enforcing the stated standards.
  • AGENTS.MD still says the project uses Vitest, recommends bunx vitest, and says no linter or formatter is configured. That conflicts with the Bun/Biome cutover and should be updated in the same migration.

Checks that did pass

  • Typecheck
  • Build
  • Scoped unit suite
  • TUI package smoke test
  • Format check
  • Core/OpenCode/Pi package dry-runs
  • Live OpenCode server-plugin loading and seven-model catalog registration

The core direction is reasonable, but the storage fail-open behavior, broken workflow command, E2E isolation race, and unsupported TUI installation contract are release blockers.

iceteaSA and others added 26 commits July 23, 2026 19:12
…RIES

ensureGitignore/ensureGitignoreSync appended a literal '.gitignore' line to <configDir>/.gitignore whenever missing. For users who track their config dir as a git repo with .gitignore committed, this produced endless working-tree drift (the entry is a no-op for a tracked file anyway — git never ignores tracked files). The four artifact entries (accounts, tmp, signature cache, logs) remain — those genuinely contain secrets/machine state. 22/22 storage tests + full suite 856 passed; dist rebuilt (untracked).
iceteaSA added 10 commits July 23, 2026 19:20
…urity, killswitch, lifecycle)

Lock: stop renewal on ownership loss, idempotent terminal release, eviction marker TTL with PID/createdAt. TUI isolation: import-graph gate in build-tui.test.ts. E2E: unconditional loopback-only fetch deny guard with per-test install/restore. Security: redact project IDs and fingerprints in debug/dump output, enforce 0600 on existing TUI log files. Killswitch: model-aware evaluation, eligibleAccounts wired into selection, fallback recheck. Lifecycle: producers disposed before sidebar drain, consumers after. Tests: all locks released in afterEach, lifecycle disposed, sidebar release awaited.
- refresh token is plaintext under 0600, not encrypted at rest by Google
- pi has no account pool/rotation/killswitch; only transport+transforms are shared
- killswitch.accounts is unset by default (emptyOperatorSettings omits it), not {}
Unescaped | inside `true|false` code spans split the table cell in GitHub's renderer, breaking the argument column for /antigravity-routing and /antigravity-killswitch.
@iceteaSA
iceteaSA force-pushed the refactor/parity-v2 branch from a58c890 to af65acf Compare July 23, 2026 18:15
@iceteaSA

Copy link
Copy Markdown
Author

All five blockers are fixed and pushed. The branch is rebased onto current main (5bb1bcb), now 31 commits, head af65acf.

1. Storage fail-open → fixed in 9bd0a86. readAndNormalizeV4() now returns a discriminated outcome (missing / ok / unreadable), and only ENOENT maps to an empty pool. Mutations on an unreadable file throw a typed AccountStorageUnreadableError (reason: malformed-json | invalid-shape | unsupported-version | io-error) and never write. Before throwing, the corrupt file is copied to <path>.corrupt-<timestamp> as a backup. Legacy v1–v3 shapes still migrate; only versions newer than 4 are treated as unsupported. Your repro — malformed existing file, persist one account — now throws and leaves the file byte-for-byte unchanged. All 23 it.todo() entries are real tests now, and the phantom issue-#89 reference is gone. Callers in auth-loader/oauth-methods propagate the error (with a toast pointing at the file) instead of proceeding as empty.

2. CI test command → fixed in d78f236. Both workflows run bun run test instead of raw bun test. No raw invocation remains in .github/workflows/.

3. E2E cleanup race → fixed in 8fbfba0. Cleanup now deletes only roots created by the current process (tracked in a module-level set). The tmp-wide prefix sweep is gone; an orphan sweep exists behind AGY_E2E_SWEEP_ORPHANS=1 with a 24h mtime bound, off by default. bun run test:e2e ran green three times back-to-back after the fix.

4. Install docs → fixed in e1b8418. You're right that oc-plugin isn't read by any host — the field is removed from package.json and every doc mention with it. Install instructions now use opencode plugin @cortexkit/opencode-antigravity-auth, with a manual subsection showing the separate opencode.json and tui.json entries. engines.opencode: ">=1.17.13 <2" is added. The pack smoke test still validates both export targets resolve from a packed install.

5. Rebase → done. The final-user-turn guard survives at packages/opencode/src/plugin/request.ts (wire-boundary enforcement before dispatch) and your regression test is preserved in request.test.ts alongside the refactored suite. Doc conflicts resolved in favor of the root-level rewrites.

On the non-blocking items: AGENTS.MD is updated for the Bun/Biome toolchain (af65acf), and the 23 todos are implemented as part of item 1. Two things I did not address in this pass: the 311 baseline lint warnings (unchanged — touched files add zero new ones, but the baseline itself needs its own cleanup PR), and stricter lint enforcement. Happy to do either as a follow-up.

Gate results on the pushed head: typecheck clean, bun run test 1675 pass / 0 fail, bun run test:e2e 28 pass / 0 fail, format clean, smoke test green.

@ualtinok

Copy link
Copy Markdown
Contributor

Thanks for the update. I re-reviewed af65acf against current main (5bb1bcb) and reran the gates and targeted failure probes.

The update does fix several items from the first review:

  • rebased onto current main, preserving the final-user-turn fix;
  • CI and release now invoke bun run test;
  • the supported opencode plugin ... installation path and engines.opencode metadata are present;
  • the 23 TODO tests are now implemented;
  • malformed top-level JSON/unsupported storage versions fail closed and preserve the original file;
  • the Bun/Biome commands in AGENTS.MD were updated.

I still found the following unresolved issues.

Blocking findings

1. The combined E2E suite still has the cleanup race

The supported gate still fails on the updated head:

bun run test:e2e
24 pass
4 fail

The failures included:

  • ENOENT while atomically writing/renaming account storage;
  • missing RPC port files;
  • FileLockOwnershipError after the lock file disappeared.

All four files pass when run in separate Bun processes:

  • plugin flow: 8/8
  • CLI flow: 9/9
  • RPC/TUI flow: 5/5
  • fetch guard: 6/6

This isolates the failure to same-process cross-file cleanup. packages/e2e-tests/src/setup.ts:61 stores every test root in the process-wide rootsOwnedByThisProcess set, and the afterAll hook at lines 163-174 deletes every root in that set. Since the combined Bun invocation runs the files in one process, one file's afterAll can still remove another file's active root.

The afterAll process-wide deletion should not run while sibling files are active. Per-test afterEach cleanup already has the exact root it owns; crash/orphan cleanup needs a mechanism that cannot touch roots from the current run.

2. Storage still silently drops malformed account records

packages/core/src/account-storage.ts:495-502 filters the account array down to records having a string refreshToken and then returns state: "ok".

I reproduced this with a v4 file containing:

  • one valid account;
  • one account whose refreshToken was a number.

Calling mutateAccountStorage() to add a third account did not throw. It rewrote the file containing only the original valid account and the new account; the malformed account was silently deleted.

That is still destructive normalization. If any persisted account record is invalid, the file should be classified as unreadable and the mutation should fail closed, preserving the original file and backup, rather than filtering the record out.

3. TUI OAuth callbacks still swallow persistence failures and report success

Both callback paths in packages/opencode/src/plugin/oauth-methods.ts ignore persistAccountPool() failures:

  • lines 1568-1586;
  • lines 1618-1634.

I directly exercised the code-method callback with persistAccountPool() throwing AccountStorageUnreadableError. The callback still returned type: "success" and emitted the success toast:

Authenticated (new@example.com)

Persistence failure must abort the OAuth callback and surface the unreadable-storage/backup details. It must not tell the user that an account was added when it was not saved. This also applies to non-corruption failures such as lock or I/O failure.

4. Manual TUI configuration uses the wrong key

Both READMEs currently show:

{ "plugins": ["@cortexkit/opencode-antigravity-auth"] }

I reran the current OpenCode installer against this package. It wrote both files with the singular key:

{ "plugin": ["file:///.../packages/opencode"] }

The manual tui.json example must use plugin, not plugins.

5. The lint gate is still non-enforcing

The updated branch still reports:

Found 311 warnings.
Found 58 infos.

and exits successfully. Therefore bun run lint is not currently a clean or enforcing quality gate. I do not consider this finding resolved by noting that the touched files add no new warnings; this branch introduces the lint/CI contract, so it should either have a clean baseline with warnings treated as failures or explicitly narrow the enforced scope without presenting the full repository as clean.

Remaining documentation residue

  • packages/opencode/src/plugin/persist-account-pool.test.ts:242 still references nonexistent Issue #89.
  • STRUCTURE.md:329 still says CI runs raw bun test; the workflow now correctly runs bun run test.
  • AGENTS.MD:90-109 still documents the removed pre-monorepo root src/ layout instead of packages/core, packages/opencode, and packages/pi.

Gates that pass

  • typecheck;
  • build;
  • scoped unit suite: 1,675 passed, 0 failed, 0 TODO;
  • format check;
  • packed TUI smoke test;
  • live opencode plugin detection of server and TUI targets.

The branch is now rebased and Git reports it as mergeable, but I still recommend not merging until the E2E race and the two account-persistence correctness issues are fixed and the documented/gated contracts are accurate.

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