Skip to content

feat(desktop/windows): coding-agent ACP adapter core (PR 1/4)#9304

Open
karthikyeluripati wants to merge 5 commits into
BasedHardware:mainfrom
karthikyeluripati:feat/win-agents-1-adapter-core
Open

feat(desktop/windows): coding-agent ACP adapter core (PR 1/4)#9304
karthikyeluripati wants to merge 5 commits into
BasedHardware:mainfrom
karthikyeluripati:feat/win-agents-1-adapter-core

Conversation

@karthikyeluripati

@karthikyeluripati karthikyeluripati commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Part 1 of a stacked train (see issue #9302):

Self-contained ACP client under src/main/codingAgent/ ported from desktop/macos/agent/src/adapters — nothing wired into chat/IPC/UI yet. Windows-specific: env allowlist (ComSpec/SystemRoot/PATHEXT), taskkill /t /f tree-kill (no POSIX process groups), windowsHide, ELECTRON_RUN_AS_NODE for the bundled Claude Code bridge.

Verification: pnpm run typecheck + lint clean; pnpm test 577 passed, incl. a real-subprocess integration test (fake ACP peer fixture). Also verified live: the real bridge created a file through this adapter on a Windows 11 machine.

Review in cubic

…enClaw, Hermes, Codex)

PR 1/4 of the Windows coding-agents train: a self-contained ACP (Agent
Client Protocol) JSON-RPC-over-stdio adapter layer under
src/main/codingAgent/, ported from the macOS agent runtime
(desktop/macos/agent/src/adapters). Nothing is wired into chat, IPC, or
UI yet — that lands in the next PRs of the stack.

- interface.ts: RuntimeAdapter contract, capability matrix for the four
  Windows adapters, binding/attempt types, id-conflation contract guards.
- acp.ts: the generic ACP client (JSON-RPC framing, session lifecycle,
  streaming session/update translation, no-progress watchdog, permission
  auto-resolution). Windows adaptations vs macOS: Windows env allowlist
  (ComSpec/SystemRoot/PATHEXT are required for shell:true spawns and npm
  .cmd shims), taskkill /t /f tree-kill in stop() (POSIX process-group
  kill does not exist on Windows and a plain kill would orphan the real
  agent behind cmd.exe), windowsHide, and ELECTRON_RUN_AS_NODE for the
  bundled default adapter.
- claudeCode.ts: default always-available adapter — spawns the bundled
  patched-acp-entry.mjs (wrapping @zed-industries/claude-agent-acp, a
  pure cross-platform npm package) via a ?asset import; no separately
  installed CLI required.
- openclaw.ts / hermes.ts / codex.ts: external-command adapters
  activated by OMI_{OPENCLAW,HERMES,CODEX}_ADAPTER_COMMAND or (later)
  settings-stored commands, with per-adapter env passthrough
  (HERMES_HOME; OPENAI_API_KEY/CODEX_* for the Codex ACP bridge).
- adapterRegistry.ts: static profiles + activation checks with
  preference-over-env command resolution for the future settings UI.
- toolPolicyStub.ts: minimal permission policy — auto-approve for the
  trusted first-party Claude Code bridge, never a permanent grant for
  untrusted external adapters (allow_once or ACP -32001).
- electron-builder.yml: asarUnpack for the entry script and the packages
  it imports (a spawned Node child cannot read from the asar archive).
- vitest.config.ts: resolve electron-vite `?asset` imports to the real
  source path so main-process modules stay testable.

Verification: cd desktop/windows && pnpm run typecheck (clean),
pnpm run lint (clean), pnpm test — 577 passed / 3 skipped, including
the new mocked-subprocess suites (spawn shapes, env-allowlist secret
non-leakage, taskkill tree-kill, watchdog cancellation, stderr
redaction, permission policy) and acp.integration.test.ts, which spawns
a real Node fake-ACP peer (fixtures/fakeAcpSubprocess.mjs) and runs the
full open → prompt → stream → result → stop lifecycle unmocked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 23 files

Confidence score: 5/5

  • Safe to merge after the addressed issues were fixed.

You’re at about 94% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread desktop/windows/src/main/codingAgent/failures.ts Outdated
Comment thread desktop/windows/src/main/codingAgent/acp.ts
Comment thread desktop/windows/src/main/codingAgent/acp.integration.test.ts Outdated
Comment thread desktop/windows/src/main/codingAgent/patched-acp-entry.mjs Outdated
Comment thread desktop/windows/src/main/codingAgent/claudeCode.test.ts
Comment thread desktop/windows/src/main/codingAgent/acp.ts Outdated
Comment thread desktop/windows/src/main/codingAgent/claudeCode.test.ts Outdated
Comment thread desktop/windows/src/main/codingAgent/externalAdapters.test.ts Outdated
Comment thread desktop/windows/electron-builder.yml Outdated
Comment thread desktop/windows/src/main/codingAgent/acp.test.ts
…n the adapter core

