Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 1 addition & 48 deletions ui/src/features/agent/workspaceActions.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
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<string> {
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'
48 changes: 48 additions & 0 deletions ui/src/features/workspaces/actions.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
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<string> {
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.`
}
10 changes: 10 additions & 0 deletions ui/src/features/workspaces/adapters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createAgentWorkspace, selectAgentWorkspace } from './actions'
import type { CreateWorkspaceCommand, SelectWorkspaceCommand } from './commands'

export async function selectWorkspace(command: SelectWorkspaceCommand): Promise<string> {
return selectAgentWorkspace(command.workspaceName)
}

export async function createWorkspace(command: CreateWorkspaceCommand): Promise<string> {
return createAgentWorkspace(command.workspaceName)
}
7 changes: 7 additions & 0 deletions ui/src/features/workspaces/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type SelectWorkspaceCommand = {
workspaceName: string
}

export type CreateWorkspaceCommand = {
workspaceName: string
}
3 changes: 1 addition & 2 deletions ui/tests/agentCapabilityPorts.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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', () => {
Expand Down