Skip to content

Commit 6d0ad50

Browse files
dante01yoonampagent
andcommitted
fix(workspace): guard prompt billing context
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a01a57-6259-75bd-bb52-c149e0f7bb9c
1 parent ae42812 commit 6d0ad50

3 files changed

Lines changed: 92 additions & 1 deletion

File tree

src/locales/en/main.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2128,7 +2128,8 @@
21282128
"extensionFileHint": "This may be due to the following script",
21292129
"promptExecutionError": "Prompt execution failed",
21302130
"accessRestrictedTitle": "Access Restricted",
2131-
"accessRestrictedMessage": "Your account is not authorized for this feature."
2131+
"accessRestrictedMessage": "Your account is not authorized for this feature.",
2132+
"workspaceChangedDuringExecution": "Your active workspace changed while preparing this execution. Run the workflow again to use the current workspace."
21322133
},
21332134
"apiNodesSignInDialog": {
21342135
"title": "Sign In Required to Use API Nodes",

src/scripts/app.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const {
6060
mockToastStore,
6161
mockExtensionService,
6262
mockNodeOutputStore,
63+
mockTeamWorkspaceStore,
6364
mockWorkspaceWorkflow,
6465
mockRefreshMissingModelPipeline,
6566
mockImportA1111,
@@ -87,6 +88,9 @@ const {
8788
refreshNodeOutputs: vi.fn(),
8889
resetAllOutputsAndPreviews: vi.fn()
8990
},
91+
mockTeamWorkspaceStore: {
92+
activeWorkspaceId: 'workspace-a' as string | null
93+
},
9094
mockWorkspaceWorkflow: {
9195
activeWorkflow: null as ComfyWorkflow | null,
9296
createNewTemporary: vi.fn(),
@@ -119,6 +123,10 @@ vi.mock('@/stores/authStore', () => ({
119123
useAuthStore: vi.fn(() => mockAuthStore)
120124
}))
121125

126+
vi.mock('@/platform/workspace/stores/teamWorkspaceStore', () => ({
127+
useTeamWorkspaceStore: vi.fn(() => mockTeamWorkspaceStore)
128+
}))
129+
122130
vi.mock('@/platform/settings/settingStore', () => ({
123131
useSettingStore: vi.fn(() => mockSettingStore)
124132
}))
@@ -281,6 +289,7 @@ describe('ComfyApp', () => {
281289
})
282290
mockApiKeyAuthStore.getApiKey.mockReturnValue(undefined)
283291
mockAuthStore.getWorkspaceAuthToken.mockResolvedValue(undefined)
292+
mockTeamWorkspaceStore.activeWorkspaceId = 'workspace-a'
284293
mockExtensionService.invokeExtensions.mockReturnValue([])
285294
mockExtensionService.invokeExtensionsAsync.mockResolvedValue(undefined)
286295
vi.mocked(extractFilesFromDragEvent).mockResolvedValue([])
@@ -335,6 +344,66 @@ describe('ComfyApp', () => {
335344
expect(queuePrompt).toHaveBeenCalledOnce()
336345
})
337346

347+
it('uses a workspace initialized while authentication is pending', async () => {
348+
prepareEmptyPromptQueue()
349+
mockTeamWorkspaceStore.activeWorkspaceId = null
350+
mockAuthStore.getWorkspaceAuthToken.mockImplementationOnce(async () => {
351+
mockTeamWorkspaceStore.activeWorkspaceId = 'workspace-a'
352+
return 'workspace-token'
353+
})
354+
const queuePrompt = vi
355+
.spyOn(api, 'queuePrompt')
356+
.mockImplementation(() => {
357+
expect(api.authToken).toBe('workspace-token')
358+
return Promise.resolve({ prompt_id: 'job-1', error: '' })
359+
})
360+
361+
await expect(app.queuePrompt(0)).resolves.toBe(true)
362+
363+
expect(queuePrompt).toHaveBeenCalledOnce()
364+
})
365+
366+
it('does not submit when the workspace changes during authentication', async () => {
367+
prepareEmptyPromptQueue()
368+
mockAuthStore.getWorkspaceAuthToken.mockImplementationOnce(async () => {
369+
mockTeamWorkspaceStore.activeWorkspaceId = 'workspace-b'
370+
return 'workspace-token-a'
371+
})
372+
const queuePrompt = vi.spyOn(api, 'queuePrompt')
373+
const showDialog = vi.spyOn(useDialogStore(), 'showDialog')
374+
375+
await expect(app.queuePrompt(0)).resolves.toBe(false)
376+
377+
expect(queuePrompt).not.toHaveBeenCalled()
378+
expect(showDialog).toHaveBeenCalledOnce()
379+
})
380+
381+
it('does not submit a prompt after the active workspace changes', async () => {
382+
prepareEmptyPromptQueue()
383+
let finishPromptBuild: () => void = () => {}
384+
vi.spyOn(app, 'graphToPrompt').mockImplementationOnce(
385+
() =>
386+
new Promise((resolve) => {
387+
finishPromptBuild = () =>
388+
resolve({
389+
output: {},
390+
workflow: createWorkflowGraphData()
391+
})
392+
})
393+
)
394+
const queuePrompt = vi.spyOn(api, 'queuePrompt')
395+
const showDialog = vi.spyOn(useDialogStore(), 'showDialog')
396+
397+
const submission = app.queuePrompt(0)
398+
await vi.waitFor(() => expect(app.graphToPrompt).toHaveBeenCalledOnce())
399+
mockTeamWorkspaceStore.activeWorkspaceId = 'workspace-b'
400+
finishPromptBuild()
401+
402+
await expect(submission).resolves.toBe(false)
403+
expect(queuePrompt).not.toHaveBeenCalled()
404+
expect(showDialog).toHaveBeenCalledOnce()
405+
})
406+
338407
it('preserves missing node packs when submitting a prompt', async () => {
339408
prepareEmptyPromptQueue()
340409
const missingNodesStore = useMissingNodesErrorStore()

src/scripts/app.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import {
7979
} from '@/scripts/domWidget'
8080
import { useAccountPreconditionDialog } from '@/platform/cloud/subscription/composables/useAccountPreconditionDialog'
8181
import { resolveAccountPrecondition } from '@/platform/errorCatalog/accountPreconditionRouting'
82+
import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore'
8283
import { useDialogService } from '@/services/dialogService'
8384
import { useExtensionService } from '@/services/extensionService'
8485
import { useLitegraphService } from '@/services/litegraphService'
@@ -1640,7 +1641,13 @@ export class ComfyApp {
16401641
let queueResultOverride: boolean | null = null
16411642

16421643
// Get auth token for backend nodes - uses workspace token if enabled, otherwise Firebase token
1644+
const workspaceIdBeforeAuthentication =
1645+
useTeamWorkspaceStore().activeWorkspaceId
16431646
const comfyOrgAuthToken = await useAuthStore().getWorkspaceAuthToken()
1647+
const executionWorkspaceId = useTeamWorkspaceStore().activeWorkspaceId
1648+
const workspaceChangedWhileAuthenticating =
1649+
workspaceIdBeforeAuthentication !== null &&
1650+
workspaceIdBeforeAuthentication !== executionWorkspaceId
16441651
const comfyOrgApiKey = useApiKeyAuthStore().getApiKey()
16451652

16461653
try {
@@ -1713,6 +1720,20 @@ export class ComfyApp {
17131720
viewMode: getWorkflowMode(queuedWorkflow)
17141721
})
17151722
}
1723+
if (
1724+
workspaceChangedWhileAuthenticating ||
1725+
executionWorkspaceId !== useTeamWorkspaceStore().activeWorkspaceId
1726+
) {
1727+
useDialogService().showErrorDialog(
1728+
new Error(t('errorDialog.workspaceChangedDuringExecution')),
1729+
{
1730+
title: t('errorDialog.promptExecutionError'),
1731+
reportType: 'promptExecutionError'
1732+
}
1733+
)
1734+
queueResultOverride = false
1735+
break
1736+
}
17161737
try {
17171738
api.authToken = comfyOrgAuthToken
17181739
api.apiKey = comfyOrgApiKey ?? undefined

0 commit comments

Comments
 (0)