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
70 changes: 70 additions & 0 deletions frontend/taskdeck-web/src/api/agentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import http from './http'
import { normalizeRunStatus, normalizeScopeType } from '../types/agent'
import type {
AgentProfile,
AgentRun,
AgentRunDetail,
AgentRunStatusValue,
AgentScopeTypeValue,
} from '../types/agent'

/** Raw profile shape from backend (enums may be numeric) */
interface RawAgentProfile extends Omit<AgentProfile, 'scopeType'> {
scopeType: AgentScopeTypeValue
}

/** Raw run shape from backend (enums may be numeric) */
interface RawAgentRun extends Omit<AgentRun, 'status'> {
status: AgentRunStatusValue
}

interface RawAgentRunDetail extends Omit<AgentRunDetail, 'status'> {
status: AgentRunStatusValue
}

function normalizeProfile(raw: RawAgentProfile): AgentProfile {
return {
...raw,
scopeType: normalizeScopeType(raw.scopeType),
}
}

function normalizeRun(raw: RawAgentRun): AgentRun {
return {
...raw,
status: normalizeRunStatus(raw.status),
}
}

function normalizeRunDetail(raw: RawAgentRunDetail): AgentRunDetail {
return {
...raw,
status: normalizeRunStatus(raw.status),
}
}

export const agentApi = {
async listProfiles(): Promise<AgentProfile[]> {
const { data } = await http.get<RawAgentProfile[]>('/agents')
return data.map(normalizeProfile)
},

async getProfile(id: string): Promise<AgentProfile> {
const { data } = await http.get<RawAgentProfile>(`/agents/${encodeURIComponent(id)}`)
return normalizeProfile(data)
},

async listRuns(agentId: string, limit = 100): Promise<AgentRun[]> {
const { data } = await http.get<RawAgentRun[]>(
`/agents/${encodeURIComponent(agentId)}/runs?limit=${limit}`,
)
return data.map(normalizeRun)
},

async getRunDetail(agentId: string, runId: string): Promise<AgentRunDetail> {
const { data } = await http.get<RawAgentRunDetail>(
`/agents/${encodeURIComponent(agentId)}/runs/${encodeURIComponent(runId)}`,
)
return normalizeRunDetail(data)
},
}
9 changes: 9 additions & 0 deletions frontend/taskdeck-web/src/components/shell/ShellSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ const navCatalog: NavItem[] = [
primaryModes: ['guided', 'workbench', 'agent'],
keywords: 'inbox captures triage',
},
{
id: 'agents',
label: 'Agents',
icon: 'G',
path: '/workspace/agents',
flag: null,
primaryModes: ['agent'],
keywords: 'agents profiles runs automation agent mode',
},
{
id: 'views',
label: 'Views',
Expand Down
23 changes: 23 additions & 0 deletions frontend/taskdeck-web/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const ReviewView = () => import('../views/ReviewView.vue')
const DevToolsView = () => import('../views/DevToolsView.vue')
const SavedViewsView = () => import('../views/SavedViewsView.vue')
const MetricsView = () => import('../views/MetricsView.vue')
const AgentsView = () => import('../views/AgentsView.vue')
const AgentRunsView = () => import('../views/AgentRunsView.vue')
const AgentRunDetailView = () => import('../views/AgentRunDetailView.vue')

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
Expand Down Expand Up @@ -257,6 +260,26 @@ const router = createRouter({
meta: { requiresShell: true },
},

// Agent surfaces (visible in agent workspace mode)
{
path: '/workspace/agents',
name: 'workspace-agents',
component: AgentsView,
meta: { requiresShell: true },
},
{
path: '/workspace/agents/:agentId/runs',
name: 'workspace-agent-runs',
component: AgentRunsView,
meta: { requiresShell: true },
},
{
path: '/workspace/agents/:agentId/runs/:runId',
name: 'workspace-agent-run-detail',
component: AgentRunDetailView,
meta: { requiresShell: true },
},

// Internal dev tooling (trace replay + scenario editor)
{
path: '/workspace/dev-tools',
Expand Down
116 changes: 116 additions & 0 deletions frontend/taskdeck-web/src/store/agentStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { agentApi } from '../api/agentApi'
import { useToastStore } from './toastStore'
import { isDemoMode } from '../utils/demoMode'
import { getErrorDisplay } from '../composables/useErrorMapper'
import type { AgentProfile, AgentRun, AgentRunDetail } from '../types/agent'

export const useAgentStore = defineStore('agent', () => {
const toast = useToastStore()

const profiles = ref<AgentProfile[]>([])
const profilesLoading = ref(false)
const profilesError = ref<string | null>(null)

const runs = ref<AgentRun[]>([])
const runsLoading = ref(false)
const runsError = ref<string | null>(null)

const runDetail = ref<AgentRunDetail | null>(null)
const runDetailLoading = ref(false)
const runDetailError = ref<string | null>(null)

async function fetchProfiles(): Promise<void> {
if (isDemoMode) {
profilesLoading.value = true
profilesError.value = null
profiles.value = []
profilesLoading.value = false
return
}
try {
profilesLoading.value = true
profilesError.value = null
profiles.value = await agentApi.listProfiles()
} catch (e: unknown) {
const msg = getErrorDisplay(e, 'Failed to load agent profiles').message
profilesError.value = msg
toast.error(msg)
throw e
} finally {
profilesLoading.value = false
}
}

async function fetchRuns(agentId: string, limit = 100): Promise<void> {
if (isDemoMode) {
runsLoading.value = true
runsError.value = null
runs.value = []
runsLoading.value = false
return
}
try {
runsLoading.value = true
runsError.value = null
runs.value = await agentApi.listRuns(agentId, limit)
} catch (e: unknown) {
const msg = getErrorDisplay(e, 'Failed to load agent runs').message
runsError.value = msg
toast.error(msg)
throw e
} finally {
runsLoading.value = false
}
}

async function fetchRunDetail(agentId: string, runId: string): Promise<void> {
if (isDemoMode) {
runDetailLoading.value = true
runDetailError.value = null
runDetail.value = null
runDetailLoading.value = false
return
}
try {
runDetailLoading.value = true
runDetailError.value = null
runDetail.value = await agentApi.getRunDetail(agentId, runId)
} catch (e: unknown) {
const msg = getErrorDisplay(e, 'Failed to load run details').message
runDetailError.value = msg
toast.error(msg)
throw e
} finally {
runDetailLoading.value = false
}
}

function clearRuns(): void {
runs.value = []
runsError.value = null
}

function clearRunDetail(): void {
runDetail.value = null
runDetailError.value = null
}

return {
profiles,
profilesLoading,
profilesError,
runs,
runsLoading,
runsError,
runDetail,
runDetailLoading,
runDetailError,
fetchProfiles,
fetchRuns,
fetchRunDetail,
clearRuns,
clearRunDetail,
}
})
Loading
Loading