Fixes 11 of the 12 automated-review findings (the asarUnpack one is
narrowed rather than removed — electron-builder flattens resolved deps
into the package, so the transitive entries are real; the scope-wide
@agentclientprotocol/** glob is now the concrete sdk package):

- failures.ts: broaden stderr redaction (token/secret/password/credential
  fields, GitHub ghp_/github_pat_, Slack xox*) and keep the TAIL of long
  diagnostics so final error lines (and the OpenClaw config sniff strings)
  survive truncation. New failures.test.ts pins all of it.
- acp.ts: gate session/update by the attempt's adapter session id so one
  process hosting several sessions can never stream into the wrong
  attempt (updates without a session id still accepted); make the
  no-watchdog path abort-aware so a cancelled attempt settles even when
  the adapter never answers session/cancel; strip user:pass@ userinfo
  from proxy values that URL() can't expose an authority for (e.g.
  "alice:pass@proxy:3128", where "alice:" parses as a scheme).
- patched-acp-entry.mjs: dynamic-import the bridge AFTER the console→
  stderr redirect (a static import is hoisted and could pollute stdout).
  Re-verified live against the real bridge: initialize handshake
  completes and stdout stays pure JSON-RPC.
- electron-builder.yml: narrow asarUnpack to the concrete runtime deps.
- tests: assert CLAUDE_CODE_USE_VERTEX deletion and windowsHide on the
  default spawn; assert adapterSessionId in the integration test; real
  session/new-level assertion that OpenClaw strips MCP servers and skips
  set_model; cross-session isolation and no-watchdog cancellation tests;
  watchdog test context uses the adapter id under test.

Verification: pnpm run typecheck (clean), pnpm run lint (clean),
pnpm test — 583 passed / 3 skipped; live smoke of the real bridge entry
(initialize handshake, no LLM call) passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 9 files (changes from recent commits).

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread desktop/windows/src/main/codingAgent/acp.ts Outdated
…rt early return

Follow-up to the PR BasedHardware#9304 review: in the no-watchdog cancellation path,
an already-aborted attempt returned before attaching handlers to the
underlying session/prompt promise, so a later rejection (e.g. process
exit) would surface as an unhandled rejection. Attach the handlers
first — finish() guards double-settling — then take the early-abort
exit. Regression test covers the pre-aborted + late-rejection sequence.

Verification: pnpm run typecheck (clean), pnpm test — 584 passed / 3
skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks for the careful Windows adapter-core port here. The shape is coherent for PR 1/4: it keeps the ACP runtime isolated under desktop/windows/src/main/codingAgent/, includes a real-subprocess fake ACP integration test, and pays attention to secret handling for external adapter subprocess environments.

I’m not approving this automatically because it is a stacked feature PR with dependency/runtime-agent changes and a large new adapter surface; it needs human maintainer review before merge.

A couple of maintainer-review items I’d like checked before this lands:

  • The new bundled Claude bridge dependency is @zed-industries/claude-agent-acp@0.18.0, which npm marks as deprecated/renamed to @agentclientprotocol/claude-agent-acp (current package is much newer). Since patched-acp-entry.mjs imports an internal dist/acp-agent.js path, this is a fragile dependency boundary; please confirm whether the renamed package can be used now, or document why the deprecated package/version is intentionally pinned.
  • The permission stub intentionally auto-selects the widest allow_always grant for the built-in Claude Code adapter while constraining external adapters. That may be reasonable for a headless first-party bridge, but it is a product/security policy decision for Windows agent behavior and should be explicitly accepted by a maintainer before wiring the later PRs into chat/IPC/UI.
  • CI is green and the test coverage looks good for the core transport/contracts. For merge confidence, I’d still want a maintainer to verify the packaged Windows app path with the renamed/deprecated dependency decision above, because the runtime depends on ?asset emission plus asarUnpack for the bridge entry and transitive packages.

No urgent security issue found in the static review; this is mainly dependency/runtime-agent policy review rather than a request to split the PR.

@Git-on-my-level Git-on-my-level added needs-maintainer-review Needs human maintainer review before merge dependency-review Touches dependencies or lockfiles; needs dependency review feature-fit-review Needs review of product/feature direction with Omi mission/vision labels Jul 9, 2026
karthikyeluripati and others added 2 commits July 9, 2026 13:18
…nt-acp public API

Addresses the maintainer dependency review on PR BasedHardware#9304: the bundled
Claude bridge was @zed-industries/claude-agent-acp@0.18.0 (deprecated,
renamed upstream) imported via an internal dist/acp-agent.js path, with
monkey-patches on package internals (ClaudeAcpAgent.prototype, the
sessions map, query.next) to capture cost/usage.

All three fragile boundaries are gone:
- Dependency is now @agentclientprotocol/claude-agent-acp@^0.58.1 (the
  maintained rename; the old name has no post-rename releases).
- The entry (renamed patched-acp-entry.mjs → claude-acp-entry.mjs)
  imports runAcp from the package's PUBLIC export and contains no
  patches at all — just the console→stderr redirect and runAcp().
- Cost is captured from the bridge's standard usage_update session
  notifications (documented ACP surface): acp.ts tracks the cumulative
  session cost per adapter session and reports the per-attempt delta;
  a per-turn _meta.costUsd on the prompt response still wins when no
  usage_update was seen (other ACP adapters).

electron-builder asarUnpack updated to the renamed package plus its
actual transitive runtime imports (@agentclientprotocol/sdk,
@anthropic-ai/claude-agent-sdk + its win32-x64 native package).
pnpm-workspace.yaml records the supply-chain exclusions pnpm required
for these fresh versions.

Verification: pnpm run typecheck (clean), pnpm run lint (clean),
pnpm test — 585 passed (new cumulative-cost-delta unit test; fixture +
integration test now exercise the usage_update path and its precedence
over _meta). Live against the real renamed bridge on Windows 11:
initialize handshake with pure JSON-RPC stdout, and a full end-to-end
task (real file created, cost $0.24 captured via usage_update).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified the maintainer-flagged packaged path by spawning the bridge
entry from a real electron-builder build (dist/win-unpacked): the
?asset entry and the unpacked packages resolved, but the handshake
failed with ERR_MODULE_NOT_FOUND — @agentclientprotocol/sdk imports
zod at runtime and zod sat inside the asar, unreadable by the plain
Node child. zod is the only remaining runtime import of the closure
(claude-agent-acp → sdk + claude-agent-sdk + zod; the sdk declares no
deps of its own and the agent-sdk only optional platform binaries).

Verification: pnpm run build:unpack, then an ACP initialize handshake
against app.asar.unpacked/out/main/chunks/claude-acp-entry-*.mjs —
completes with pure JSON-RPC stdout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@karthikyeluripati

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review @Git-on-my-level — all three items addressed:

  1. Dependency: Migrated to @agentclientprotocol/claude-agent-acp@^0.58.1 (9f13e61). The internal dist/acp-agent.js import is gone (public export only), and so are the monkey-patches entirely: 0.58.x emits cost via standard usage_update session notifications, so the adapter reads cost from the documented ACP surface (cumulative→per-attempt delta, unit-tested) and the entry script is now just a console→stderr redirect + runAcp(). Verified live on Windows 11: handshake with pure JSON-RPC stdout, plus a full end-to-end task through the new bridge (real file written, $0.24 captured via usage_update).

  2. Permission policy: Agreed this needs explicit maintainer sign-off before the later PRs wire this into chat. Rationale for the current stub: the built-in bridge is first-party and the headless core has no UI to surface a prompt, so auto-approve keeps a voice-triggered task from stalling invisibly; external adapters never receive a permanent grant (allow_once or ACP -32001). If you'd rather Claude Code also be capped at allow_once (or gated behind a setting) until the UI PRs land, that's a two-line change in toolPolicyStub.ts — happy to make it, your call.

  3. Packaged path: Verified with a real electron-builder build — and it caught a gap: @agentclientprotocol/sdk imports zod at runtime, which sat inside the asar. Fixed by unpacking it (2cae69d); the enumerated runtime closure is now claude-agent-acp → sdk + claude-agent-sdk (+ win32-x64 native) + zod, and the ACP handshake passes when spawning the entry from app.asar.unpacked in the packaged layout.

@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks for the follow-up and for addressing the earlier dependency concern. I re-reviewed the current head (2cae69d62ac9bccd222728e16d6482c17c9d2f03). The update is materially better: the bridge now uses @agentclientprotocol/claude-agent-acp@0.58.1, the entry point sticks to the package's public runAcp() export, the fragile internal import/monkey-patching is gone, and the tests cover the cumulative usage_update cost path plus subprocess/env handling.

I’m still not formally approving because this remains PR 1/4 of a stacked Windows coding-agent runtime feature, touches dependency/runtime packaging, and includes product/security-policy behavior around automatic ACP permission resolution. The existing needs-maintainer-review, dependency-review, and feature-fit-review labels still look appropriate.

Maintainer review focus before merge:

  • Please explicitly accept the Windows policy choice that the bundled Claude Code adapter auto-selects allow_always while external adapters are constrained to non-permanent grants/denial. That may be right for a headless first-party bridge, but it changes agent tool-permission behavior once later PRs wire this into chat/IPC/UI.
  • Dependency review should be deliberate: @agentclientprotocol/claude-agent-acp@0.58.1 is not deprecated and the lockfile integrity matches npm metadata, but it was published recently and this PR adds minimumReleaseAgeExclude entries for the ACP/Anthropic SDK packages. That bypass should be a maintainer-accepted exception, not an accidental supply-chain policy relaxation.
  • The packaged Windows runtime path still deserves human verification on a clean Windows build because correctness depends on ?asset emission plus asarUnpack for the bridge and transitive runtime packages.

No urgent security issue found in this static review, and I’m not asking for a split: the PR is cohesive as an adapter-core slice. This is a positive implementation signal, with human product/security/dependency sign-off still required before merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependency-review Touches dependencies or lockfiles; needs dependency review feature-fit-review Needs review of product/feature direction with Omi mission/vision needs-maintainer-review Needs human maintainer review before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants