Skip to content

Commit 1b6c210

Browse files
authored
Merge pull request #808 from Chris0Jeky/agt/agent-mode-surfaces-and-runs
Add Agents/Runs surfaces and run-detail timeline in Agent mode
2 parents d4fbe3b + ac12f16 commit 1b6c210

File tree

13 files changed

+1906
-0
lines changed

13 files changed

+1906
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import http from './http'
2+
import { normalizeRunStatus, normalizeScopeType } from '../types/agent'
3+
import type {
4+
AgentProfile,
5+
AgentRun,
6+
AgentRunDetail,
7+
AgentRunStatusValue,
8+
AgentScopeTypeValue,
9+
} from '../types/agent'
10+
11+
/** Raw profile shape from backend (enums may be numeric) */
12+
interface RawAgentProfile extends Omit<AgentProfile, 'scopeType'> {
13+
scopeType: AgentScopeTypeValue
14+
}
15+
16+
/** Raw run shape from backend (enums may be numeric) */
17+
interface RawAgentRun extends Omit<AgentRun, 'status'> {
18+
status: AgentRunStatusValue
19+
}
20+
21+
interface RawAgentRunDetail extends Omit<AgentRunDetail, 'status'> {
22+
status: AgentRunStatusValue
23+
}
24+
25+
function normalizeProfile(raw: RawAgentProfile): AgentProfile {
26+
return {
27+
...raw,
28+
scopeType: normalizeScopeType(raw.scopeType),
29+
}
30+
}
31+
32+
function normalizeRun(raw: RawAgentRun): AgentRun {
33+
return {
34+
...raw,
35+
status: normalizeRunStatus(raw.status),
36+
}
37+
}
38+
39+
function normalizeRunDetail(raw: RawAgentRunDetail): AgentRunDetail {
40+
return {
41+
...raw,
42+
status: normalizeRunStatus(raw.status),
43+
}
44+
}
45+
46+
export const agentApi = {
47+
async listProfiles(): Promise<AgentProfile[]> {
48+
const { data } = await http.get<RawAgentProfile[]>('/agents')
49+
return data.map(normalizeProfile)
50+
},
51+
52+
async getProfile(id: string): Promise<AgentProfile> {
53+
const { data } = await http.get<RawAgentProfile>(`/agents/${encodeURIComponent(id)}`)
54+
return normalizeProfile(data)
55+
},
56+
57+
async listRuns(agentId: string, limit = 100): Promise<AgentRun[]> {
58+
const { data } = await http.get<RawAgentRun[]>(
59+
`/agents/${encodeURIComponent(agentId)}/runs?limit=${limit}`,
60+
)
61+
return data.map(normalizeRun)
62+
},
63+
64+
async getRunDetail(agentId: string, runId: string): Promise<AgentRunDetail> {
65+
const { data } = await http.get<RawAgentRunDetail>(
66+
`/agents/${encodeURIComponent(agentId)}/runs/${encodeURIComponent(runId)}`,
67+
)
68+
return normalizeRunDetail(data)
69+
},
70+
}

