Skip to content

refactor(adapters): cut ~3,100 LOC of redundancy in the agent adapter layer (#2349)#2355

Open
harshitsinghbhandari wants to merge 8 commits into
AgentWrapper:mainfrom
harshitsinghbhandari:ao/agent-orchestrator-18/adapter-dedup
Open

refactor(adapters): cut ~3,100 LOC of redundancy in the agent adapter layer (#2349)#2355
harshitsinghbhandari wants to merge 8 commits into
AgentWrapper:mainfrom
harshitsinghbhandari:ao/agent-orchestrator-18/adapter-dedup

Conversation

@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator

Closes #2349.

Consolidates the copy-pasted logic across the 23 agent adapters in backend/internal/adapters/agent/ into a handful of shared helpers, with no behavioral change and one latent correctness fix (missing fsync).

Net delta

-3,105 LOC of non-test production code (-3,454 total incl. tests): 78 files changed, 1,212 insertions, 4,666 deletions.

New shared packages

  • hooksjson — generic matcher-group hooks file (Read/Write/Install/Uninstall/AreInstalled). The claude/goose/qwen/droid hooks.go each collapse from ~180-380 LOC to ~60.
  • binaryutil.ResolveBinary(ctx, BinarySpec) — table-driven binary search; per-adapter resolvers shrink to a ~10-line BinarySpec literal.
  • agentbase.Base (embed) + agentbase.StandardSessionInfo — default no-op ports.Agent methods + the shared session-metadata reader.
  • activitystate.StandardDeriveActivityState — the name-only hook→activity mapping shared via activitydispatch.
  • ports.NormalizePermissionMode and hookutil.FileExists — the two most-copied one-liners.

What was removed (findings from #2349)

Finding Result
1. Matcher-group hook skeleton hooksjson.Manager (claudecode, goose, qwen, droid)
2. Binary resolver boilerplate binaryutil.BinarySpec (19 resolvers)
3. normalizePermissionMode ×15 ports.NormalizePermissionMode
4. Identical DeriveActivityState ×8 activitystate.StandardDeriveActivityState; activity.go deleted
5. atomicWriteFile ×7 (4 missing fsync) hookutil.AtomicWriteFile
6. SessionInfo boilerplate agentbase.StandardSessionInfo
7-9. GetPromptDeliveryStrategy / GetConfigSpec / tier-C no-op stubs agentbase.Base embed
10. fileExists ×23 hookutil.FileExists

Correctness fix: the private atomicWriteFile copies in autohand/cline/goose/kiro omitted tmp.Sync(); routing them through hookutil.AtomicWriteFile restores the fsync before rename.

Kept deliberately separate (per #2349)

  • Per-agent permission/approval flag mapping (real behavior).
  • Special binary resolvers: codex (windows shim), opencode (bare-name fallback), aider & cursor (home-before-abs candidate order).
  • Non-matcher-group hook formats (cursor/kiro flat entries, copilot owns its file, cline per-file scripts, codex TOML flags, opencode/kilocode TS plugins, autohand global config).
  • Payload-parsing activity derivers (claudecode, codex, droid, agy, opencode).

Verification

go build ./..., go vet ./..., and go test ./... all green from backend/1,647 tests pass across 84 packages. Per-adapter public behavior (launch/restore argv, hook file contents, session info, binary resolution) is unchanged; test expectations were only migrated where they referenced now-shared internal types.

🤖 Generated with Claude Code

harshitsinghbhandari and others added 4 commits July 2, 2026 19:30
…r#2349)

Introduce the shared building blocks the per-adapter cleanup will use:
- ports.NormalizePermissionMode (finding 3)
- hookutil.FileExists (finding 10)
- binaryutil.ResolveBinary + BinarySpec (finding 2)
- activitystate.StandardDeriveActivityState (finding 4)
- agentbase.Base embed + StandardSessionInfo (findings 6-9)

No adapters wired up yet; behavior unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Wrapper#2349)

Reference conversion proving the shared packages against real tests:
- hooks.go collapses onto hooksjson.Manager (finding 1)
- ResolveGooseBinary via binaryutil.BinarySpec (finding 2)
- gooseMode uses ports.NormalizePermissionMode (finding 3)
- activity.go deleted; dispatch points at activitystate (findings 4, 5)
- SessionInfo via agentbase.StandardSessionInfo; Base embed drops the
  GetConfigSpec/GetPromptDeliveryStrategy no-ops (findings 6-8)
- fileExists/atomicWriteFile copies removed (findings 5, 10)

Also adds hooksjson package + activitystate test. goose: 327->~90 LOC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pers (AgentWrapper#2349)

Applies the shared helpers across every remaining adapter:
- hooksjson.Manager for the matcher-group cohort (claudecode, qwen, droid)
- binaryutil.BinarySpec for 19 binary resolvers (aider/cursor/opencode/codex
  keep their special resolvers; all now use hookutil.FileExists)
- ports.NormalizePermissionMode replaces 15 private copies
- agentbase.Base embed drops the GetConfigSpec/GetPromptDeliveryStrategy no-ops
  (and GetAgentHooks/GetRestoreCommand/SessionInfo no-ops on hookless adapters)
- agentbase.StandardSessionInfo replaces the per-adapter metadata readers
- activitystate.StandardDeriveActivityState via dispatch for the 8 name-only
  derivers; their activity.go files removed (claudecode/codex/droid/agy/opencode
  keep payload-parsing derivers)
- 4 private atomicWriteFile copies missing fsync now use hookutil.AtomicWriteFile
- 23 private fileExists copies removed

Full backend build + vet + test suite green (1647 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@Vaibhaav-Tiwari Vaibhaav-Tiwari left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kiro binary lookup changes on Windows:
Before this PR, Kiro looked here first:

%LOCALAPPDATA%\Programs\kiro\kiro-cli.exe

After this PR, the shared binary resolver checks this first:

%APPDATA%\npm\kiro-cli.*

So if a Windows user has both installed, AO may now launch the npm shim instead of the native Kiro install. That is a behavior change and should be fixed.

…rapper#2349)

Review feedback: the shared resolver hardcoded Windows candidate order as
APPDATA then LOCALAPPDATA, which flipped Kiro's lookup so the npm shim
(%APPDATA%\npm\kiro-cli.*) was probed before the native install
(%LOCALAPPDATA%\Programs\kiro\kiro-cli.exe). A fixed order can't preserve every
adapter's original order (goose/vibe want APPDATA first, kiro wants LOCALAPPDATA
first), so BinarySpec now takes an ordered WinPaths []WinPath list where each
entry names its base (WinAppData/WinLocalAppData/WinHome). Every adapter's list
reproduces its pre-refactor order exactly; kiro's native path is restored to
first.

go build / vet / test all green (1647 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@harshitsinghbhandari

Copy link
Copy Markdown
Collaborator Author

Good catch, thanks @Vaibhaav-Tiwari. Fixed in 9b1fc51.

Root cause: the shared resolver hardcoded Windows candidate order as %APPDATA% then %LOCALAPPDATA%, which flipped Kiro's lookup so the npm shim was probed before the native %LOCALAPPDATA%\Programs\kiro\kiro-cli.exe. A single fixed order can't preserve every adapter (goose/vibe genuinely want APPDATA first; kiro wants LOCALAPPDATA first), so BinarySpec now carries an ordered WinPaths []WinPath list where each entry names its base. Every adapter's list reproduces its pre-refactor order exactly, and Kiro's native install is restored to first. Build/vet/test green (1647 tests).

harshitsinghbhandari and others added 3 commits July 3, 2026 20:16
…gentWrapper#2349)

Embedding agentbase.Base (value receiver) lets govet's unusedwrite analyzer
prove that setting resolvedBinary in tests that only call Base-promoted methods
(GetConfigSpec/GetPromptDeliveryStrategy/SessionInfo/cancellation) is a dead
write. Drop the field from those 23 constructions; GetLaunchCommand/GetRestore
tests that actually read it keep it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inherited via the merge of main: a SessionRecord literal aligned its Metadata
field across a multi-key line, which gofmt/goimports rejects. One-line reformat
to unblock CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Cut ~3,000 LOC of redundancy in the agent adapter layer

2 participants