Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add ACP registry provider support - #2439

Closed
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry
Closed

Add ACP registry provider support#2439
juliusmarminge wants to merge 6 commits into
mainfrom
t3code/acp-registry

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a new ACP Registry provider driver backed by the ACP registry flow, including session startup and model selection handling.
  • Extends provider instance metadata to carry iconUrl through server snapshot construction and driver creation.
  • Updates the Cursor ACP adapter to support provider-specific event labeling, custom spawn behavior, model normalization, and optional Cursor model option handling.
  • Expands the web UI and shared contracts so ACP registry instances can be created, displayed, and selected consistently across settings and model pickers.

Testing

  • Not run

Note

High Risk
High risk because it introduces a new provider driver that spawns external ACP agent processes and adds server RPCs that download/extract/install binaries from remote registry URLs, expanding the attack surface and potential platform-specific failure modes.

Overview
Adds first-class ACP Registry support end-to-end: a new AcpRegistryDriver (multi-instance) built on the existing ACP/Cursor session adapter, plus contracts/settings updates to persist ACP registry configuration and defaults.

Extends provider instance/snapshot metadata to carry iconUrl through the server registry, unavailable snapshots, and UI rendering, and updates several drivers to accept/pass through iconUrl.

Generalizes makeCursorAdapter so it can be reused by non-Cursor ACP providers (custom provider kind, spawn command/args/env, model normalization, optional Cursor-specific model-option application, and customizable “ready” reason).

Adds server websocket RPCs server.listAcpRegistry and server.installAcpRegistryBinary that fetch the ACP registry index and optionally download/extract an agent binary into the server state dir, and updates the web UI wizard/model pickers to browse registry agents, install binaries, and create ACP registry-backed provider instances (including env var editing and hiding the default ACP registry catalog entry from model selection).

Reviewed by Cursor Bugbot for commit 46fc8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ACP Registry provider support with binary install and model picker integration

  • Introduces a new acpRegistry provider driver that spawns a configured command as an ACP agent, registers it as a built-in driver, and exposes it in settings with configurable command and args fields.
  • Adds two WS RPC endpoints (server.listAcpRegistry and server.installAcpRegistryBinary) so the client can browse a remote registry index, resolve per-platform launch specs, and install binary agents.
  • Extends the Add Provider Instance dialog with a Registry step: users can search, select, or install ACP agents; selecting an agent populates command, args, env vars, and icon; env vars can be marked sensitive and are validated before save.
  • Adds isModelPickerProviderInstanceEntry to exclude the default ACP Registry catalog instance from model selection while keeping explicitly imported agents visible.
  • Propagates optional iconUrl through provider snapshots, ProviderInstanceConfig, driver create inputs, and UI components (ProviderInstanceIcon, ModelListRow, ModelPickerContent, etc.) with image-load fallback to glyph or initials.
  • Risk: args fields in existing provider instance configs are now stored and parsed as string[]; any code reading args as a plain string will need updating.
📊 Macroscope summarized 121f70b. 5 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai

coderabbitaiBot commented May 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 205a440c-6ddd-4b4c-8696-8204ff5b80de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/acp-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 1, 2026
@juliusmarminge
juliusmarminge marked this pull request as draft May 1, 2026 08:39

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ACP Registry agents use incorrect Cursor-specific auth
    • Added authMethodId and clientCapabilities to CursorAdapterLiveOptions, forwarded them through makeCursorAdapter to makeCursorAcpRuntime, and set AcpRegistryDriver to use authMethodId: "none" with empty clientCapabilities instead of the Cursor-specific defaults.

Create PR

Or push these changes by commenting:

