diff --git a/ui/src/features/agent/workspaceActions.ts b/ui/src/features/agent/workspaceActions.ts index c964ad26..8e203c87 100644 --- a/ui/src/features/agent/workspaceActions.ts +++ b/ui/src/features/agent/workspaceActions.ts @@ -1,48 +1 @@ -import * as api from '../../api/client' -import { useStore } from '../../stores/useStore' - -const normalized = (value: string): string => value.trim().toLocaleLowerCase() - -async function authoritativeWorkspaces() { - const result = await api.fetchWorkspaces() - useStore.setState({ workspaces: result.workspaces }) - return result -} - -export async function selectAgentWorkspace(requestedName: string): Promise { - if (requestedName === '__uploads__') { - throw new Error('Uploads es una vista virtual de sólo lectura, no un workspace seleccionable para generar.') - } - const before = await authoritativeWorkspaces() - const workspace = before.workspaces.find(item => normalized(item.name) === normalized(requestedName)) - if (!workspace) { - throw new Error(`No existe el workspace “${requestedName}”. Los disponibles son: ${before.workspaces.map(item => item.name).join(', ') || 'ninguno'}.`) - } - if (before.active === workspace.name && useStore.getState().activeWorkspace === workspace.name) { - return `El workspace “${workspace.name}” ya estaba activo.` - } - await useStore.getState().switchWorkspace(workspace.name) - const after = await api.fetchWorkspaces() - if (after.active !== workspace.name || useStore.getState().activeWorkspace !== workspace.name) { - throw new Error(`El backend no confirmó el cambio al workspace “${workspace.name}”; no afirmaré que se completó.`) - } - return `He cambiado al workspace “${workspace.name}”. El chat y las siguientes acciones continúan en ese contexto.` -} - -export async function createAgentWorkspace(requestedName: string): Promise { - const name = requestedName.trim() - if (!name || name === '__uploads__') throw new Error('Ese nombre de workspace no es válido.') - const before = await authoritativeWorkspaces() - const existing = before.workspaces.find(item => normalized(item.name) === normalized(name)) - if (existing) { - const selected = await selectAgentWorkspace(existing.name) - return `El workspace “${existing.name}” ya existía. ${selected}` - } - await useStore.getState().createWorkspace(name) - const after = await api.fetchWorkspaces() - const created = after.workspaces.find(item => normalized(item.name) === normalized(name)) - if (!created || after.active !== created.name || useStore.getState().activeWorkspace !== created.name) { - throw new Error(`El backend no confirmó la creación y selección de “${name}”.`) - } - return `He creado y seleccionado el workspace “${created.name}”. El chat continúa aquí y las nuevas generaciones se guardarán en él.` -} +export { createAgentWorkspace, selectAgentWorkspace } from '../workspaces/actions' diff --git a/ui/src/features/workspaces/actions.ts b/ui/src/features/workspaces/actions.ts new file mode 100644 index 00000000..c964ad26 --- /dev/null +++ b/ui/src/features/workspaces/actions.ts @@ -0,0 +1,48 @@ +import * as api from '../../api/client' +import { useStore } from '../../stores/useStore' + +const normalized = (value: string): string => value.trim().toLocaleLowerCase() + +async function authoritativeWorkspaces() { + const result = await api.fetchWorkspaces() + useStore.setState({ workspaces: result.workspaces }) + return result +} + +export async function selectAgentWorkspace(requestedName: string): Promise { + if (requestedName === '__uploads__') { + throw new Error('Uploads es una vista virtual de sólo lectura, no un workspace seleccionable para generar.') + } + const before = await authoritativeWorkspaces() + const workspace = before.workspaces.find(item => normalized(item.name) === normalized(requestedName)) + if (!workspace) { + throw new Error(`No existe el workspace “${requestedName}”. Los disponibles son: ${before.workspaces.map(item => item.name).join(', ') || 'ninguno'}.`) + } + if (before.active === workspace.name && useStore.getState().activeWorkspace === workspace.name) { + return `El workspace “${workspace.name}” ya estaba activo.` + } + await useStore.getState().switchWorkspace(workspace.name) + const after = await api.fetchWorkspaces() + if (after.active !== workspace.name || useStore.getState().activeWorkspace !== workspace.name) { + throw new Error(`El backend no confirmó el cambio al workspace “${workspace.name}”; no afirmaré que se completó.`) + } + return `He cambiado al workspace “${workspace.name}”. El chat y las siguientes acciones continúan en ese contexto.` +} + +export async function createAgentWorkspace(requestedName: string): Promise { + const name = requestedName.trim() + if (!name || name === '__uploads__') throw new Error('Ese nombre de workspace no es válido.') + const before = await authoritativeWorkspaces() + const existing = before.workspaces.find(item => normalized(item.name) === normalized(name)) + if (existing) { + const selected = await selectAgentWorkspace(existing.name) + return `El workspace “${existing.name}” ya existía. ${selected}` + } + await useStore.getState().createWorkspace(name) + const after = await api.fetchWorkspaces() + const created = after.workspaces.find(item => normalized(item.name) === normalized(name)) + if (!created || after.active !== created.name || useStore.getState().activeWorkspace !== created.name) { + throw new Error(`El backend no confirmó la creación y selección de “${name}”.`) + } + return `He creado y seleccionado el workspace “${created.name}”. El chat continúa aquí y las nuevas generaciones se guardarán en él.` +} diff --git a/ui/src/features/workspaces/adapters.ts b/ui/src/features/workspaces/adapters.ts new file mode 100644 index 00000000..048db12e --- /dev/null +++ b/ui/src/features/workspaces/adapters.ts @@ -0,0 +1,10 @@ +import { createAgentWorkspace, selectAgentWorkspace } from './actions' +import type { CreateWorkspaceCommand, SelectWorkspaceCommand } from './commands' + +export async function selectWorkspace(command: SelectWorkspaceCommand): Promise { + return selectAgentWorkspace(command.workspaceName) +} + +export async function createWorkspace(command: CreateWorkspaceCommand): Promise { + return createAgentWorkspace(command.workspaceName) +} diff --git a/ui/src/features/workspaces/commands.ts b/ui/src/features/workspaces/commands.ts new file mode 100644 index 00000000..5ae9cc62 --- /dev/null +++ b/ui/src/features/workspaces/commands.ts @@ -0,0 +1,7 @@ +export type SelectWorkspaceCommand = { + workspaceName: string +} + +export type CreateWorkspaceCommand = { + workspaceName: string +} diff --git a/ui/tests/agentCapabilityPorts.test.mjs b/ui/tests/agentCapabilityPorts.test.mjs index 190b2a6b..8c847ec6 100644 --- a/ui/tests/agentCapabilityPorts.test.mjs +++ b/ui/tests/agentCapabilityPorts.test.mjs @@ -26,7 +26,6 @@ const SET_STATE_ALLOWLIST = [ ['labActions.ts', 'stageStoryVideo', 3], ['queueActions.ts', 'cancelCanonicalQueueTask', 1], ['queueActions.ts', 'resumeCanonicalQueueTask', 1], - ['workspaceActions.ts', 'authoritativeWorkspaces', 1], ] const SLICE_AGENT_IMPORT_ALLOWLIST = [ @@ -214,7 +213,7 @@ test('useStore.setState in features/agent stays on the named allowlist outside a 'Direct store writes in Agent Mode must shrink the allowlist when a function moves to a slice adapter, and must not grow. ' + `added=${JSON.stringify(added)} removed=${JSON.stringify(removed)}`, ) - assert.equal(actual.reduce((total, row) => total + row[2], 0), 8) + assert.equal(actual.reduce((total, row) => total + row[2], 0), 7) }) test('other feature slices do not import Agent Mode except the frozen UI-bus listeners', () => {