frontend/taskdeck-web/src/components/shell/ShellSidebar.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ const navCatalog: NavItem[] = [
111111
primaryModes: ['guided', 'workbench', 'agent'],
112112
keywords: 'inbox captures triage',
113113
},
114+
{
115+
id: 'agents',
116+
label: 'Agents',
117+
icon: 'G',
118+
path: '/workspace/agents',
119+
flag: null,
120+
primaryModes: ['agent'],
121+
keywords: 'agents profiles runs automation agent mode',
122+
},
114123
{
115124
id: 'views',
116125
label: 'Views',

frontend/taskdeck-web/src/router/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const ReviewView = () => import('../views/ReviewView.vue')
3939
const DevToolsView = () => import('../views/DevToolsView.vue')
4040
const SavedViewsView = () => import('../views/SavedViewsView.vue')
4141
const MetricsView = () => import('../views/MetricsView.vue')
42+
const AgentsView = () => import('../views/AgentsView.vue')
43+
const AgentRunsView = () => import('../views/AgentRunsView.vue')
44+
const AgentRunDetailView = () => import('../views/AgentRunDetailView.vue')
4245

4346
const router = createRouter({
4447
history: createWebHistory(import.meta.env.BASE_URL),
@@ -257,6 +260,26 @@ const router = createRouter({
257260
meta: { requiresShell: true },
258261
},
259262

263+
// Agent surfaces (visible in agent workspace mode)
264+
{
265+
path: '/workspace/agents',
266+
name: 'workspace-agents',
267+
component: AgentsView,
268+
meta: { requiresShell: true },
269+
},
270+
{
271+
path: '/workspace/agents/:agentId/runs',
272+
name: 'workspace-agent-runs',
273+
component: AgentRunsView,
274+
meta: { requiresShell: true },
275+
},
276+
{
277+
path: '/workspace/agents/:agentId/runs/:runId',
278+
name: 'workspace-agent-run-detail',
279+
component: AgentRunDetailView,
280+
meta: { requiresShell: true },
281+
},
282+
260283
// Internal dev tooling (trace replay + scenario editor)
261284
{
262285
path: '/workspace/dev-tools',
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { defineStore } from 'pinia'
2+
import { ref } from 'vue'
3+
import { agentApi } from '../api/agentApi'
4+
import { useToastStore } from './toastStore'
5+
import { isDemoMode } from '../utils/demoMode'
6+
import { getErrorDisplay } from '../composables/useErrorMapper'
7+
import type { AgentProfile, AgentRun, AgentRunDetail } from '../types/agent'
8+
9+
export const useAgentStore = defineStore('agent', () => {
10+
const toast = useToastStore()
11+
12+
const profiles = ref<AgentProfile[]>([])
13+
const profilesLoading = ref(false)
14+
const profilesError = ref<string | null>(null)
15+
16+
const runs = ref<AgentRun[]>([])
17+
const runsLoading = ref(false)
18+
const runsError = ref<string | null>(null)
19+
20+
const runDetail = ref<AgentRunDetail | null>(null)
21+
const runDetailLoading = ref(false)
22+
const runDetailError = ref<string | null>(null)
23+
24+
async function fetchProfiles(): Promise<void> {
25+
if (isDemoMode) {
26+
profilesLoading.value = true
27+
profilesError.value = null
28+
profiles.value = []
29+
profilesLoading.value = false
30+
return
31+
}
32+
try {
33+
profilesLoading.value = true
34+
profilesError.value = null
35+
profiles.value = await agentApi.listProfiles()
36+
} catch (e: unknown) {
37+
const msg = getErrorDisplay(e, 'Failed to load agent profiles').message
38+
profilesError.value = msg
39+
toast.error(msg)
40+
throw e
41+
} finally {
42+
profilesLoading.value = false
43+
}
44+
}
45+
46+
async function fetchRuns(agentId: string, limit = 100): Promise<void> {
47+
if (isDemoMode) {
48+
runsLoading.value = true
49+
runsError.value = null
50+
runs.value = []
51+
runsLoading.value = false
52+
return
53+
}
54+
try {
55+
runsLoading.value = true
56+
runsError.value = null
57+
runs.value = await agentApi.listRuns(agentId, limit)
58+
} catch (e: unknown) {
59+
const msg = getErrorDisplay(e, 'Failed to load agent runs').message
60+
runsError.value = msg
61+
toast.error(msg)
62+
throw e
63+
} finally {
64+
runsLoading.value = false
65+
}
66+
}
67+
68+
async function fetchRunDetail(agentId: string, runId: string): Promise<void> {
69+
if (isDemoMode) {
70+
runDetailLoading.value = true
71+
runDetailError.value = null
72+
runDetail.value = null
73+
runDetailLoading.value = false
74+
return
75+
}
76+
try {
77+
runDetailLoading.value = true
78+
runDetailError.value = null
79+
runDetail.value = await agentApi.getRunDetail(agentId, runId)
80+
} catch (e: unknown) {
81+
const msg = getErrorDisplay(e, 'Failed to load run details').message
82+
runDetailError.value = msg
83+
toast.error(msg)
84+
throw e
85+
} finally {
86+
runDetailLoading.value = false
87+
}
88+
}
89+
90+
function clearRuns(): void {
91+
runs.value = []
92+
runsError.value = null
93+
}
94+
95+
function clearRunDetail(): void {
96+
runDetail.value = null
97+
runDetailError.value = null
98+
}
99+
100+
return {
101+
profiles,
102+
profilesLoading,
103+
profilesError,
104+
runs,
105+
runsLoading,
106+
runsError,
107+
runDetail,
108+
runDetailLoading,
109+
runDetailError,
110+
fetchProfiles,
111+
fetchRuns,
112+
fetchRunDetail,
113+
clearRuns,
114+
clearRunDetail,
115+
}
116+
})

0 commit comments

Comments
 (0)