@cursor push 78fb05040a
Preview (78fb05040a)
diff --git a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts--- a/apps/server/src/provider/Drivers/AcpRegistryDriver.ts+++ b/apps/server/src/provider/Drivers/AcpRegistryDriver.ts@@ -137,6 +137,8 @@
readyReason: "ACP session ready",
applyCursorModelOptions: false,
normalizeModel: (model) => model?.trim() || "default",
+ authMethodId: "none",+ clientCapabilities: {},
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
spawn: ({ cwd, environment: spawnEnv }) => ({
command: effectiveConfig.command.trim(),
diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts--- a/apps/server/src/provider/Layers/CursorAdapter.ts+++ b/apps/server/src/provider/Layers/CursorAdapter.ts@@ -101,6 +101,8 @@
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;
+ readonly authMethodId?: string;+ readonly clientCapabilities?: EffectAcpSchema.InitializeRequest["clientCapabilities"];
/**
* Selections are honored when `modelSelection.instanceId` matches this value.
* Defaults to the legacy built-in instance id (`cursor`).
@@ -546,6 +548,10 @@
cwd,
...(resumeSessionId ? { resumeSessionId } : {}),
clientInfo: { name: "t3-code", version: "0.0.0" },
+ ...(options?.authMethodId ? { authMethodId: options.authMethodId } : {}),+ ...(options?.clientCapabilities+ ? { clientCapabilities: options.clientCapabilities }+ : {}),
...acpNativeLoggers,
}).pipe(
Effect.provideService(Scope.Scope, sessionScope),

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

readonly env?: NodeJS.ProcessEnv;
};
readonly normalizeModel?: (model: string | null | undefined) => string;
readonly applyCursorModelOptions?: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACP Registry agents use incorrect Cursor-specific auth

Medium Severity

The CursorAdapterLiveOptions interface was extended with provider, spawn, normalizeModel, and applyCursorModelOptions but not with authMethodId or clientCapabilities. The AcpRegistryDriver calls makeCursorAdapter which internally calls makeCursorAcpRuntime, where these default to "cursor_login" and CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES. Generic ACP registry agents don't support Cursor login, so session initialization will use incorrect auth and capability negotiation. The CursorAcpRuntimeInput was extended with optional authMethodId and clientCapabilities in this same PR, but the adapter layer doesn't forward them.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 46fc8ff. Configure here.

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a significant new feature (ACP Registry provider support) with binary download/installation capabilities and extensive new UI workflows. There is also an unresolved medium-severity bug regarding incorrect auth configuration for ACP agents. The scope and nature of these changes warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Comment threadapps/server/src/provider/Drivers/AcpRegistryDriver.ts Outdated
interface PendingApproval {
readonly decision: Deferred.Deferred<ProviderApprovalDecision>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 LowLayers/GenericAcpAdapter.ts:80

respondToUserInput reads from ctx.pendingUserInputs (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to respondToUserInput returns "Unknown pending user-input request". Unlike pendingApprovals which is populated in the handleRequestPermission callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates pendingUserInputs needs to be added.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around line 80:
`respondToUserInput` reads from `ctx.pendingUserInputs` (line 666), but nothing in the adapter ever writes entries to this map. The map is created at line 241 but remains empty, so every call to `respondToUserInput` returns "Unknown pending user-input request". Unlike `pendingApprovals` which is populated in the `handleRequestPermission` callback, the corresponding population logic for user inputs is missing. If this adapter is intended to support user input requests, the handler that populates `pendingUserInputs` needs to be added.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts line 241 (map creation), line 397 (stored in ctx), line 666 (read via .get()). git_grep for `pendingUserInputs.set(` returns no results. git_grep for `pendingApprovals.set(` returns line 304, confirming the parallel pattern exists for approvals but is missing for user inputs. All references to pendingUserInputs: lines 92, 120, 123, 191, 241, 397, 631, 666 — none are writes.

}
}).pipe(Effect.scoped);

const extractArchive = (archivePath: string, destinationDir: string) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Lowacp/AcpRegistryBinaryInstaller.ts:220

On Windows, extractArchive calls PowerShell with -LiteralPath and -DestinationPath as separate arguments, but -Command joins all arguments with spaces into a single command string. If archivePath or destinationDir contain spaces, the paths are split incorrectly and Expand-Archive fails. For example, a path like C:\Users\John Smith\Temp\file.zip becomes two tokens (C:\Users\John and Smith\Temp\file.zip). Consider passing the entire command as a single quoted string to -Command, or using proper escaping.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around line 220:
On Windows, `extractArchive` calls PowerShell with `-LiteralPath` and `-DestinationPath` as separate arguments, but `-Command` joins all arguments with spaces into a single command string. If `archivePath` or `destinationDir` contain spaces, the paths are split incorrectly and `Expand-Archive` fails. For example, a path like `C:\Users\John Smith\Temp\file.zip` becomes two tokens (`C:\Users\John` and `Smith\Temp\file.zip`). Consider passing the entire command as a single quoted string to `-Command`, or using proper escaping.
Evidence trail:
- `apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts` lines 225-233 (REVIEWED_COMMIT): PowerShell invocation passes archivePath and destinationDir as separate args after `-Command`
- `https://github.com/PowerShell/PowerShell` `src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs` line 1552: `_commandLineCommand = string.Join(' ', args, i, args.Length - i);` — confirms args after `-Command` are joined with spaces from the already-parsed (quotes-stripped) argv
- Same file lines 1041-1048: `_commandHasArgs` is only true for `-commandwithargs`/`-cwa`, not for `-command`/`-c`
- Same file line 1629: `private bool _commandHasArgs;` — defaults to false

Comment on lines +186 to +204
const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
yield* settlePendingUserInputsAsEmptyAnswers(ctx.pendingUserInputs);
if (ctx.notificationFiber) {
yield* Fiber.interrupt(ctx.notificationFiber);
}
yield* Effect.ignore(Scope.close(ctx.scope, Exit.void));
sessions.delete(ctx.threadId);
yield* offerRuntimeEvent({
type: "session.exited",
...(yield* makeEventStamp()),
provider: providerKind,
threadId: ctx.threadId,
payload: { exitKind: "graceful" },
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 MediumLayers/GenericAcpAdapter.ts:186

threadLocksRef leaks memory: getThreadSemaphore creates and stores a Semaphore for each unique threadId, but stopSessionInternal only removes the session from sessions without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like Effect.addFinalizer in withThreadLock.

 const stopSessionInternal = (ctx: GenericAcpSessionContext) =>
Effect.gen(function* () {
if (ctx.stopped) return;
ctx.stopped = true;
+ yield* SynchronizedRef.update(threadLocksRef, (map) => {+ const next = new Map(map);+ next.delete(ctx.threadId);+ return next;+ });
yield* settlePendingApprovalsAsCancelled(ctx.pendingApprovals);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GenericAcpAdapter.ts around lines 186-204:
`threadLocksRef` leaks memory: `getThreadSemaphore` creates and stores a `Semaphore` for each unique `threadId`, but `stopSessionInternal` only removes the session from `sessions` without removing the corresponding semaphore entry. Over time the map grows unboundedly with every stopped session. Consider deleting the semaphore entry when stopping a session, or using a cleanup mechanism like `Effect.addFinalizer` in `withThreadLock`.
Evidence trail:
apps/server/src/provider/Layers/GenericAcpAdapter.ts lines 148-149 (sessions map and threadLocksRef creation), lines 158-168 (getThreadSemaphore adding entries), lines 186-203 (stopSessionInternal deleting from sessions but not threadLocksRef). git_grep for 'threadLocksRef' confirms no removal of entries anywhere in the codebase.

Comment on lines +177 to +181
const json = yield* Effect.try({
try: () => JSON.parse(raw.value) as unknown,
catch: () => null,
});
if (json === null) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumacp/AcpRegistryBinaryInstaller.ts:177

When JSON.parse throws on invalid JSON, Effect.try maps the exception to null as an error value, not a success value. The yield* then short-circuits the generator with failure rather than assigning null to json. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire listAcpRegistryAgents call. Consider using Effect.option (like on line 182-184) or Effect.either to catch and handle the parse error locally.

- const json = yield* Effect.try({- try: () => JSON.parse(raw.value) as unknown,- catch: () => null,- });- if (json === null) return null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts around lines 177-181:
When `JSON.parse` throws on invalid JSON, `Effect.try` maps the exception to `null` as an *error* value, not a success value. The `yield*` then short-circuits the generator with failure rather than assigning `null` to `json`. This contradicts the intended behavior of treating invalid manifest files as "no manifest" — instead, parse errors propagate and can fail the entire `listAcpRegistryAgents` call. Consider using `Effect.option` (like on line 182-184) or `Effect.either` to catch and handle the parse error locally.
Evidence trail:
File: apps/server/src/provider/acp/AcpRegistryBinaryInstaller.ts lines 165-200 (viewed at REVIEWED_COMMIT). Effect.try API behavior confirmed via Effect-TS community patterns on GitHub (e.g., https://github.com/jpb06/effect-errors, https://github.com/Effect-TS/effect/issues/3563) showing catch callback returns the error channel value, not the success channel value.

Julius Marminge added 6 commits May 4, 2026 01:18
- Introduce ACP Registry driver and client scaffolding
- Thread provider icon URLs through server and UI models
- Expand Cursor ACP adapter hooks for alternate providers
- Route ACP registry sessions through a generic ACP runtime
- Make ACP auth optional and skip Cursor-only extensions for generic providers
- Route ACP registry through the shared generic adapter
- Simplify cursor adapter session setup and model selection
- Preserve cursor-specific extension handling in the cursor layer
- Move ACP binary install and launch resolution into a shared module
- Switch stream exposure to a getter so runtime events stay fresh
- Keep WebSocket RPC handlers thin and reuse registry install helpers
- Convert ACP registry binary path helpers to Effect-based access
- Reuse shared path resolution for install previews and installs
- Keep archive command validation and manifest lookup behavior intact
- Normalize search tokens and score field matches
- Sort dialog results by best query match instead of substring order
@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
t3code-appReadyReadyPreview, CommentMay 4, 2026 8:23am

Request Review

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

will be part of #2829

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge