From 0a5566f3e6e28bf5c9d23ca85c0f5a8e95737655 Mon Sep 17 00:00:00 2001 From: JuliRossi Date: Wed, 10 Jun 2026 14:49:43 -0300 Subject: [PATCH 1/6] fix(drive-integration): use environment alias for agents API calls sdk.ids.environment returns the real environment ID (e.g. master-2026-06-09) instead of the alias (master), causing the agents API path to not match aliased environments. Fall back to raw ID when no alias is set. Co-Authored-By: Claude Sonnet 4.6 --- apps/drive-integration/src/hooks/useWorkflowAgent.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/drive-integration/src/hooks/useWorkflowAgent.ts b/apps/drive-integration/src/hooks/useWorkflowAgent.ts index bdab38792a..2273e5216a 100644 --- a/apps/drive-integration/src/hooks/useWorkflowAgent.ts +++ b/apps/drive-integration/src/hooks/useWorkflowAgent.ts @@ -265,7 +265,7 @@ export const useWorkflowAgent = ({ setIsAnalyzing(true); const spaceId = sdk.ids.space; - const environmentId = sdk.ids.environment; + const environmentId = sdk.ids.environmentAlias ?? sdk.ids.environment; const threadId = [crypto.randomUUID(), WORKFLOW_AGENT_ID].join('-'); const payload: AgentGeneratePayload = { @@ -308,7 +308,7 @@ export const useWorkflowAgent = ({ setIsAnalyzing(true); const spaceId = sdk.ids.space; - const environmentId = sdk.ids.environment; + const environmentId = sdk.ids.environmentAlias ?? sdk.ids.environment; try { await resumeWorkflowRun(sdk, spaceId, environmentId, runId, resumePayload); From cce8ed8e33065fa1dc5c79f70dba51b2d7d93bc7 Mon Sep 17 00:00:00 2001 From: JuliRossi Date: Wed, 10 Jun 2026 16:57:22 -0300 Subject: [PATCH 2/6] fix(drive-integration): resolve environment alias before agents API calls When the app is opened on a raw environment ID (e.g. master-2026-06-09), sdk.ids.environmentAlias is undefined. Fall back to fetching the environment and reading sys.aliases to find the alias name, so the agents API path matches the app installation which is scoped to the alias. Co-Authored-By: Claude Sonnet 4.6 --- .../src/hooks/useWorkflowAgent.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/drive-integration/src/hooks/useWorkflowAgent.ts b/apps/drive-integration/src/hooks/useWorkflowAgent.ts index 2273e5216a..045a9d152f 100644 --- a/apps/drive-integration/src/hooks/useWorkflowAgent.ts +++ b/apps/drive-integration/src/hooks/useWorkflowAgent.ts @@ -253,6 +253,21 @@ const pollAgentRun = async ( throw new Error('Workflow polling timeout'); }; +async function resolveEnvironmentId(sdk: PageAppSDK): Promise { + if (sdk.ids.environmentAlias) { + return sdk.ids.environmentAlias; + } + const spaceId = sdk.ids.space; + const rawEnvId = sdk.ids.environment; + try { + const env = await sdk.cma.environment.get({ spaceId, environmentId: rawEnvId }); + const alias = env.sys.aliases?.[0]?.sys.id; + return alias ?? rawEnvId; + } catch { + return rawEnvId; + } +} + export const useWorkflowAgent = ({ sdk, documentId, @@ -265,7 +280,7 @@ export const useWorkflowAgent = ({ setIsAnalyzing(true); const spaceId = sdk.ids.space; - const environmentId = sdk.ids.environmentAlias ?? sdk.ids.environment; + const environmentId = await resolveEnvironmentId(sdk); const threadId = [crypto.randomUUID(), WORKFLOW_AGENT_ID].join('-'); const payload: AgentGeneratePayload = { @@ -308,7 +323,7 @@ export const useWorkflowAgent = ({ setIsAnalyzing(true); const spaceId = sdk.ids.space; - const environmentId = sdk.ids.environmentAlias ?? sdk.ids.environment; + const environmentId = await resolveEnvironmentId(sdk); try { await resumeWorkflowRun(sdk, spaceId, environmentId, runId, resumePayload); From b8ff0df627c6534f0cf6f5620be3657303ff196e Mon Sep 17 00:00:00 2001 From: JuliRossi Date: Thu, 11 Jun 2026 10:23:53 -0300 Subject: [PATCH 3/6] test(drive-integration): add unit tests for resolveEnvironmentId Covers: alias fast-path, CMA alias lookup, no-alias fallback, and CMA error fallback. Co-Authored-By: Claude Sonnet 4.6 --- .../src/hooks/useWorkflowAgent.ts | 2 +- .../test/hooks/resolveEnvironmentId.test.ts | 65 +++++++++++++++++++ apps/drive-integration/test/mocks/mockCma.ts | 2 +- 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts diff --git a/apps/drive-integration/src/hooks/useWorkflowAgent.ts b/apps/drive-integration/src/hooks/useWorkflowAgent.ts index 045a9d152f..458112bc27 100644 --- a/apps/drive-integration/src/hooks/useWorkflowAgent.ts +++ b/apps/drive-integration/src/hooks/useWorkflowAgent.ts @@ -253,7 +253,7 @@ const pollAgentRun = async ( throw new Error('Workflow polling timeout'); }; -async function resolveEnvironmentId(sdk: PageAppSDK): Promise { +export async function resolveEnvironmentId(sdk: PageAppSDK): Promise { if (sdk.ids.environmentAlias) { return sdk.ids.environmentAlias; } diff --git a/apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts b/apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts new file mode 100644 index 0000000000..721ade9d83 --- /dev/null +++ b/apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import type { PageAppSDK } from '@contentful/app-sdk'; +import { resolveEnvironmentId } from '../../src/hooks/useWorkflowAgent'; +import { createMockSDK } from '../mocks'; + +describe('resolveEnvironmentId', () => { + let sdk: PageAppSDK; + + beforeEach(() => { + sdk = createMockSDK() as PageAppSDK; + vi.clearAllMocks(); + }); + + it('returns the alias directly when sdk.ids.environmentAlias is set', async () => { + sdk = createMockSDK({ + ids: { space: 'test-space-id', environment: 'master-2026-06-09', environmentAlias: 'master' }, + }) as PageAppSDK; + + const result = await resolveEnvironmentId(sdk); + + expect(result).toBe('master'); + expect(sdk.cma.environment.get).not.toHaveBeenCalled(); + }); + + it('resolves alias via CMA when environmentAlias is undefined and environment has aliases', async () => { + sdk = createMockSDK({ + ids: { space: 'test-space-id', environment: 'master-2026-06-09' }, + }) as PageAppSDK; + vi.mocked(sdk.cma.environment.get).mockResolvedValue({ + sys: { id: 'master-2026-06-09', aliases: [{ sys: { id: 'master' } }] }, + } as any); + + const result = await resolveEnvironmentId(sdk); + + expect(result).toBe('master'); + expect(sdk.cma.environment.get).toHaveBeenCalledWith({ + spaceId: 'test-space-id', + environmentId: 'master-2026-06-09', + }); + }); + + it('returns raw environment id when no alias exists on the environment', async () => { + sdk = createMockSDK({ + ids: { space: 'test-space-id', environment: 'my-feature-env' }, + }) as PageAppSDK; + vi.mocked(sdk.cma.environment.get).mockResolvedValue({ + sys: { id: 'my-feature-env', aliases: [] }, + } as any); + + const result = await resolveEnvironmentId(sdk); + + expect(result).toBe('my-feature-env'); + }); + + it('falls back to raw environment id when CMA call fails', async () => { + sdk = createMockSDK({ + ids: { space: 'test-space-id', environment: 'master-2026-06-09' }, + }) as PageAppSDK; + vi.mocked(sdk.cma.environment.get).mockRejectedValue(new Error('Network error')); + + const result = await resolveEnvironmentId(sdk); + + expect(result).toBe('master-2026-06-09'); + }); +}); diff --git a/apps/drive-integration/test/mocks/mockCma.ts b/apps/drive-integration/test/mocks/mockCma.ts index 45ae8f6708..86ddc19b50 100644 --- a/apps/drive-integration/test/mocks/mockCma.ts +++ b/apps/drive-integration/test/mocks/mockCma.ts @@ -18,7 +18,7 @@ export const createMockCMA = () => { get: vi.fn().mockResolvedValue({ sys: { id: 'test-space-id' } }), }, environment: { - get: vi.fn().mockResolvedValue({ sys: { id: 'test-environment-id' } }), + get: vi.fn().mockResolvedValue({ sys: { id: 'test-environment-id', aliases: [] } }), }, appAction: { getManyForEnvironment: vi.fn().mockResolvedValue({ items: [] }), From ee3476f1208c4322d36113ddf1835fc616e11ae3 Mon Sep 17 00:00:00 2001 From: JuliRossi Date: Thu, 11 Jun 2026 10:31:04 -0300 Subject: [PATCH 4/6] feat(drive-integration): add APP_NOT_INSTALLED and AI_SERVICE_UNAVAILABLE error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports error cases from #11012 — surfaces actionable messages when the app is not installed in the target environment or the AI service is down. Co-Authored-By: Claude Sonnet 4.6 --- apps/drive-integration/src/hooks/useWorkflowAgent.ts | 8 ++++++++ .../Page/components/mainpage/ModalOrchestrator.tsx | 12 ++++++++++++ apps/drive-integration/src/types/workflow.ts | 1 + .../src/utils/constants/messages.ts | 2 ++ 4 files changed, 23 insertions(+) diff --git a/apps/drive-integration/src/hooks/useWorkflowAgent.ts b/apps/drive-integration/src/hooks/useWorkflowAgent.ts index 458112bc27..d99502022f 100644 --- a/apps/drive-integration/src/hooks/useWorkflowAgent.ts +++ b/apps/drive-integration/src/hooks/useWorkflowAgent.ts @@ -131,6 +131,10 @@ const getBackendWorkflowFailureReason = (runData: AgentRunData): WorkflowFailure return WorkflowFailureReason.AI_SERVICE_UNAVAILABLE; } + if (workflowFailure.code === WorkflowFailureReason.APP_NOT_INSTALLED) { + return WorkflowFailureReason.APP_NOT_INSTALLED; + } + if (workflowFailure.code === WorkflowFailureReason.GENERIC) { return WorkflowFailureReason.GENERIC; } @@ -154,6 +158,10 @@ const getWorkflowFailureMessage = ( return ERROR_MESSAGES.AI_SERVICE_UNAVAILABLE; } + if (failureReason === WorkflowFailureReason.APP_NOT_INSTALLED) { + return ERROR_MESSAGES.APP_NOT_INSTALLED; + } + return getRunErrorMessage(runData); }; diff --git a/apps/drive-integration/src/locations/Page/components/mainpage/ModalOrchestrator.tsx b/apps/drive-integration/src/locations/Page/components/mainpage/ModalOrchestrator.tsx index b343e441c0..31e6431bee 100644 --- a/apps/drive-integration/src/locations/Page/components/mainpage/ModalOrchestrator.tsx +++ b/apps/drive-integration/src/locations/Page/components/mainpage/ModalOrchestrator.tsx @@ -187,6 +187,18 @@ export const ModalOrchestrator = forwardRef Manage apps and try again.', } as const; export const SUCCESS_MESSAGES = { From df25f13e18e2ae0fe9a801ef8dd8829ae439cd46 Mon Sep 17 00:00:00 2001 From: JuliRossi Date: Fri, 12 Jun 2026 12:03:27 -0300 Subject: [PATCH 5/6] refactor(drive-integration): simplify env alias resolution to sdk.ids.environmentAlias ?? sdk.ids.environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the resolveEnvironmentId helper (which made a CMA call to look up aliases) with the one-liner pattern used across closest-preview, slack, braze, and other apps. The CMA fallback was fragile (aliases[0] guess) and unnecessary — sdk.ids.environmentAlias is already populated when the user accesses via alias, which is the case that was causing the 403. Co-Authored-By: Claude Sonnet 4.6 --- .../src/hooks/useWorkflowAgent.ts | 18 +---- .../test/hooks/resolveEnvironmentId.test.ts | 65 ------------------- apps/drive-integration/test/mocks/mockCma.ts | 2 +- 3 files changed, 3 insertions(+), 82 deletions(-) delete mode 100644 apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts diff --git a/apps/drive-integration/src/hooks/useWorkflowAgent.ts b/apps/drive-integration/src/hooks/useWorkflowAgent.ts index d99502022f..866f4e2aa1 100644 --- a/apps/drive-integration/src/hooks/useWorkflowAgent.ts +++ b/apps/drive-integration/src/hooks/useWorkflowAgent.ts @@ -261,20 +261,6 @@ const pollAgentRun = async ( throw new Error('Workflow polling timeout'); }; -export async function resolveEnvironmentId(sdk: PageAppSDK): Promise { - if (sdk.ids.environmentAlias) { - return sdk.ids.environmentAlias; - } - const spaceId = sdk.ids.space; - const rawEnvId = sdk.ids.environment; - try { - const env = await sdk.cma.environment.get({ spaceId, environmentId: rawEnvId }); - const alias = env.sys.aliases?.[0]?.sys.id; - return alias ?? rawEnvId; - } catch { - return rawEnvId; - } -} export const useWorkflowAgent = ({ sdk, @@ -288,7 +274,7 @@ export const useWorkflowAgent = ({ setIsAnalyzing(true); const spaceId = sdk.ids.space; - const environmentId = await resolveEnvironmentId(sdk); + const environmentId = sdk.ids.environmentAlias ?? sdk.ids.environment; const threadId = [crypto.randomUUID(), WORKFLOW_AGENT_ID].join('-'); const payload: AgentGeneratePayload = { @@ -331,7 +317,7 @@ export const useWorkflowAgent = ({ setIsAnalyzing(true); const spaceId = sdk.ids.space; - const environmentId = await resolveEnvironmentId(sdk); + const environmentId = sdk.ids.environmentAlias ?? sdk.ids.environment; try { await resumeWorkflowRun(sdk, spaceId, environmentId, runId, resumePayload); diff --git a/apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts b/apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts deleted file mode 100644 index 721ade9d83..0000000000 --- a/apps/drive-integration/test/hooks/resolveEnvironmentId.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import type { PageAppSDK } from '@contentful/app-sdk'; -import { resolveEnvironmentId } from '../../src/hooks/useWorkflowAgent'; -import { createMockSDK } from '../mocks'; - -describe('resolveEnvironmentId', () => { - let sdk: PageAppSDK; - - beforeEach(() => { - sdk = createMockSDK() as PageAppSDK; - vi.clearAllMocks(); - }); - - it('returns the alias directly when sdk.ids.environmentAlias is set', async () => { - sdk = createMockSDK({ - ids: { space: 'test-space-id', environment: 'master-2026-06-09', environmentAlias: 'master' }, - }) as PageAppSDK; - - const result = await resolveEnvironmentId(sdk); - - expect(result).toBe('master'); - expect(sdk.cma.environment.get).not.toHaveBeenCalled(); - }); - - it('resolves alias via CMA when environmentAlias is undefined and environment has aliases', async () => { - sdk = createMockSDK({ - ids: { space: 'test-space-id', environment: 'master-2026-06-09' }, - }) as PageAppSDK; - vi.mocked(sdk.cma.environment.get).mockResolvedValue({ - sys: { id: 'master-2026-06-09', aliases: [{ sys: { id: 'master' } }] }, - } as any); - - const result = await resolveEnvironmentId(sdk); - - expect(result).toBe('master'); - expect(sdk.cma.environment.get).toHaveBeenCalledWith({ - spaceId: 'test-space-id', - environmentId: 'master-2026-06-09', - }); - }); - - it('returns raw environment id when no alias exists on the environment', async () => { - sdk = createMockSDK({ - ids: { space: 'test-space-id', environment: 'my-feature-env' }, - }) as PageAppSDK; - vi.mocked(sdk.cma.environment.get).mockResolvedValue({ - sys: { id: 'my-feature-env', aliases: [] }, - } as any); - - const result = await resolveEnvironmentId(sdk); - - expect(result).toBe('my-feature-env'); - }); - - it('falls back to raw environment id when CMA call fails', async () => { - sdk = createMockSDK({ - ids: { space: 'test-space-id', environment: 'master-2026-06-09' }, - }) as PageAppSDK; - vi.mocked(sdk.cma.environment.get).mockRejectedValue(new Error('Network error')); - - const result = await resolveEnvironmentId(sdk); - - expect(result).toBe('master-2026-06-09'); - }); -}); diff --git a/apps/drive-integration/test/mocks/mockCma.ts b/apps/drive-integration/test/mocks/mockCma.ts index 86ddc19b50..45ae8f6708 100644 --- a/apps/drive-integration/test/mocks/mockCma.ts +++ b/apps/drive-integration/test/mocks/mockCma.ts @@ -18,7 +18,7 @@ export const createMockCMA = () => { get: vi.fn().mockResolvedValue({ sys: { id: 'test-space-id' } }), }, environment: { - get: vi.fn().mockResolvedValue({ sys: { id: 'test-environment-id', aliases: [] } }), + get: vi.fn().mockResolvedValue({ sys: { id: 'test-environment-id' } }), }, appAction: { getManyForEnvironment: vi.fn().mockResolvedValue({ items: [] }), From ae5ad0e49fadb2a3e7c7d9e99cc6996e338ed4e6 Mon Sep 17 00:00:00 2001 From: JuliRossi Date: Fri, 12 Jun 2026 12:12:22 -0300 Subject: [PATCH 6/6] chore(drive-integration): apply prettier formatting to useWorkflowAgent.ts Co-Authored-By: Claude Sonnet 4.6 --- apps/drive-integration/src/hooks/useWorkflowAgent.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/drive-integration/src/hooks/useWorkflowAgent.ts b/apps/drive-integration/src/hooks/useWorkflowAgent.ts index 866f4e2aa1..27f82b15ca 100644 --- a/apps/drive-integration/src/hooks/useWorkflowAgent.ts +++ b/apps/drive-integration/src/hooks/useWorkflowAgent.ts @@ -261,7 +261,6 @@ const pollAgentRun = async ( throw new Error('Workflow polling timeout'); }; - export const useWorkflowAgent = ({ sdk, documentId,