From e25767c6f8984cea3b214a671ade996fdca692f8 Mon Sep 17 00:00:00 2001 From: Tingri Lohar Mohammed Mohsin <96524936+Mohsintl@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:59:21 +0530 Subject: [PATCH 1/3] fix(provider-acp-registry): append agent error message on sign-in failure (#30218) When an ACP agent's authenticate or session initialization fails, failure() previously used a static generic string for detail, discarding the specific errorMessage attached to cause. Append the AcpRequestError errorMessage to detail when present so the provider card displays the actionable reason to the user. Fixes pingdotgg/t3code#30218 --- .../src/server/auth.test.ts | 32 ++++++++++++++++++- .../provider-acp-registry/src/server/auth.ts | 22 +++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/provider-acp-registry/src/server/auth.test.ts b/packages/provider-acp-registry/src/server/auth.test.ts index b81e2e3fba73e..0fc2d6b45b3f0 100644 --- a/packages/provider-acp-registry/src/server/auth.test.ts +++ b/packages/provider-acp-registry/src/server/auth.test.ts @@ -61,6 +61,7 @@ const makeHarness = ( failVerification = false, settings = decodeSettings({ agentId: "test-agent" }), providerInstanceId = instanceId, + failAuthentication?: string, ) => Effect.gen(function* () { const verify = yield* Deferred.make(); @@ -110,6 +111,12 @@ const makeHarness = ( }), authenticate: (methodId) => Effect.gen(function* () { + if (failAuthentication) + return yield* new AcpRequestError({ + method: "authenticate", + code: -32000, + errorMessage: failAuthentication, + }); authenticated.push(methodId); if (elicitation) yield* elicitation( @@ -342,8 +349,31 @@ it.effect("does not report authentication when post-login verification fails", ( h.exit(0); yield* h.phase("verifying"); yield* Deferred.succeed(h.verify, undefined); - yield* h.phase("failed"); + const failed = yield* h.phase("failed"); + assert.strictEqual( + failed.message, + "The provider could not create a session after terminal sign-in. Authentication required", + ); assert.deepEqual(h.changed, []); assert.isTrue(h.killed()); }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), ); + +it.effect("includes the agent error message when authenticate fails", () => + Effect.gen(function* () { + const h = yield* makeHarness( + browserMethod, + false, + undefined, + undefined, + "Connect to the VPN, then sign in again.", + ); + yield* h.controller.start("owner", Effect.void, "browser"); + const failed = yield* h.phase("failed"); + assert.strictEqual( + failed.message, + "The ACP agent could not complete sign-in. Connect to the VPN, then sign in again.", + ); + assert.deepEqual(h.changed, []); + }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), +); diff --git a/packages/provider-acp-registry/src/server/auth.ts b/packages/provider-acp-registry/src/server/auth.ts index 240bca67cea57..891ceb00cf486 100644 --- a/packages/provider-acp-registry/src/server/auth.ts +++ b/packages/provider-acp-registry/src/server/auth.ts @@ -12,7 +12,9 @@ import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Queue from "effect/Queue"; import * as Scope from "effect/Scope"; +import * as Schema from "effect/Schema"; import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; +import * as EffectAcpErrors from "effect-acp/errors"; import type * as AcpSchema from "effect-acp/compat"; import * as PtyAdapter from "@t3tools/shared/PtyAdapter"; @@ -27,6 +29,8 @@ type Runtime = Pick< "initialize" | "authenticate" | "start" | "handleElicitation" | "logout" >; +const isAcpRequestError = Schema.is(EffectAcpErrors.AcpRequestError); + export const makeAcpRegistryAuth = Effect.fn("makeAcpRegistryAuth")(function* (options: { readonly instanceId: ProviderInstanceId; readonly settings: AcpRegistrySettings; @@ -46,8 +50,22 @@ export const makeAcpRegistryAuth = Effect.fn("makeAcpRegistryAuth")(function* (o ); const agentKey = options.settings.source === "local" ? `local:${options.instanceId}` : options.settings.agentId; - const failure = (operation: string, detail: string, cause?: unknown) => - new ProviderSetupError({ instanceId: options.instanceId, operation, detail, cause }); + const failure = (operation: string, detail: string, cause?: unknown) => { + let resolvedDetail = detail; + if (isAcpRequestError(cause)) { + const message = cause.errorMessage.trim(); + if (message.length > 0) { + const prefix = detail.endsWith(".") ? detail : `${detail}.`; + resolvedDetail = `${prefix} ${message}`; + } + } + return new ProviderSetupError({ + instanceId: options.instanceId, + operation, + detail: resolvedDetail, + cause, + }); + }; const resolve = catalog .resolve(options.settings, options.cwd, options.environment) .pipe( From 85aaac1ae241de7283f8ebd2885dacbad9488566 Mon Sep 17 00:00:00 2001 From: Tingri Lohar Mohammed Mohsin <96524936+Mohsintl@users.noreply.github.com> Date: Wed, 4 Nov 2026 01:25:20 +0530 Subject: [PATCH 2/3] fix(provider-acp-registry): retain error in cause with bounded safe detail Ensure AcpRequestError is never interpolated into ProviderSetupError detail, maintaining a bounded, safe user-facing message while preserving the original error in cause. --- .../src/server/auth.test.ts | 35 +++++++++---------- .../provider-acp-registry/src/server/auth.ts | 22 ++---------- 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/packages/provider-acp-registry/src/server/auth.test.ts b/packages/provider-acp-registry/src/server/auth.test.ts index 0fc2d6b45b3f0..5da5982525155 100644 --- a/packages/provider-acp-registry/src/server/auth.test.ts +++ b/packages/provider-acp-registry/src/server/auth.test.ts @@ -352,28 +352,27 @@ it.effect("does not report authentication when post-login verification fails", ( const failed = yield* h.phase("failed"); assert.strictEqual( failed.message, - "The provider could not create a session after terminal sign-in. Authentication required", + "The provider could not create a session after terminal sign-in.", ); assert.deepEqual(h.changed, []); assert.isTrue(h.killed()); }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), ); -it.effect("includes the agent error message when authenticate fails", () => - Effect.gen(function* () { - const h = yield* makeHarness( - browserMethod, - false, - undefined, - undefined, - "Connect to the VPN, then sign in again.", - ); - yield* h.controller.start("owner", Effect.void, "browser"); - const failed = yield* h.phase("failed"); - assert.strictEqual( - failed.message, - "The ACP agent could not complete sign-in. Connect to the VPN, then sign in again.", - ); - assert.deepEqual(h.changed, []); - }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), +it.effect( + "retains agent error in cause without interpolating into detail when authenticate fails", + () => + Effect.gen(function* () { + const h = yield* makeHarness( + browserMethod, + false, + undefined, + undefined, + "Connect to the VPN, then sign in again.", + ); + yield* h.controller.start("owner", Effect.void, "browser"); + const failed = yield* h.phase("failed"); + assert.strictEqual(failed.message, "The ACP agent could not complete sign-in."); + assert.deepEqual(h.changed, []); + }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), ); diff --git a/packages/provider-acp-registry/src/server/auth.ts b/packages/provider-acp-registry/src/server/auth.ts index 891ceb00cf486..240bca67cea57 100644 --- a/packages/provider-acp-registry/src/server/auth.ts +++ b/packages/provider-acp-registry/src/server/auth.ts @@ -12,9 +12,7 @@ import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Queue from "effect/Queue"; import * as Scope from "effect/Scope"; -import * as Schema from "effect/Schema"; import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; -import * as EffectAcpErrors from "effect-acp/errors"; import type * as AcpSchema from "effect-acp/compat"; import * as PtyAdapter from "@t3tools/shared/PtyAdapter"; @@ -29,8 +27,6 @@ type Runtime = Pick< "initialize" | "authenticate" | "start" | "handleElicitation" | "logout" >; -const isAcpRequestError = Schema.is(EffectAcpErrors.AcpRequestError); - export const makeAcpRegistryAuth = Effect.fn("makeAcpRegistryAuth")(function* (options: { readonly instanceId: ProviderInstanceId; readonly settings: AcpRegistrySettings; @@ -50,22 +46,8 @@ export const makeAcpRegistryAuth = Effect.fn("makeAcpRegistryAuth")(function* (o ); const agentKey = options.settings.source === "local" ? `local:${options.instanceId}` : options.settings.agentId; - const failure = (operation: string, detail: string, cause?: unknown) => { - let resolvedDetail = detail; - if (isAcpRequestError(cause)) { - const message = cause.errorMessage.trim(); - if (message.length > 0) { - const prefix = detail.endsWith(".") ? detail : `${detail}.`; - resolvedDetail = `${prefix} ${message}`; - } - } - return new ProviderSetupError({ - instanceId: options.instanceId, - operation, - detail: resolvedDetail, - cause, - }); - }; + const failure = (operation: string, detail: string, cause?: unknown) => + new ProviderSetupError({ instanceId: options.instanceId, operation, detail, cause }); const resolve = catalog .resolve(options.settings, options.cwd, options.environment) .pipe( From 8f8c493fbdd1f8bc1a8e1f1d5404ad24d9cc9b5e Mon Sep 17 00:00:00 2001 From: Tingri Lohar Mohammed Mohsin <96524936+Mohsintl@users.noreply.github.com> Date: Wed, 4 Nov 2026 01:49:24 +0530 Subject: [PATCH 3/3] fix(provider-acp-registry): append bounded agent error message on sign-in failure (#30218) --- .../src/server/auth.test.ts | 35 ++++++++++--------- .../provider-acp-registry/src/server/auth.ts | 23 ++++++++++-- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/provider-acp-registry/src/server/auth.test.ts b/packages/provider-acp-registry/src/server/auth.test.ts index 5da5982525155..0fc2d6b45b3f0 100644 --- a/packages/provider-acp-registry/src/server/auth.test.ts +++ b/packages/provider-acp-registry/src/server/auth.test.ts @@ -352,27 +352,28 @@ it.effect("does not report authentication when post-login verification fails", ( const failed = yield* h.phase("failed"); assert.strictEqual( failed.message, - "The provider could not create a session after terminal sign-in.", + "The provider could not create a session after terminal sign-in. Authentication required", ); assert.deepEqual(h.changed, []); assert.isTrue(h.killed()); }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), ); -it.effect( - "retains agent error in cause without interpolating into detail when authenticate fails", - () => - Effect.gen(function* () { - const h = yield* makeHarness( - browserMethod, - false, - undefined, - undefined, - "Connect to the VPN, then sign in again.", - ); - yield* h.controller.start("owner", Effect.void, "browser"); - const failed = yield* h.phase("failed"); - assert.strictEqual(failed.message, "The ACP agent could not complete sign-in."); - assert.deepEqual(h.changed, []); - }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), +it.effect("includes the agent error message when authenticate fails", () => + Effect.gen(function* () { + const h = yield* makeHarness( + browserMethod, + false, + undefined, + undefined, + "Connect to the VPN, then sign in again.", + ); + yield* h.controller.start("owner", Effect.void, "browser"); + const failed = yield* h.phase("failed"); + assert.strictEqual( + failed.message, + "The ACP agent could not complete sign-in. Connect to the VPN, then sign in again.", + ); + assert.deepEqual(h.changed, []); + }).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))), ); diff --git a/packages/provider-acp-registry/src/server/auth.ts b/packages/provider-acp-registry/src/server/auth.ts index 240bca67cea57..b6c0a3d3e2376 100644 --- a/packages/provider-acp-registry/src/server/auth.ts +++ b/packages/provider-acp-registry/src/server/auth.ts @@ -12,7 +12,9 @@ import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Queue from "effect/Queue"; import * as Scope from "effect/Scope"; +import * as Schema from "effect/Schema"; import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; +import * as EffectAcpErrors from "effect-acp/errors"; import type * as AcpSchema from "effect-acp/compat"; import * as PtyAdapter from "@t3tools/shared/PtyAdapter"; @@ -27,6 +29,9 @@ type Runtime = Pick< "initialize" | "authenticate" | "start" | "handleElicitation" | "logout" >; +const MAX_ERROR_MESSAGE_LENGTH = 1_024; +const isAcpRequestError = Schema.is(EffectAcpErrors.AcpRequestError); + export const makeAcpRegistryAuth = Effect.fn("makeAcpRegistryAuth")(function* (options: { readonly instanceId: ProviderInstanceId; readonly settings: AcpRegistrySettings; @@ -46,8 +51,22 @@ export const makeAcpRegistryAuth = Effect.fn("makeAcpRegistryAuth")(function* (o ); const agentKey = options.settings.source === "local" ? `local:${options.instanceId}` : options.settings.agentId; - const failure = (operation: string, detail: string, cause?: unknown) => - new ProviderSetupError({ instanceId: options.instanceId, operation, detail, cause }); + const failure = (operation: string, detail: string, cause?: unknown) => { + let resolvedDetail = detail; + if (isAcpRequestError(cause)) { + const message = cause.errorMessage.trim().slice(0, MAX_ERROR_MESSAGE_LENGTH); + if (message.length > 0) { + const prefix = detail.endsWith(".") ? detail : `${detail}.`; + resolvedDetail = `${prefix} ${message}`; + } + } + return new ProviderSetupError({ + instanceId: options.instanceId, + operation, + detail: resolvedDetail, + cause, + }); + }; const resolve = catalog .resolve(options.settings, options.cwd, options.environment) .pipe(