fleetlens 0.11.2 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/app-path-routes-manifest.json +3 -0
- package/app/apps/web/.next/build-manifest.json +3 -3
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/routes-manifest.json +22 -0
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js +2 -1
- package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/api/ask/route.js +1 -1
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js +1 -1
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js +1 -1
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/events/route.js +1 -1
- package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/jobs/route.js +1 -1
- package/app/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/pdf/[key]/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/pdf/[key]/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/pdf/[key]/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js +10 -0
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/pdf/[key]/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/runs/[runId]/route.js +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/route.js +1 -1
- package/app/apps/web/.next/server/app/api/runs/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/settings/route.js +1 -1
- package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team-config/route.js +1 -1
- package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/workflow-agent/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/workflow-agent/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/workflow-agent/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/workflow-agent/route.js +7 -0
- package/app/apps/web/.next/server/app/api/workflow-agent/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/workflow-agent/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/workflow-agent/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/changelog/page.js +2 -1
- package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js +2 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js +2 -1
- package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js +2 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js +2 -1
- package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js +2 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js +2 -1
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/page.js +2 -1
- package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js +2 -1
- package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/print/[key]/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/print/[key]/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/print/[key]/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/print/[key]/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/print/[key]/page.js +19 -0
- package/app/apps/web/.next/server/app/print/[key]/page.js.map +5 -0
- package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js +2 -1
- package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js +2 -1
- package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +2 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js +2 -1
- package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/settings/page.js +2 -1
- package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/team/page.js +2 -1
- package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js +2 -1
- package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0455vu3._.js +2 -2
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0_8zh-i._.js → [root-of-the-server]__04j3tng._.js} +2 -2
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0jprdks._.js → [root-of-the-server]__0cnh3k4._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0e.f-t.._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0kc9e1n._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0y5p73y._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0ej_p3s._.js → [root-of-the-server]__132eosx._.js} +2 -2
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_pdf_[key]_route_actions_0intcw9.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_workflow-agent_route_actions_0gxw9tv.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_entries_dist_node_0bckr7-.js +1 -1
- package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__04eq6z2._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__04il6m2._.js +11 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0n-ax-5._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0o03ymy._.js +2 -2
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0rvzdnx._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0z7mg0b._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~.f_3z._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__136-0me._.js +8 -0
- package/app/apps/web/.next/server/chunks/ssr/_0cgg5zi._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/_0jw2sm7._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/_0kajk31._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/_0w.cv64._.js +8 -0
- package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_0.mu7c7._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_print_[key]_page_actions_0_ygul-.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_01d-iwx._.js +6 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_week-digest_tsx_0c9ha31._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_0k0tl52._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07orc4m.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_12.endt._.js +1 -1
- package/app/apps/web/.next/server/edge/chunks/[root-of-the-server]__0.sx~id._.js +11 -0
- package/app/apps/web/.next/server/edge/chunks/node_modules_next_dist_esm_build_templates_edge-wrapper_0p1t1z8.js +3 -0
- package/app/apps/web/.next/server/edge/chunks/turbopack-node_modules_next_dist_esm_build_templates_edge-wrapper_0mw-sqe.js +3 -0
- package/app/apps/web/.next/server/functions-config-manifest.json +4 -1
- package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/app/apps/web/.next/server/middleware-manifest.json +30 -2
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/00xkqnvk4-d1-.js +10 -0
- package/app/apps/web/.next/static/chunks/03_7wmpodz36t.js +1 -0
- package/app/apps/web/.next/static/chunks/0716x58e1_2c5.js +1 -0
- package/app/apps/web/.next/static/chunks/0nnjldasalwdu.js +4 -0
- package/app/apps/web/.next/static/chunks/0okg13phslr1..js +4 -0
- package/app/apps/web/.next/static/chunks/0vf-3rauafbek.js +6 -0
- package/app/apps/web/.next/static/chunks/0vl27-vjob2_9.js +6 -0
- package/app/apps/web/.next/static/chunks/0yo458-7577lr.js +1 -0
- package/app/apps/web/.next/static/chunks/14bjditmmrn7l.js +1 -0
- package/app/apps/web/.next/static/rrY-ephAArv8tjJOXD622/_clientMiddlewareManifest.js +6 -0
- package/app/apps/web/app/api/events/route.ts +50 -2
- package/app/apps/web/app/api/pdf/[key]/route.ts +199 -0
- package/app/apps/web/app/api/workflow-agent/route.ts +27 -0
- package/app/apps/web/app/insights/[key]/page.tsx +4 -1
- package/app/apps/web/app/layout.tsx +23 -1
- package/app/apps/web/app/page.tsx +24 -1
- package/app/apps/web/app/print/[key]/page.tsx +91 -0
- package/app/apps/web/app/projects/[slug]/page.tsx +6 -7
- package/app/apps/web/app/projects/page.tsx +2 -3
- package/app/apps/web/app/sessions/[id]/session-view.tsx +1297 -19
- package/app/apps/web/app/sessions/sessions-grid.tsx +10 -1
- package/app/apps/web/components/dashboard-view.tsx +2 -1
- package/app/apps/web/components/export-pdf-button.tsx +76 -0
- package/app/apps/web/components/month-digest-view.tsx +10 -0
- package/app/apps/web/components/week-digest-view.tsx +10 -0
- package/app/apps/web/lib/changelog-data.generated.ts +1 -1
- package/app/apps/web/lib/entries-index.ts +5 -4
- package/app/apps/web/lib/find-chrome.ts +71 -0
- package/app/apps/web/middleware.ts +22 -0
- package/app/apps/web/package.json +1 -1
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/daemon-worker.js +1716 -281
- package/dist/index.js +1617 -195
- package/package.json +1 -1
- package/app/apps/web/.next/server/chunks/packages_parser_dist_04rh1zd._.js +0 -3
- package/app/apps/web/.next/server/chunks/packages_parser_dist_0ndsxe6._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0_xpuh7._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0d~2~le._.js +0 -8
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0w52fbc._.js +0 -12
- package/app/apps/web/.next/server/chunks/ssr/_02s1n-x._.js +0 -8
- package/app/apps/web/.next/server/chunks/ssr/_0fjbf--._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0ly1xes._.js +0 -6
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_0af94gl._.js +0 -3
- package/app/apps/web/.next/static/chunks/0.3mqbj24kwtc.js +0 -6
- package/app/apps/web/.next/static/chunks/0byz8fqw.exef.js +0 -1
- package/app/apps/web/.next/static/chunks/0l3ck.awv~4t7.js +0 -1
- package/app/apps/web/.next/static/chunks/0lxuac2v0_gdl.js +0 -6
- package/app/apps/web/.next/static/chunks/0m62vx92~0_0d.js +0 -1
- package/app/apps/web/.next/static/chunks/0rtba0saz74e1.js +0 -10
- package/app/apps/web/.next/static/chunks/0s4eyofh28l~u.js +0 -1
- package/app/apps/web/.next/static/chunks/0uev~g~qvbjv9.js +0 -4
- package/app/apps/web/.next/static/m6ibRSdAu8jD10VBA2OFH/_clientMiddlewareManifest.js +0 -1
- /package/app/apps/web/.next/static/{m6ibRSdAu8jD10VBA2OFH → rrY-ephAArv8tjJOXD622}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{m6ibRSdAu8jD10VBA2OFH → rrY-ephAArv8tjJOXD622}/_ssgManifest.js +0 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
Cpu,
|
|
13
13
|
Folder,
|
|
14
14
|
GitPullRequest,
|
|
15
|
+
Workflow as WorkflowIcon,
|
|
15
16
|
Wrench,
|
|
16
17
|
X,
|
|
17
18
|
} from "lucide-react";
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
SessionDetail,
|
|
23
24
|
SessionEvent,
|
|
24
25
|
SubagentRun,
|
|
26
|
+
WorkflowRun,
|
|
25
27
|
PrMarker,
|
|
26
28
|
} from "@claude-lens/parser";
|
|
27
29
|
import type { Entry } from "@claude-lens/entries";
|
|
@@ -93,9 +95,11 @@ const FILTER_MODES: { value: FilterMode; label: string }[] = [
|
|
|
93
95
|
|
|
94
96
|
/** Drawer width in px — reserved on the transcript's right edge when open. */
|
|
95
97
|
const DRAWER_WIDTH = 460;
|
|
98
|
+
/** Wider sheet for the workflow-agent step log — dense step rows need room. */
|
|
99
|
+
const WF_AGENT_DRAWER_WIDTH = 600;
|
|
96
100
|
|
|
97
|
-
type TabId = "transcript" | "team" | "debug";
|
|
98
|
-
const VALID_TABS: TabId[] = ["transcript", "team", "debug"];
|
|
101
|
+
type TabId = "transcript" | "workflows" | "team" | "debug";
|
|
102
|
+
const VALID_TABS: TabId[] = ["transcript", "workflows", "team", "debug"];
|
|
99
103
|
|
|
100
104
|
export function SessionView({
|
|
101
105
|
session,
|
|
@@ -152,6 +156,14 @@ export function SessionView({
|
|
|
152
156
|
const [filter, setFilter] = useState<FilterMode>("turns");
|
|
153
157
|
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
|
154
158
|
const [selectedSubagentId, setSelectedSubagentId] = useState<string | null>(null);
|
|
159
|
+
/** Workflow run to focus (auto-expand + scroll) on the Workflows tab —
|
|
160
|
+
* set when a workflow lane on the minimap is clicked. */
|
|
161
|
+
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | null>(null);
|
|
162
|
+
/** Workflow agent whose full step log is open in the right side-sheet. */
|
|
163
|
+
const [selectedWorkflowAgent, setSelectedWorkflowAgent] = useState<{
|
|
164
|
+
runId: string;
|
|
165
|
+
agent: WorkflowRun["agents"][number];
|
|
166
|
+
} | null>(null);
|
|
155
167
|
const [askOpen, setAskOpen] = useState(false);
|
|
156
168
|
const [expandedTurns, setExpandedTurns] = useState<Set<number>>(new Set());
|
|
157
169
|
/** When a timeline click sets the index we also want to scroll. Track that
|
|
@@ -159,6 +171,20 @@ export function SessionView({
|
|
|
159
171
|
const [scrollIntent, setScrollIntent] = useState(0);
|
|
160
172
|
const rowRefs = useRef<Record<number, HTMLDivElement | null>>({});
|
|
161
173
|
|
|
174
|
+
// The event/subagent drawers are transcript-scoped (position: fixed, shown
|
|
175
|
+
// whenever a selection exists). Clear the selection when leaving the
|
|
176
|
+
// transcript so a stale drawer doesn't overlay the Workflows/Team/Debug tabs.
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (tab !== "transcript") {
|
|
179
|
+
setSelectedIndex(null);
|
|
180
|
+
setSelectedSubagentId(null);
|
|
181
|
+
}
|
|
182
|
+
if (tab !== "workflows") {
|
|
183
|
+
setSelectedWorkflowId(null);
|
|
184
|
+
setSelectedWorkflowAgent(null);
|
|
185
|
+
}
|
|
186
|
+
}, [tab]);
|
|
187
|
+
|
|
162
188
|
/** Measured height of the sticky header — used both as the drawer's
|
|
163
189
|
* top offset (so it sits exactly below the header) and as the
|
|
164
190
|
* scroll-margin for transcript rows (so click-to-focus lands a row
|
|
@@ -265,6 +291,8 @@ export function SessionView({
|
|
|
265
291
|
|
|
266
292
|
const { events, durationMs, totalUsage, model, eventCount, projectName } = session;
|
|
267
293
|
const airTimeMs = session.airTimeMs ?? durationMs;
|
|
294
|
+
const workflowCount = session.workflows?.length ?? 0;
|
|
295
|
+
const hasWorkflows = workflowCount > 0;
|
|
268
296
|
|
|
269
297
|
/** Inbound `<teammate-message>` events are cross-session team traffic
|
|
270
298
|
* wrapped in a synthetic user event. On the LEAD's transcript these are
|
|
@@ -337,13 +365,23 @@ export function SessionView({
|
|
|
337
365
|
anchors.sort((a, b) => a.ms - b.ms);
|
|
338
366
|
const sessionStartMs = anchors[0]!.ms - (events.find((e) => e.timestamp)?.tOffsetMs ?? 0);
|
|
339
367
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
368
|
+
// "Busy" spans = delegated agent work that means the parent isn't idle:
|
|
369
|
+
// subagent runs AND dynamic-workflow execution. Both are carved out of the
|
|
370
|
+
// idle bands so a long Workflow run (during which the parent just waits)
|
|
371
|
+
// reads as active agent time, not dead air. A still-running workflow has
|
|
372
|
+
// no end offset yet — extend it to the last anchor.
|
|
373
|
+
const lastAnchorOff = anchors[anchors.length - 1]!.ms - sessionStartMs;
|
|
374
|
+
const busySpans = [
|
|
375
|
+
...(session.subagents ?? [])
|
|
376
|
+
.filter((s) => s.startTOffsetMs !== undefined && s.endTOffsetMs !== undefined)
|
|
377
|
+
.map((s) => ({ start: s.startTOffsetMs as number, end: s.endTOffsetMs as number })),
|
|
378
|
+
...(session.workflows ?? [])
|
|
379
|
+
.filter((w) => w.startTOffsetMs !== undefined)
|
|
380
|
+
.map((w) => ({
|
|
381
|
+
start: w.startTOffsetMs as number,
|
|
382
|
+
end: (w.endTOffsetMs ?? lastAnchorOff) as number,
|
|
383
|
+
})),
|
|
384
|
+
].sort((a, b) => a.start - b.start);
|
|
347
385
|
|
|
348
386
|
/** Subtract subagent spans from [start, end] in offset coordinates,
|
|
349
387
|
* returning 0+ remaining slices in time order. */
|
|
@@ -352,7 +390,7 @@ export function SessionView({
|
|
|
352
390
|
end: number,
|
|
353
391
|
): { start: number; end: number }[] => {
|
|
354
392
|
let pieces: { start: number; end: number }[] = [{ start, end }];
|
|
355
|
-
for (const span of
|
|
393
|
+
for (const span of busySpans) {
|
|
356
394
|
if (span.end <= start) continue;
|
|
357
395
|
if (span.start >= end) break;
|
|
358
396
|
const next: { start: number; end: number }[] = [];
|
|
@@ -370,9 +408,9 @@ export function SessionView({
|
|
|
370
408
|
};
|
|
371
409
|
|
|
372
410
|
const overlapsSubagent = (start: number, end: number): boolean => {
|
|
373
|
-
for (const span of
|
|
411
|
+
for (const span of busySpans) {
|
|
374
412
|
if (span.end <= start) continue;
|
|
375
|
-
//
|
|
413
|
+
// busySpans is sorted by start, so once span.start >= end no
|
|
376
414
|
// later span can overlap [start, end] either — bail out cleanly.
|
|
377
415
|
if (span.start >= end) return false;
|
|
378
416
|
return true;
|
|
@@ -433,7 +471,7 @@ export function SessionView({
|
|
|
433
471
|
}
|
|
434
472
|
}
|
|
435
473
|
return bands;
|
|
436
|
-
}, [events, session.subagents]);
|
|
474
|
+
}, [events, session.subagents, session.workflows]);
|
|
437
475
|
|
|
438
476
|
const coldResumeMarkers = useMemo(() => {
|
|
439
477
|
const seen = new Set<string>();
|
|
@@ -530,6 +568,23 @@ export function SessionView({
|
|
|
530
568
|
setScrollIntent((n) => n + 1);
|
|
531
569
|
}
|
|
532
570
|
|
|
571
|
+
/** Scroll the transcript to the row whose assistant message issued a given
|
|
572
|
+
* tool_use id. Used by workflow cards / lanes to jump to the originating
|
|
573
|
+
* Workflow tool call. */
|
|
574
|
+
function jumpToToolUse(toolUseId?: string) {
|
|
575
|
+
if (!toolUseId) return;
|
|
576
|
+
const ev = events.find((e) =>
|
|
577
|
+
e.blocks.some((b) => b?.type === "tool_use" && b.id === toolUseId),
|
|
578
|
+
);
|
|
579
|
+
if (ev) {
|
|
580
|
+
setSelectedSubagentId(null);
|
|
581
|
+
// The Workflow row lives in the transcript — switch there first (e.g.
|
|
582
|
+
// when jumping from the Workflows tab) so the scroll target exists.
|
|
583
|
+
if (tab !== "transcript") setTab("transcript");
|
|
584
|
+
scrollToIndex(ev.index);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
533
588
|
const totalInput = totalUsage.input + totalUsage.cacheRead + totalUsage.cacheWrite;
|
|
534
589
|
|
|
535
590
|
return (
|
|
@@ -604,6 +659,28 @@ export function SessionView({
|
|
|
604
659
|
>
|
|
605
660
|
Transcript
|
|
606
661
|
</button>
|
|
662
|
+
{hasWorkflows && (
|
|
663
|
+
<button
|
|
664
|
+
className={`af-tab-btn ${tab === "workflows" ? "active" : ""}`}
|
|
665
|
+
onClick={() => setTab("workflows")}
|
|
666
|
+
>
|
|
667
|
+
Workflows
|
|
668
|
+
<span
|
|
669
|
+
style={{
|
|
670
|
+
marginLeft: 6,
|
|
671
|
+
fontSize: 10,
|
|
672
|
+
fontWeight: 700,
|
|
673
|
+
padding: "1px 6px",
|
|
674
|
+
borderRadius: 100,
|
|
675
|
+
background: "rgba(234,88,12,0.16)",
|
|
676
|
+
color: "#EA580C",
|
|
677
|
+
fontFamily: "var(--font-mono)",
|
|
678
|
+
}}
|
|
679
|
+
>
|
|
680
|
+
{workflowCount}
|
|
681
|
+
</span>
|
|
682
|
+
</button>
|
|
683
|
+
)}
|
|
607
684
|
{team && (
|
|
608
685
|
<button
|
|
609
686
|
className={`af-tab-btn ${tab === "team" ? "active" : ""}`}
|
|
@@ -725,6 +802,15 @@ export function SessionView({
|
|
|
725
802
|
)}
|
|
726
803
|
<InlineStatDivider />
|
|
727
804
|
<InlineStat value={`${eventCount} events`} />
|
|
805
|
+
{(session.workflowCount ?? 0) > 0 && (
|
|
806
|
+
<>
|
|
807
|
+
<InlineStatDivider />
|
|
808
|
+
<InlineStat
|
|
809
|
+
icon={<WorkflowIcon size={12} />}
|
|
810
|
+
value={`${session.workflowCount} workflow${session.workflowCount === 1 ? "" : "s"} · ${(session.spawnedAgentCount ?? 0).toLocaleString()} agents`}
|
|
811
|
+
/>
|
|
812
|
+
</>
|
|
813
|
+
)}
|
|
728
814
|
{prMarkers.length > 0 && (
|
|
729
815
|
<>
|
|
730
816
|
<InlineStatDivider />
|
|
@@ -855,6 +941,11 @@ export function SessionView({
|
|
|
855
941
|
onSelect={scrollToIndex}
|
|
856
942
|
headerOffset={headerH}
|
|
857
943
|
subagents={session.subagents}
|
|
944
|
+
workflows={session.workflows}
|
|
945
|
+
onWorkflowClick={(runId) => {
|
|
946
|
+
setSelectedWorkflowId(runId);
|
|
947
|
+
setTab("workflows");
|
|
948
|
+
}}
|
|
858
949
|
collapsed={collapsed}
|
|
859
950
|
prMarkers={prMarkers}
|
|
860
951
|
coldResumeMarkers={coldResumeMarkers}
|
|
@@ -892,7 +983,30 @@ export function SessionView({
|
|
|
892
983
|
onVisibleTrackIdsChange={setTeamVisibleTrackIds}
|
|
893
984
|
seekTarget={teamSeekTarget}
|
|
894
985
|
/>
|
|
895
|
-
) : tab === "
|
|
986
|
+
) : tab === "workflows" && hasWorkflows ? (
|
|
987
|
+
<div style={{ paddingBottom: 24 }}>
|
|
988
|
+
<WorkflowsPanel
|
|
989
|
+
workflows={session.workflows!}
|
|
990
|
+
spawnedAgentCount={session.spawnedAgentCount ?? 0}
|
|
991
|
+
focusRunId={selectedWorkflowId}
|
|
992
|
+
selectedAgentKey={
|
|
993
|
+
selectedWorkflowAgent
|
|
994
|
+
? `${selectedWorkflowAgent.runId}:${selectedWorkflowAgent.agent.agentId ?? selectedWorkflowAgent.agent.index}`
|
|
995
|
+
: null
|
|
996
|
+
}
|
|
997
|
+
onOpenAgent={(runId, agent) =>
|
|
998
|
+
setSelectedWorkflowAgent((cur) =>
|
|
999
|
+
cur && cur.runId === runId && cur.agent.index === agent.index
|
|
1000
|
+
? null
|
|
1001
|
+
: { runId, agent },
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
1004
|
+
onJumpToParent={jumpToToolUse}
|
|
1005
|
+
/>
|
|
1006
|
+
</div>
|
|
1007
|
+
) : tab === "debug" ? (
|
|
1008
|
+
<DebugList events={events} />
|
|
1009
|
+
) : (
|
|
896
1010
|
<>
|
|
897
1011
|
{entries.length > 0 && (
|
|
898
1012
|
<div style={{ display: "flex", flexDirection: "column", gap: 10, marginBottom: 14 }}>
|
|
@@ -943,8 +1057,6 @@ export function SessionView({
|
|
|
943
1057
|
rawIdleBands={rawIdleBands}
|
|
944
1058
|
/>
|
|
945
1059
|
</>
|
|
946
|
-
) : (
|
|
947
|
-
<DebugList events={events} />
|
|
948
1060
|
)}
|
|
949
1061
|
</div>
|
|
950
1062
|
|
|
@@ -1021,6 +1133,33 @@ export function SessionView({
|
|
|
1021
1133
|
);
|
|
1022
1134
|
})()}
|
|
1023
1135
|
|
|
1136
|
+
{/* Workflow-agent step log — opens as a right side-sheet so the full
|
|
1137
|
+
(often 100+ step) transcript doesn't make the Workflows tab huge. */}
|
|
1138
|
+
{selectedWorkflowAgent && (
|
|
1139
|
+
<aside
|
|
1140
|
+
style={{
|
|
1141
|
+
position: "fixed",
|
|
1142
|
+
top: headerH,
|
|
1143
|
+
right: 0,
|
|
1144
|
+
bottom: 0,
|
|
1145
|
+
width: WF_AGENT_DRAWER_WIDTH,
|
|
1146
|
+
maxWidth: "100vw",
|
|
1147
|
+
borderLeft: "1px solid var(--af-border-subtle)",
|
|
1148
|
+
background: "var(--af-surface)",
|
|
1149
|
+
overflowY: "auto",
|
|
1150
|
+
zIndex: 28,
|
|
1151
|
+
boxShadow: "-8px 0 24px rgba(15, 23, 42, 0.1)",
|
|
1152
|
+
}}
|
|
1153
|
+
>
|
|
1154
|
+
<WorkflowAgentDrawer
|
|
1155
|
+
sessionId={session.id}
|
|
1156
|
+
runId={selectedWorkflowAgent.runId}
|
|
1157
|
+
agent={selectedWorkflowAgent.agent}
|
|
1158
|
+
onClose={() => setSelectedWorkflowAgent(null)}
|
|
1159
|
+
/>
|
|
1160
|
+
</aside>
|
|
1161
|
+
)}
|
|
1162
|
+
|
|
1024
1163
|
{/* Ask drawer */}
|
|
1025
1164
|
{askOpen && (
|
|
1026
1165
|
<aside
|
|
@@ -1458,6 +1597,8 @@ function Minimap({
|
|
|
1458
1597
|
onSelect,
|
|
1459
1598
|
headerOffset,
|
|
1460
1599
|
subagents,
|
|
1600
|
+
workflows,
|
|
1601
|
+
onWorkflowClick,
|
|
1461
1602
|
collapsed,
|
|
1462
1603
|
selectedSubagentId,
|
|
1463
1604
|
onSelectSubagent,
|
|
@@ -1472,6 +1613,8 @@ function Minimap({
|
|
|
1472
1613
|
onSelect: (i: number) => void;
|
|
1473
1614
|
headerOffset: number;
|
|
1474
1615
|
subagents?: SubagentRun[];
|
|
1616
|
+
workflows?: WorkflowRun[];
|
|
1617
|
+
onWorkflowClick?: (runId: string) => void;
|
|
1475
1618
|
collapsed?: boolean;
|
|
1476
1619
|
selectedSubagentId?: string | null;
|
|
1477
1620
|
onSelectSubagent?: (id: string | null) => void;
|
|
@@ -1494,6 +1637,9 @@ function Minimap({
|
|
|
1494
1637
|
const SUB_LANE_H = 11;
|
|
1495
1638
|
/** Gap between the main timeline and the sub-agent lanes (when present). */
|
|
1496
1639
|
const SUB_LANE_GAP = 6;
|
|
1640
|
+
/** Per-workflow lane height — a touch taller than subagent lanes so the
|
|
1641
|
+
* workflow tier reads as distinct. */
|
|
1642
|
+
const WF_LANE_H = 13;
|
|
1497
1643
|
const BAR_TOP = 3;
|
|
1498
1644
|
const BAR_BOT = MAIN_H - 3;
|
|
1499
1645
|
const BAR_H = BAR_BOT - BAR_TOP;
|
|
@@ -1513,6 +1659,7 @@ function Minimap({
|
|
|
1513
1659
|
turn?: TurnMegaRow;
|
|
1514
1660
|
idleMs?: number;
|
|
1515
1661
|
subagent?: SubagentRun;
|
|
1662
|
+
workflow?: WorkflowRun;
|
|
1516
1663
|
pr?: PrMarker;
|
|
1517
1664
|
cold?: {
|
|
1518
1665
|
tOffsetMs: number;
|
|
@@ -1827,7 +1974,50 @@ function Minimap({
|
|
|
1827
1974
|
|
|
1828
1975
|
const SUB_BLOCK_TOP = MAIN_H + SUB_LANE_GAP;
|
|
1829
1976
|
const SUB_LANES_H = laneCount > 0 ? laneCount * SUB_LANE_H : 0;
|
|
1830
|
-
|
|
1977
|
+
|
|
1978
|
+
// ---- Workflow lane assignment --------------------------------------
|
|
1979
|
+
// Same greedy sweep as subagents. Workflow runs are mostly sequential
|
|
1980
|
+
// milestones, so this usually collapses to one lane — but overlap is
|
|
1981
|
+
// handled if two runs ever ran concurrently.
|
|
1982
|
+
const { wfLaneOf, wfLaneCount } = useMemo(() => {
|
|
1983
|
+
// Only place workflows we can actually draw (startTOffsetMs known). A
|
|
1984
|
+
// running run has no end yet — it extends to the session end (safeDur),
|
|
1985
|
+
// matching the open-ended bar the render draws. Counting only placeable
|
|
1986
|
+
// runs keeps wfLaneCount in lockstep with what renders, so no empty
|
|
1987
|
+
// reserved band appears.
|
|
1988
|
+
const placeable = (workflows ?? []).filter((w) => w.startTOffsetMs !== undefined);
|
|
1989
|
+
if (placeable.length === 0) {
|
|
1990
|
+
return { wfLaneOf: new Map<string, number>(), wfLaneCount: 0 };
|
|
1991
|
+
}
|
|
1992
|
+
const sorted = [...placeable].sort(
|
|
1993
|
+
(a, b) => (a.startTOffsetMs ?? 0) - (b.startTOffsetMs ?? 0),
|
|
1994
|
+
);
|
|
1995
|
+
const laneEnds: number[] = [];
|
|
1996
|
+
const wfLaneOf = new Map<string, number>();
|
|
1997
|
+
for (const w of sorted) {
|
|
1998
|
+
const start = w.startTOffsetMs ?? 0;
|
|
1999
|
+
const end = w.endTOffsetMs ?? safeDur;
|
|
2000
|
+
let assigned = -1;
|
|
2001
|
+
for (let i = 0; i < laneEnds.length; i++) {
|
|
2002
|
+
if (laneEnds[i]! <= start) {
|
|
2003
|
+
assigned = i;
|
|
2004
|
+
break;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
if (assigned === -1) {
|
|
2008
|
+
assigned = laneEnds.length;
|
|
2009
|
+
laneEnds.push(0);
|
|
2010
|
+
}
|
|
2011
|
+
laneEnds[assigned] = Math.max(end, start + 1);
|
|
2012
|
+
wfLaneOf.set(w.runId, assigned);
|
|
2013
|
+
}
|
|
2014
|
+
return { wfLaneOf, wfLaneCount: laneEnds.length };
|
|
2015
|
+
}, [workflows, safeDur]);
|
|
2016
|
+
|
|
2017
|
+
const SUB_BLOCK_BOTTOM = MAIN_H + (laneCount > 0 ? SUB_LANE_GAP + SUB_LANES_H : 0);
|
|
2018
|
+
const WF_BLOCK_TOP = SUB_BLOCK_BOTTOM + (wfLaneCount > 0 ? SUB_LANE_GAP : 0);
|
|
2019
|
+
const WF_LANES_H = wfLaneCount > 0 ? wfLaneCount * WF_LANE_H : 0;
|
|
2020
|
+
const TOTAL_H = SUB_BLOCK_BOTTOM + (wfLaneCount > 0 ? SUB_LANE_GAP + WF_LANES_H : 0);
|
|
1831
2021
|
|
|
1832
2022
|
return (
|
|
1833
2023
|
<div
|
|
@@ -2023,6 +2213,73 @@ function Minimap({
|
|
|
2023
2213
|
</g>
|
|
2024
2214
|
)}
|
|
2025
2215
|
|
|
2216
|
+
{/* Workflow lanes — one bar per dynamic-workflow run, on the same
|
|
2217
|
+
time axis as the main timeline, colored by status (orange tier,
|
|
2218
|
+
distinct from the blue/purple subagents). The agent count is the
|
|
2219
|
+
headline — a single Workflow tool call hides a whole fleet. */}
|
|
2220
|
+
{wfLaneCount > 0 && workflows && (
|
|
2221
|
+
<g>
|
|
2222
|
+
<line
|
|
2223
|
+
x1={0}
|
|
2224
|
+
x2={WIDTH}
|
|
2225
|
+
y1={WF_BLOCK_TOP - SUB_LANE_GAP / 2}
|
|
2226
|
+
y2={WF_BLOCK_TOP - SUB_LANE_GAP / 2}
|
|
2227
|
+
stroke="var(--af-border-subtle)"
|
|
2228
|
+
strokeWidth="0.6"
|
|
2229
|
+
strokeDasharray="2 4"
|
|
2230
|
+
/>
|
|
2231
|
+
{workflows.map((w) => {
|
|
2232
|
+
if (w.startTOffsetMs === undefined) return null;
|
|
2233
|
+
const lane = wfLaneOf.get(w.runId) ?? 0;
|
|
2234
|
+
const startX = msToX(w.startTOffsetMs);
|
|
2235
|
+
// A still-running run has no endTOffsetMs — draw it open-ended to
|
|
2236
|
+
// the session end so it's visible (and dashed, to read as ongoing)
|
|
2237
|
+
// rather than vanishing while still occupying a reserved lane.
|
|
2238
|
+
const inProgress = w.endTOffsetMs === undefined;
|
|
2239
|
+
const endX = msToX(w.endTOffsetMs ?? safeDur);
|
|
2240
|
+
const wWidth = Math.max(endX - startX, 8);
|
|
2241
|
+
const y = WF_BLOCK_TOP + lane * WF_LANE_H;
|
|
2242
|
+
const h = WF_LANE_H - 2;
|
|
2243
|
+
const fill = workflowColor(w.status);
|
|
2244
|
+
// Center the agent count label inside the bar when there's room.
|
|
2245
|
+
const label = `${w.agentCount}`;
|
|
2246
|
+
const showLabel = wWidth > 22;
|
|
2247
|
+
return (
|
|
2248
|
+
<g key={w.runId} style={{ cursor: "pointer" }}>
|
|
2249
|
+
<rect
|
|
2250
|
+
x={startX}
|
|
2251
|
+
y={y}
|
|
2252
|
+
width={wWidth}
|
|
2253
|
+
height={h}
|
|
2254
|
+
fill={fill}
|
|
2255
|
+
fillOpacity={inProgress ? 0.55 : 1}
|
|
2256
|
+
stroke={inProgress ? fill : "transparent"}
|
|
2257
|
+
strokeWidth={inProgress ? 0.8 : 0}
|
|
2258
|
+
strokeDasharray={inProgress ? "2 2" : undefined}
|
|
2259
|
+
rx="2"
|
|
2260
|
+
onMouseEnter={(e) => setHover({ clientX: e.clientX, workflow: w })}
|
|
2261
|
+
onClick={() => onWorkflowClick?.(w.runId)}
|
|
2262
|
+
/>
|
|
2263
|
+
{showLabel && (
|
|
2264
|
+
<text
|
|
2265
|
+
x={startX + wWidth / 2}
|
|
2266
|
+
y={y + h / 2 + 3}
|
|
2267
|
+
textAnchor="middle"
|
|
2268
|
+
fontSize="8.5"
|
|
2269
|
+
fontWeight="700"
|
|
2270
|
+
fill="#fff"
|
|
2271
|
+
pointerEvents="none"
|
|
2272
|
+
style={{ fontFamily: "var(--font-mono)" }}
|
|
2273
|
+
>
|
|
2274
|
+
{label}
|
|
2275
|
+
</text>
|
|
2276
|
+
)}
|
|
2277
|
+
</g>
|
|
2278
|
+
);
|
|
2279
|
+
})}
|
|
2280
|
+
</g>
|
|
2281
|
+
)}
|
|
2282
|
+
|
|
2026
2283
|
{/* PR markers — diamond + vertical line at each `gh pr create` */}
|
|
2027
2284
|
{prMarkers?.map((pr, i) => {
|
|
2028
2285
|
if (pr.tOffsetMs === undefined) return null;
|
|
@@ -2123,6 +2380,7 @@ function Minimap({
|
|
|
2123
2380
|
turn={hover.turn}
|
|
2124
2381
|
idleMs={hover.idleMs}
|
|
2125
2382
|
subagent={hover.subagent}
|
|
2383
|
+
workflow={hover.workflow}
|
|
2126
2384
|
pr={hover.pr}
|
|
2127
2385
|
cold={hover.cold}
|
|
2128
2386
|
model={model}
|
|
@@ -2205,6 +2463,17 @@ function subagentColor(agentType: string, background?: boolean): string {
|
|
|
2205
2463
|
return background ? bright : base;
|
|
2206
2464
|
}
|
|
2207
2465
|
|
|
2466
|
+
/** Workflow signature color, modulated by run status. Deliberately a warm
|
|
2467
|
+
* orange — distinct from the blue/purple subagent palette so workflow lanes
|
|
2468
|
+
* read as a different tier on the minimap. Failed runs go red, in-flight
|
|
2469
|
+
* runs go green. */
|
|
2470
|
+
function workflowColor(status: string): string {
|
|
2471
|
+
const s = status.toLowerCase();
|
|
2472
|
+
if (s === "failed" || s === "aborted" || s === "error") return "#EF4444";
|
|
2473
|
+
if (s === "running" || s === "in_progress" || s === "active") return "#10B981";
|
|
2474
|
+
return "#EA580C";
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2208
2477
|
function MinimapHoverCard({
|
|
2209
2478
|
containerRef,
|
|
2210
2479
|
clientX,
|
|
@@ -2212,6 +2481,7 @@ function MinimapHoverCard({
|
|
|
2212
2481
|
turn,
|
|
2213
2482
|
idleMs,
|
|
2214
2483
|
subagent,
|
|
2484
|
+
workflow,
|
|
2215
2485
|
pr,
|
|
2216
2486
|
cold,
|
|
2217
2487
|
model,
|
|
@@ -2222,6 +2492,7 @@ function MinimapHoverCard({
|
|
|
2222
2492
|
turn?: TurnMegaRow;
|
|
2223
2493
|
idleMs?: number;
|
|
2224
2494
|
subagent?: SubagentRun;
|
|
2495
|
+
workflow?: WorkflowRun;
|
|
2225
2496
|
pr?: PrMarker;
|
|
2226
2497
|
cold?: { tOffsetMs: number; info: NonNullable<SessionEvent["coldResume"]> };
|
|
2227
2498
|
model?: string;
|
|
@@ -2233,6 +2504,92 @@ function MinimapHoverCard({
|
|
|
2233
2504
|
// header, so there's never reliable space above it for a tooltip.
|
|
2234
2505
|
const posStyle = { top: "calc(100% + 8px)", left };
|
|
2235
2506
|
|
|
2507
|
+
if (workflow) {
|
|
2508
|
+
const w = workflow;
|
|
2509
|
+
const startOff = formatOffset(w.startTOffsetMs);
|
|
2510
|
+
const endOff = formatOffset(w.endTOffsetMs);
|
|
2511
|
+
const dur = w.durationMs !== undefined ? formatGap(w.durationMs) : "";
|
|
2512
|
+
return (
|
|
2513
|
+
<div
|
|
2514
|
+
style={{
|
|
2515
|
+
position: "absolute",
|
|
2516
|
+
...posStyle,
|
|
2517
|
+
zIndex: 100,
|
|
2518
|
+
background: "#0F172A",
|
|
2519
|
+
color: "#F1F5F9",
|
|
2520
|
+
borderRadius: 10,
|
|
2521
|
+
padding: "12px 14px",
|
|
2522
|
+
fontSize: 11,
|
|
2523
|
+
pointerEvents: "none",
|
|
2524
|
+
boxShadow: "0 6px 24px rgba(15,23,42,0.22)",
|
|
2525
|
+
maxWidth: 440,
|
|
2526
|
+
minWidth: 280,
|
|
2527
|
+
lineHeight: 1.45,
|
|
2528
|
+
}}
|
|
2529
|
+
>
|
|
2530
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
|
|
2531
|
+
<span
|
|
2532
|
+
style={{
|
|
2533
|
+
fontSize: 10,
|
|
2534
|
+
fontWeight: 600,
|
|
2535
|
+
padding: "2px 8px",
|
|
2536
|
+
borderRadius: 4,
|
|
2537
|
+
background: workflowColor(w.status),
|
|
2538
|
+
color: "#fff",
|
|
2539
|
+
textTransform: "uppercase",
|
|
2540
|
+
letterSpacing: "0.04em",
|
|
2541
|
+
}}
|
|
2542
|
+
>
|
|
2543
|
+
workflow · {w.status}
|
|
2544
|
+
</span>
|
|
2545
|
+
<span
|
|
2546
|
+
style={{
|
|
2547
|
+
fontFamily: "var(--font-mono)",
|
|
2548
|
+
fontSize: 10,
|
|
2549
|
+
opacity: 0.7,
|
|
2550
|
+
marginLeft: "auto",
|
|
2551
|
+
}}
|
|
2552
|
+
>
|
|
2553
|
+
{startOff} → {endOff} · {dur}
|
|
2554
|
+
</span>
|
|
2555
|
+
</div>
|
|
2556
|
+
<div style={{ fontWeight: 600, marginBottom: 4, fontFamily: "var(--font-mono)" }}>
|
|
2557
|
+
{w.name}
|
|
2558
|
+
</div>
|
|
2559
|
+
{w.description && (
|
|
2560
|
+
<div style={{ opacity: 0.8, marginBottom: 6 }}>{w.description}</div>
|
|
2561
|
+
)}
|
|
2562
|
+
<div
|
|
2563
|
+
style={{
|
|
2564
|
+
fontSize: 10,
|
|
2565
|
+
opacity: 0.75,
|
|
2566
|
+
display: "flex",
|
|
2567
|
+
gap: 8,
|
|
2568
|
+
flexWrap: "wrap",
|
|
2569
|
+
paddingTop: 6,
|
|
2570
|
+
borderTop: "1px solid rgba(241,245,249,0.08)",
|
|
2571
|
+
fontFamily: "var(--font-mono)",
|
|
2572
|
+
}}
|
|
2573
|
+
>
|
|
2574
|
+
<span style={{ color: "#FB923C", fontWeight: 600 }}>{w.agentCount} agents</span>
|
|
2575
|
+
<span>·</span>
|
|
2576
|
+
<span>{w.toolCallCount.toLocaleString()} tools</span>
|
|
2577
|
+
<span>·</span>
|
|
2578
|
+
<span>{formatTokens(w.totalTokens)} tok</span>
|
|
2579
|
+
{w.phases.length > 0 && (
|
|
2580
|
+
<>
|
|
2581
|
+
<span>·</span>
|
|
2582
|
+
<span>{w.phases.length} phases</span>
|
|
2583
|
+
</>
|
|
2584
|
+
)}
|
|
2585
|
+
</div>
|
|
2586
|
+
<div style={{ marginTop: 6, fontSize: 10, opacity: 0.55, fontStyle: "italic" }}>
|
|
2587
|
+
Click to open this run in the Workflows tab.
|
|
2588
|
+
</div>
|
|
2589
|
+
</div>
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2236
2593
|
if (subagent) {
|
|
2237
2594
|
const startOff = formatOffset(subagent.startTOffsetMs);
|
|
2238
2595
|
const endOff = formatOffset(subagent.endTOffsetMs);
|
|
@@ -4466,6 +4823,924 @@ function SubagentDrawer({
|
|
|
4466
4823
|
);
|
|
4467
4824
|
}
|
|
4468
4825
|
|
|
4826
|
+
/* ------------------------------------------------------------------ */
|
|
4827
|
+
/* Workflows panel */
|
|
4828
|
+
/* */
|
|
4829
|
+
/* A single `Workflow` tool call collapses a whole dynamic-workflow */
|
|
4830
|
+
/* fan-out into one transcript row. This panel surfaces what that row */
|
|
4831
|
+
/* actually did: each run's spawned-agent count, tool calls, tokens, */
|
|
4832
|
+
/* phases, and progress log — the fleet work the transcript hides. */
|
|
4833
|
+
/* ------------------------------------------------------------------ */
|
|
4834
|
+
|
|
4835
|
+
function WorkflowsPanel({
|
|
4836
|
+
workflows,
|
|
4837
|
+
spawnedAgentCount,
|
|
4838
|
+
focusRunId,
|
|
4839
|
+
selectedAgentKey,
|
|
4840
|
+
onOpenAgent,
|
|
4841
|
+
onJumpToParent,
|
|
4842
|
+
}: {
|
|
4843
|
+
workflows: WorkflowRun[];
|
|
4844
|
+
spawnedAgentCount: number;
|
|
4845
|
+
focusRunId?: string | null;
|
|
4846
|
+
selectedAgentKey?: string | null;
|
|
4847
|
+
onOpenAgent: (runId: string, agent: WorkflowRun["agents"][number]) => void;
|
|
4848
|
+
onJumpToParent: (toolUseId?: string) => void;
|
|
4849
|
+
}) {
|
|
4850
|
+
const totalTokens = workflows.reduce((n, w) => n + w.totalTokens, 0);
|
|
4851
|
+
const totalTools = workflows.reduce((n, w) => n + w.toolCallCount, 0);
|
|
4852
|
+
return (
|
|
4853
|
+
<section
|
|
4854
|
+
style={{
|
|
4855
|
+
marginBottom: 16,
|
|
4856
|
+
border: "1px solid var(--af-border-subtle)",
|
|
4857
|
+
borderRadius: 10,
|
|
4858
|
+
background: "var(--af-surface)",
|
|
4859
|
+
overflow: "hidden",
|
|
4860
|
+
}}
|
|
4861
|
+
>
|
|
4862
|
+
<div
|
|
4863
|
+
style={{
|
|
4864
|
+
display: "flex",
|
|
4865
|
+
alignItems: "center",
|
|
4866
|
+
gap: 10,
|
|
4867
|
+
flexWrap: "wrap",
|
|
4868
|
+
padding: "10px 14px",
|
|
4869
|
+
borderBottom: "1px solid var(--af-border-subtle)",
|
|
4870
|
+
background: "var(--af-surface-subtle, rgba(234,88,12,0.04))",
|
|
4871
|
+
}}
|
|
4872
|
+
>
|
|
4873
|
+
<span
|
|
4874
|
+
style={{
|
|
4875
|
+
display: "inline-flex",
|
|
4876
|
+
alignItems: "center",
|
|
4877
|
+
gap: 7,
|
|
4878
|
+
fontSize: 13,
|
|
4879
|
+
fontWeight: 600,
|
|
4880
|
+
color: "var(--af-text)",
|
|
4881
|
+
}}
|
|
4882
|
+
>
|
|
4883
|
+
<WorkflowIcon size={14} style={{ color: "#EA580C" }} />
|
|
4884
|
+
Workflows
|
|
4885
|
+
</span>
|
|
4886
|
+
<span
|
|
4887
|
+
style={{
|
|
4888
|
+
fontSize: 11.5,
|
|
4889
|
+
color: "var(--af-text-tertiary)",
|
|
4890
|
+
fontFamily: "var(--font-mono)",
|
|
4891
|
+
}}
|
|
4892
|
+
>
|
|
4893
|
+
{workflows.length} run{workflows.length === 1 ? "" : "s"} ·{" "}
|
|
4894
|
+
{spawnedAgentCount.toLocaleString()} agents · {totalTools.toLocaleString()} tool calls ·{" "}
|
|
4895
|
+
{formatTokens(totalTokens)} tok orchestrated
|
|
4896
|
+
</span>
|
|
4897
|
+
</div>
|
|
4898
|
+
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
4899
|
+
{workflows.map((w) => (
|
|
4900
|
+
<WorkflowCard
|
|
4901
|
+
key={w.runId}
|
|
4902
|
+
w={w}
|
|
4903
|
+
focused={focusRunId === w.runId}
|
|
4904
|
+
selectedAgentKey={selectedAgentKey}
|
|
4905
|
+
onOpenAgent={onOpenAgent}
|
|
4906
|
+
onJumpToParent={onJumpToParent}
|
|
4907
|
+
/>
|
|
4908
|
+
))}
|
|
4909
|
+
</div>
|
|
4910
|
+
</section>
|
|
4911
|
+
);
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
function WorkflowCard({
|
|
4915
|
+
w,
|
|
4916
|
+
focused = false,
|
|
4917
|
+
selectedAgentKey,
|
|
4918
|
+
onOpenAgent,
|
|
4919
|
+
onJumpToParent,
|
|
4920
|
+
}: {
|
|
4921
|
+
w: WorkflowRun;
|
|
4922
|
+
focused?: boolean;
|
|
4923
|
+
selectedAgentKey?: string | null;
|
|
4924
|
+
onOpenAgent: (runId: string, agent: WorkflowRun["agents"][number]) => void;
|
|
4925
|
+
onJumpToParent: (toolUseId?: string) => void;
|
|
4926
|
+
}) {
|
|
4927
|
+
const [open, setOpen] = useState(focused);
|
|
4928
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
4929
|
+
// Focused via a minimap lane click — expand and scroll this run into view.
|
|
4930
|
+
useEffect(() => {
|
|
4931
|
+
if (focused) {
|
|
4932
|
+
setOpen(true);
|
|
4933
|
+
cardRef.current?.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
4934
|
+
}
|
|
4935
|
+
}, [focused]);
|
|
4936
|
+
const color = workflowColor(w.status);
|
|
4937
|
+
const dur = w.durationMs !== undefined ? formatGap(w.durationMs) : "—";
|
|
4938
|
+
const startOff = formatOffset(w.startTOffsetMs);
|
|
4939
|
+
|
|
4940
|
+
return (
|
|
4941
|
+
<div
|
|
4942
|
+
ref={cardRef}
|
|
4943
|
+
style={{
|
|
4944
|
+
borderTop: "1px solid var(--af-border-subtle)",
|
|
4945
|
+
background: focused ? "rgba(234,88,12,0.07)" : "transparent",
|
|
4946
|
+
transition: "background 0.5s ease",
|
|
4947
|
+
scrollMarginTop: 120,
|
|
4948
|
+
}}
|
|
4949
|
+
>
|
|
4950
|
+
{/* Collapsed header — click to expand */}
|
|
4951
|
+
<button
|
|
4952
|
+
onClick={() => setOpen((v) => !v)}
|
|
4953
|
+
style={{
|
|
4954
|
+
width: "100%",
|
|
4955
|
+
display: "flex",
|
|
4956
|
+
alignItems: "center",
|
|
4957
|
+
gap: 10,
|
|
4958
|
+
padding: "10px 14px",
|
|
4959
|
+
background: "transparent",
|
|
4960
|
+
border: "none",
|
|
4961
|
+
cursor: "pointer",
|
|
4962
|
+
textAlign: "left",
|
|
4963
|
+
color: "inherit",
|
|
4964
|
+
flexWrap: "wrap",
|
|
4965
|
+
}}
|
|
4966
|
+
>
|
|
4967
|
+
<span style={{ color: "var(--af-text-tertiary)", display: "inline-flex" }}>
|
|
4968
|
+
{open ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
4969
|
+
</span>
|
|
4970
|
+
<span
|
|
4971
|
+
style={{
|
|
4972
|
+
fontFamily: "var(--font-mono)",
|
|
4973
|
+
fontSize: 13,
|
|
4974
|
+
fontWeight: 600,
|
|
4975
|
+
color: "var(--af-text)",
|
|
4976
|
+
minWidth: 0,
|
|
4977
|
+
overflow: "hidden",
|
|
4978
|
+
textOverflow: "ellipsis",
|
|
4979
|
+
whiteSpace: "nowrap",
|
|
4980
|
+
}}
|
|
4981
|
+
title={w.name}
|
|
4982
|
+
>
|
|
4983
|
+
{w.name}
|
|
4984
|
+
</span>
|
|
4985
|
+
<span
|
|
4986
|
+
style={{
|
|
4987
|
+
fontSize: 9.5,
|
|
4988
|
+
fontWeight: 600,
|
|
4989
|
+
padding: "2px 7px",
|
|
4990
|
+
borderRadius: 100,
|
|
4991
|
+
background: color,
|
|
4992
|
+
color: "#fff",
|
|
4993
|
+
textTransform: "uppercase",
|
|
4994
|
+
letterSpacing: "0.04em",
|
|
4995
|
+
}}
|
|
4996
|
+
>
|
|
4997
|
+
{w.status}
|
|
4998
|
+
</span>
|
|
4999
|
+
<span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 12 }}>
|
|
5000
|
+
<span
|
|
5001
|
+
style={{
|
|
5002
|
+
fontSize: 12,
|
|
5003
|
+
fontWeight: 700,
|
|
5004
|
+
padding: "2px 10px",
|
|
5005
|
+
borderRadius: 100,
|
|
5006
|
+
background: "rgba(234,88,12,0.14)",
|
|
5007
|
+
color: "#EA580C",
|
|
5008
|
+
fontFamily: "var(--font-mono)",
|
|
5009
|
+
whiteSpace: "nowrap",
|
|
5010
|
+
}}
|
|
5011
|
+
>
|
|
5012
|
+
{w.agentCount} agents
|
|
5013
|
+
</span>
|
|
5014
|
+
<WfMiniStat icon={<Wrench size={11} />} value={`${w.toolCallCount.toLocaleString()}`} />
|
|
5015
|
+
<WfMiniStat value={`${formatTokens(w.totalTokens)} tok`} />
|
|
5016
|
+
<WfMiniStat icon={<Clock size={11} />} value={dur} />
|
|
5017
|
+
</span>
|
|
5018
|
+
</button>
|
|
5019
|
+
|
|
5020
|
+
{/* Expanded detail */}
|
|
5021
|
+
{open && (
|
|
5022
|
+
<div style={{ padding: "0 14px 14px 38px", display: "flex", flexDirection: "column", gap: 12 }}>
|
|
5023
|
+
{w.description && (
|
|
5024
|
+
<div style={{ fontSize: 12.5, color: "var(--af-text-secondary)", lineHeight: 1.5 }}>
|
|
5025
|
+
{w.description}
|
|
5026
|
+
</div>
|
|
5027
|
+
)}
|
|
5028
|
+
|
|
5029
|
+
<div
|
|
5030
|
+
style={{
|
|
5031
|
+
display: "flex",
|
|
5032
|
+
gap: 14,
|
|
5033
|
+
flexWrap: "wrap",
|
|
5034
|
+
fontSize: 11,
|
|
5035
|
+
fontFamily: "var(--font-mono)",
|
|
5036
|
+
color: "var(--af-text-tertiary)",
|
|
5037
|
+
}}
|
|
5038
|
+
>
|
|
5039
|
+
<span>starts at {startOff}</span>
|
|
5040
|
+
{w.model && <span>· {w.model}</span>}
|
|
5041
|
+
<span>· runId {w.runId}</span>
|
|
5042
|
+
{w.parentToolUseId && (
|
|
5043
|
+
<button
|
|
5044
|
+
type="button"
|
|
5045
|
+
onClick={() => onJumpToParent(w.parentToolUseId)}
|
|
5046
|
+
style={{
|
|
5047
|
+
marginLeft: "auto",
|
|
5048
|
+
fontSize: 11,
|
|
5049
|
+
color: "var(--af-accent)",
|
|
5050
|
+
background: "transparent",
|
|
5051
|
+
border: "none",
|
|
5052
|
+
cursor: "pointer",
|
|
5053
|
+
padding: 0,
|
|
5054
|
+
fontFamily: "var(--font-mono)",
|
|
5055
|
+
}}
|
|
5056
|
+
>
|
|
5057
|
+
Jump to Workflow call →
|
|
5058
|
+
</button>
|
|
5059
|
+
)}
|
|
5060
|
+
</div>
|
|
5061
|
+
|
|
5062
|
+
{w.agents.length > 0 ? (
|
|
5063
|
+
<WorkflowPhaseTabs
|
|
5064
|
+
w={w}
|
|
5065
|
+
selectedAgentKey={selectedAgentKey}
|
|
5066
|
+
onOpenAgent={onOpenAgent}
|
|
5067
|
+
/>
|
|
5068
|
+
) : (
|
|
5069
|
+
w.phases.length > 0 && (
|
|
5070
|
+
<div>
|
|
5071
|
+
<SectionLabel>Phases</SectionLabel>
|
|
5072
|
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
|
|
5073
|
+
{w.phases.map((p, i) => (
|
|
5074
|
+
<span
|
|
5075
|
+
key={`${p.title}-${i}`}
|
|
5076
|
+
title={p.detail}
|
|
5077
|
+
style={{
|
|
5078
|
+
display: "inline-flex",
|
|
5079
|
+
alignItems: "center",
|
|
5080
|
+
gap: 5,
|
|
5081
|
+
fontSize: 11,
|
|
5082
|
+
padding: "3px 9px",
|
|
5083
|
+
borderRadius: 4,
|
|
5084
|
+
background: "var(--af-border-subtle)",
|
|
5085
|
+
color: "var(--af-text)",
|
|
5086
|
+
}}
|
|
5087
|
+
>
|
|
5088
|
+
<b style={{ fontWeight: 600, fontFamily: "var(--font-mono)" }}>{p.title}</b>
|
|
5089
|
+
{p.detail && (
|
|
5090
|
+
<span style={{ color: "var(--af-text-tertiary)", maxWidth: 360, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
|
5091
|
+
{p.detail}
|
|
5092
|
+
</span>
|
|
5093
|
+
)}
|
|
5094
|
+
</span>
|
|
5095
|
+
))}
|
|
5096
|
+
</div>
|
|
5097
|
+
</div>
|
|
5098
|
+
)
|
|
5099
|
+
)}
|
|
5100
|
+
|
|
5101
|
+
{w.logs.length > 0 && <WorkflowRunLog logs={w.logs} />}
|
|
5102
|
+
</div>
|
|
5103
|
+
)}
|
|
5104
|
+
</div>
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
5107
|
+
|
|
5108
|
+
/** Compact model label: "claude-opus-4-8[1m]" → "opus-4-8", "claude-haiku-4-5-20251001" → "haiku-4-5". */
|
|
5109
|
+
function shortenModel(model: string): string {
|
|
5110
|
+
return model
|
|
5111
|
+
.replace(/^claude-/, "")
|
|
5112
|
+
.replace(/\[1m\]$/, "")
|
|
5113
|
+
.replace(/-\d{6,}$/, "")
|
|
5114
|
+
.trim();
|
|
5115
|
+
}
|
|
5116
|
+
|
|
5117
|
+
/** Status dot color for a single workflow-spawned agent. */
|
|
5118
|
+
function agentStateColor(state?: string): string {
|
|
5119
|
+
const s = (state ?? "").toLowerCase();
|
|
5120
|
+
if (s === "done" || s === "completed" || s === "success") return "#10B981";
|
|
5121
|
+
if (s === "error" || s === "failed") return "#EF4444";
|
|
5122
|
+
if (s === "running" || s === "active" || s === "in_progress") return "#F59E0B";
|
|
5123
|
+
return "#8A8580";
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
/** Per-phase tabs for a workflow run. Each tab lists the agents (actions)
|
|
5127
|
+
* that ran in that phase so you can review what actually happened. */
|
|
5128
|
+
function WorkflowPhaseTabs({
|
|
5129
|
+
w,
|
|
5130
|
+
selectedAgentKey,
|
|
5131
|
+
onOpenAgent,
|
|
5132
|
+
}: {
|
|
5133
|
+
w: WorkflowRun;
|
|
5134
|
+
selectedAgentKey?: string | null;
|
|
5135
|
+
onOpenAgent: (runId: string, agent: WorkflowRun["agents"][number]) => void;
|
|
5136
|
+
}) {
|
|
5137
|
+
// Build the phase list from meta.phases (ordered), bucketing agents by
|
|
5138
|
+
// phaseIndex. Agents whose phaseIndex isn't in meta.phases fall into an
|
|
5139
|
+
// appended bucket keyed by their phaseTitle so nothing is dropped.
|
|
5140
|
+
const groups = useMemo(() => {
|
|
5141
|
+
const byIndex = new Map<number, WorkflowRun["agents"]>();
|
|
5142
|
+
const extras = new Map<string, WorkflowRun["agents"]>();
|
|
5143
|
+
for (const a of w.agents) {
|
|
5144
|
+
if (a.phaseIndex && a.phaseIndex >= 1 && a.phaseIndex <= w.phases.length) {
|
|
5145
|
+
const arr = byIndex.get(a.phaseIndex) ?? [];
|
|
5146
|
+
arr.push(a);
|
|
5147
|
+
byIndex.set(a.phaseIndex, arr);
|
|
5148
|
+
} else {
|
|
5149
|
+
const key = a.phaseTitle ?? "Other";
|
|
5150
|
+
const arr = extras.get(key) ?? [];
|
|
5151
|
+
arr.push(a);
|
|
5152
|
+
extras.set(key, arr);
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
const out = w.phases.map((p, i) => ({
|
|
5156
|
+
key: `p${i + 1}`,
|
|
5157
|
+
title: p.title,
|
|
5158
|
+
detail: p.detail,
|
|
5159
|
+
agents: byIndex.get(i + 1) ?? [],
|
|
5160
|
+
}));
|
|
5161
|
+
for (const [title, agents] of extras) {
|
|
5162
|
+
out.push({ key: `x-${title}`, title, detail: undefined, agents });
|
|
5163
|
+
}
|
|
5164
|
+
return out;
|
|
5165
|
+
}, [w]);
|
|
5166
|
+
|
|
5167
|
+
// Default to the first phase that actually has agents.
|
|
5168
|
+
const firstWithAgents = groups.find((g) => g.agents.length > 0)?.key ?? groups[0]?.key ?? "";
|
|
5169
|
+
const [active, setActive] = useState(firstWithAgents);
|
|
5170
|
+
const activeGroup = groups.find((g) => g.key === active) ?? groups[0];
|
|
5171
|
+
|
|
5172
|
+
return (
|
|
5173
|
+
<div>
|
|
5174
|
+
<SectionLabel>Phases & actions</SectionLabel>
|
|
5175
|
+
{/* Phase tab strip */}
|
|
5176
|
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 10 }}>
|
|
5177
|
+
{groups.map((g) => {
|
|
5178
|
+
const isActive = g.key === active;
|
|
5179
|
+
return (
|
|
5180
|
+
<button
|
|
5181
|
+
key={g.key}
|
|
5182
|
+
onClick={() => setActive(g.key)}
|
|
5183
|
+
style={{
|
|
5184
|
+
display: "inline-flex",
|
|
5185
|
+
alignItems: "center",
|
|
5186
|
+
gap: 6,
|
|
5187
|
+
fontSize: 11.5,
|
|
5188
|
+
fontWeight: isActive ? 600 : 500,
|
|
5189
|
+
padding: "4px 10px",
|
|
5190
|
+
borderRadius: 6,
|
|
5191
|
+
border: "1px solid",
|
|
5192
|
+
borderColor: isActive ? "#EA580C" : "var(--af-border-subtle)",
|
|
5193
|
+
background: isActive ? "rgba(234,88,12,0.12)" : "transparent",
|
|
5194
|
+
color: isActive ? "#EA580C" : "var(--af-text-secondary)",
|
|
5195
|
+
cursor: "pointer",
|
|
5196
|
+
fontFamily: "var(--font-mono)",
|
|
5197
|
+
}}
|
|
5198
|
+
>
|
|
5199
|
+
{g.title}
|
|
5200
|
+
<span
|
|
5201
|
+
style={{
|
|
5202
|
+
fontSize: 10,
|
|
5203
|
+
fontWeight: 700,
|
|
5204
|
+
padding: "0px 5px",
|
|
5205
|
+
borderRadius: 100,
|
|
5206
|
+
background: isActive ? "#EA580C" : "var(--af-border-subtle)",
|
|
5207
|
+
color: isActive ? "#fff" : "var(--af-text-tertiary)",
|
|
5208
|
+
}}
|
|
5209
|
+
>
|
|
5210
|
+
{g.agents.length}
|
|
5211
|
+
</span>
|
|
5212
|
+
</button>
|
|
5213
|
+
);
|
|
5214
|
+
})}
|
|
5215
|
+
</div>
|
|
5216
|
+
|
|
5217
|
+
{/* Active phase body */}
|
|
5218
|
+
{activeGroup && (
|
|
5219
|
+
<div>
|
|
5220
|
+
{activeGroup.detail && (
|
|
5221
|
+
<div
|
|
5222
|
+
style={{
|
|
5223
|
+
fontSize: 11.5,
|
|
5224
|
+
color: "var(--af-text-tertiary)",
|
|
5225
|
+
marginBottom: 8,
|
|
5226
|
+
fontStyle: "italic",
|
|
5227
|
+
}}
|
|
5228
|
+
>
|
|
5229
|
+
{activeGroup.detail}
|
|
5230
|
+
</div>
|
|
5231
|
+
)}
|
|
5232
|
+
{activeGroup.agents.length === 0 ? (
|
|
5233
|
+
<div style={{ fontSize: 12, color: "var(--af-text-tertiary)", padding: "8px 0" }}>
|
|
5234
|
+
No agents recorded for this phase.
|
|
5235
|
+
</div>
|
|
5236
|
+
) : (
|
|
5237
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
|
5238
|
+
{activeGroup.agents.map((a) => (
|
|
5239
|
+
<WorkflowAgentRow
|
|
5240
|
+
key={`${a.index}-${a.agentId ?? a.label}`}
|
|
5241
|
+
a={a}
|
|
5242
|
+
selected={selectedAgentKey === `${w.runId}:${a.agentId ?? a.index}`}
|
|
5243
|
+
onOpen={() => onOpenAgent(w.runId, a)}
|
|
5244
|
+
/>
|
|
5245
|
+
))}
|
|
5246
|
+
</div>
|
|
5247
|
+
)}
|
|
5248
|
+
</div>
|
|
5249
|
+
)}
|
|
5250
|
+
</div>
|
|
5251
|
+
);
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5254
|
+
/** One agent (action) inside a phase — collapsed shows label + metering;
|
|
5255
|
+
* expanding reveals the task prompt and the returned result. */
|
|
5256
|
+
/** Full transcript detail for one workflow agent — fetched on demand from
|
|
5257
|
+
* /api/workflow-agent. Mirrors the parser's WorkflowAgentDetail. */
|
|
5258
|
+
type WfAgentDetail = {
|
|
5259
|
+
prompt?: string;
|
|
5260
|
+
finalText?: string;
|
|
5261
|
+
steps: { index: number; tool: string; preview: string; full?: string; isError?: boolean }[];
|
|
5262
|
+
toolCalls: { name: string; count: number }[];
|
|
5263
|
+
toolCallCount: number;
|
|
5264
|
+
assistantMessageCount: number;
|
|
5265
|
+
eventCount: number;
|
|
5266
|
+
totalUsage: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
5267
|
+
model?: string;
|
|
5268
|
+
durationMs?: number;
|
|
5269
|
+
};
|
|
5270
|
+
|
|
5271
|
+
function WorkflowAgentRow({
|
|
5272
|
+
a,
|
|
5273
|
+
selected,
|
|
5274
|
+
onOpen,
|
|
5275
|
+
}: {
|
|
5276
|
+
a: WorkflowRun["agents"][number];
|
|
5277
|
+
selected: boolean;
|
|
5278
|
+
onOpen: () => void;
|
|
5279
|
+
}) {
|
|
5280
|
+
const dur = a.durationMs !== undefined ? formatGap(a.durationMs) : undefined;
|
|
5281
|
+
return (
|
|
5282
|
+
<button
|
|
5283
|
+
onClick={onOpen}
|
|
5284
|
+
style={{
|
|
5285
|
+
width: "100%",
|
|
5286
|
+
display: "flex",
|
|
5287
|
+
alignItems: "center",
|
|
5288
|
+
gap: 8,
|
|
5289
|
+
padding: "8px 10px",
|
|
5290
|
+
textAlign: "left",
|
|
5291
|
+
cursor: "pointer",
|
|
5292
|
+
border: "1px solid",
|
|
5293
|
+
borderColor: selected ? "#EA580C" : "var(--af-border-subtle)",
|
|
5294
|
+
borderRadius: 6,
|
|
5295
|
+
background: selected
|
|
5296
|
+
? "rgba(234,88,12,0.08)"
|
|
5297
|
+
: "var(--af-surface-subtle, rgba(120,115,108,0.04))",
|
|
5298
|
+
color: "inherit",
|
|
5299
|
+
flexWrap: "wrap",
|
|
5300
|
+
}}
|
|
5301
|
+
>
|
|
5302
|
+
<span
|
|
5303
|
+
title={a.state}
|
|
5304
|
+
style={{ width: 7, height: 7, borderRadius: "50%", background: agentStateColor(a.state), flexShrink: 0 }}
|
|
5305
|
+
/>
|
|
5306
|
+
<span
|
|
5307
|
+
style={{
|
|
5308
|
+
fontFamily: "var(--font-mono)",
|
|
5309
|
+
fontSize: 12,
|
|
5310
|
+
fontWeight: 600,
|
|
5311
|
+
color: "var(--af-text)",
|
|
5312
|
+
minWidth: 0,
|
|
5313
|
+
overflow: "hidden",
|
|
5314
|
+
textOverflow: "ellipsis",
|
|
5315
|
+
whiteSpace: "nowrap",
|
|
5316
|
+
maxWidth: 280,
|
|
5317
|
+
}}
|
|
5318
|
+
title={a.label}
|
|
5319
|
+
>
|
|
5320
|
+
{a.label}
|
|
5321
|
+
</span>
|
|
5322
|
+
{a.model && (
|
|
5323
|
+
<span style={{ fontSize: 10, color: "var(--af-text-tertiary)", fontFamily: "var(--font-mono)" }}>
|
|
5324
|
+
{shortenModel(a.model)}
|
|
5325
|
+
</span>
|
|
5326
|
+
)}
|
|
5327
|
+
{a.lastToolSummary && (
|
|
5328
|
+
<span
|
|
5329
|
+
style={{
|
|
5330
|
+
fontSize: 11,
|
|
5331
|
+
color: "var(--af-text-tertiary)",
|
|
5332
|
+
overflow: "hidden",
|
|
5333
|
+
textOverflow: "ellipsis",
|
|
5334
|
+
whiteSpace: "nowrap",
|
|
5335
|
+
maxWidth: 240,
|
|
5336
|
+
}}
|
|
5337
|
+
title={a.lastToolSummary}
|
|
5338
|
+
>
|
|
5339
|
+
→ {a.lastToolSummary}
|
|
5340
|
+
</span>
|
|
5341
|
+
)}
|
|
5342
|
+
<span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 10, flexShrink: 0 }}>
|
|
5343
|
+
{dur && <WfMiniStat icon={<Clock size={10} />} value={dur} />}
|
|
5344
|
+
{a.tokens !== undefined && <WfMiniStat value={`${formatTokens(a.tokens)} tok`} />}
|
|
5345
|
+
{a.toolCalls !== undefined && <WfMiniStat icon={<Wrench size={10} />} value={`${a.toolCalls}`} />}
|
|
5346
|
+
<span style={{ color: selected ? "#EA580C" : "var(--af-text-tertiary)", display: "inline-flex" }}>
|
|
5347
|
+
<ChevronRight size={14} />
|
|
5348
|
+
</span>
|
|
5349
|
+
</span>
|
|
5350
|
+
</button>
|
|
5351
|
+
);
|
|
5352
|
+
}
|
|
5353
|
+
|
|
5354
|
+
/** Right side-sheet showing one workflow agent's full transcript — Task,
|
|
5355
|
+
* ordered Steps, and Result — fetched on demand. Kept out of the card so the
|
|
5356
|
+
* Workflows tab stays short even for 100+ step agents. */
|
|
5357
|
+
function WorkflowAgentDrawer({
|
|
5358
|
+
sessionId,
|
|
5359
|
+
runId,
|
|
5360
|
+
agent,
|
|
5361
|
+
onClose,
|
|
5362
|
+
}: {
|
|
5363
|
+
sessionId: string;
|
|
5364
|
+
runId: string;
|
|
5365
|
+
agent: WorkflowRun["agents"][number];
|
|
5366
|
+
onClose: () => void;
|
|
5367
|
+
}) {
|
|
5368
|
+
const [detail, setDetail] = useState<WfAgentDetail | null>(null);
|
|
5369
|
+
const [loading, setLoading] = useState(false);
|
|
5370
|
+
const [failed, setFailed] = useState(false);
|
|
5371
|
+
|
|
5372
|
+
useEffect(() => {
|
|
5373
|
+
setDetail(null);
|
|
5374
|
+
setFailed(false);
|
|
5375
|
+
if (!agent.agentId) return;
|
|
5376
|
+
setLoading(true);
|
|
5377
|
+
const ctrl = new AbortController();
|
|
5378
|
+
const url = `/api/workflow-agent?session=${encodeURIComponent(sessionId)}&run=${encodeURIComponent(runId)}&agent=${encodeURIComponent(agent.agentId)}`;
|
|
5379
|
+
fetch(url, { signal: ctrl.signal })
|
|
5380
|
+
.then((r) => (r.ok ? r.json() : Promise.reject(r.status)))
|
|
5381
|
+
.then((d: WfAgentDetail) => setDetail(d))
|
|
5382
|
+
.catch((e) => {
|
|
5383
|
+
if (e?.name !== "AbortError") setFailed(true);
|
|
5384
|
+
})
|
|
5385
|
+
.finally(() => setLoading(false));
|
|
5386
|
+
return () => ctrl.abort();
|
|
5387
|
+
}, [sessionId, runId, agent.agentId]);
|
|
5388
|
+
|
|
5389
|
+
const prompt = detail?.prompt ?? agent.promptPreview;
|
|
5390
|
+
const finalText = detail?.finalText ?? agent.resultPreview;
|
|
5391
|
+
const dur = detail?.durationMs ?? agent.durationMs;
|
|
5392
|
+
const model = detail?.model ?? agent.model;
|
|
5393
|
+
const toolCount = detail?.toolCallCount ?? agent.toolCalls;
|
|
5394
|
+
const tok = detail ? detail.totalUsage.input + detail.totalUsage.output : agent.tokens;
|
|
5395
|
+
|
|
5396
|
+
return (
|
|
5397
|
+
<div>
|
|
5398
|
+
{/* Sticky title bar */}
|
|
5399
|
+
<div
|
|
5400
|
+
style={{
|
|
5401
|
+
padding: "14px 18px",
|
|
5402
|
+
borderBottom: "1px solid var(--af-border-subtle)",
|
|
5403
|
+
display: "flex",
|
|
5404
|
+
alignItems: "center",
|
|
5405
|
+
gap: 10,
|
|
5406
|
+
background: "var(--af-surface)",
|
|
5407
|
+
position: "sticky",
|
|
5408
|
+
top: 0,
|
|
5409
|
+
zIndex: 1,
|
|
5410
|
+
}}
|
|
5411
|
+
>
|
|
5412
|
+
<span
|
|
5413
|
+
title={agent.state}
|
|
5414
|
+
style={{ width: 8, height: 8, borderRadius: "50%", background: agentStateColor(agent.state), flexShrink: 0 }}
|
|
5415
|
+
/>
|
|
5416
|
+
<span
|
|
5417
|
+
style={{
|
|
5418
|
+
fontSize: 13.5,
|
|
5419
|
+
fontWeight: 600,
|
|
5420
|
+
fontFamily: "var(--font-mono)",
|
|
5421
|
+
color: "var(--af-text)",
|
|
5422
|
+
flex: 1,
|
|
5423
|
+
minWidth: 0,
|
|
5424
|
+
overflow: "hidden",
|
|
5425
|
+
textOverflow: "ellipsis",
|
|
5426
|
+
whiteSpace: "nowrap",
|
|
5427
|
+
}}
|
|
5428
|
+
title={agent.label}
|
|
5429
|
+
>
|
|
5430
|
+
{agent.label}
|
|
5431
|
+
</span>
|
|
5432
|
+
<button
|
|
5433
|
+
onClick={onClose}
|
|
5434
|
+
style={{ background: "transparent", border: "none", cursor: "pointer", color: "var(--af-text-tertiary)", padding: 4 }}
|
|
5435
|
+
aria-label="Close"
|
|
5436
|
+
>
|
|
5437
|
+
<X size={16} />
|
|
5438
|
+
</button>
|
|
5439
|
+
</div>
|
|
5440
|
+
|
|
5441
|
+
{/* Meta strip */}
|
|
5442
|
+
<div
|
|
5443
|
+
style={{
|
|
5444
|
+
padding: "9px 18px",
|
|
5445
|
+
fontFamily: "var(--font-mono)",
|
|
5446
|
+
fontSize: 11,
|
|
5447
|
+
color: "var(--af-text-tertiary)",
|
|
5448
|
+
borderBottom: "1px solid var(--af-border-subtle)",
|
|
5449
|
+
display: "flex",
|
|
5450
|
+
gap: 12,
|
|
5451
|
+
flexWrap: "wrap",
|
|
5452
|
+
}}
|
|
5453
|
+
>
|
|
5454
|
+
{agent.phaseTitle && <span>phase {agent.phaseTitle}</span>}
|
|
5455
|
+
{agent.state && <span>· {agent.state}</span>}
|
|
5456
|
+
{model && <span>· {shortenModel(model)}</span>}
|
|
5457
|
+
{dur !== undefined && <span>· {formatGap(dur)}</span>}
|
|
5458
|
+
{toolCount !== undefined && <span>· {toolCount} tools</span>}
|
|
5459
|
+
{tok !== undefined && <span>· {formatTokens(tok)} tok</span>}
|
|
5460
|
+
</div>
|
|
5461
|
+
|
|
5462
|
+
{/* Body — each section collapsed by default; click to expand. */}
|
|
5463
|
+
<div style={{ padding: "12px 18px 18px", display: "flex", flexDirection: "column", gap: 8 }}>
|
|
5464
|
+
{loading && (
|
|
5465
|
+
<div style={{ fontSize: 11.5, color: "var(--af-text-tertiary)", fontStyle: "italic" }}>
|
|
5466
|
+
Loading full transcript…
|
|
5467
|
+
</div>
|
|
5468
|
+
)}
|
|
5469
|
+
{failed && (
|
|
5470
|
+
<div style={{ fontSize: 11.5, color: "var(--af-text-tertiary)", fontStyle: "italic" }}>
|
|
5471
|
+
Full transcript unavailable — showing the journal summary.
|
|
5472
|
+
</div>
|
|
5473
|
+
)}
|
|
5474
|
+
|
|
5475
|
+
{prompt && (
|
|
5476
|
+
<DrawerCollapsible title="Task" hint={detail ? undefined : "preview"}>
|
|
5477
|
+
<div style={{ fontSize: 12, color: "var(--af-text-secondary)", whiteSpace: "pre-wrap", lineHeight: 1.5 }}>
|
|
5478
|
+
{prompt}
|
|
5479
|
+
</div>
|
|
5480
|
+
</DrawerCollapsible>
|
|
5481
|
+
)}
|
|
5482
|
+
|
|
5483
|
+
{detail && detail.steps.length > 0 && (
|
|
5484
|
+
<DrawerCollapsible
|
|
5485
|
+
title="Steps"
|
|
5486
|
+
count={detail.steps.length}
|
|
5487
|
+
hint={detail.toolCalls.slice(0, 6).map((t) => `${shortenToolName(t.name)} ×${t.count}`).join(" · ")}
|
|
5488
|
+
>
|
|
5489
|
+
<div style={{ border: "1px solid var(--af-border-subtle)", borderRadius: 6, background: "var(--af-surface)" }}>
|
|
5490
|
+
{detail.steps.map((s) => (
|
|
5491
|
+
<WorkflowStepRow key={s.index} s={s} />
|
|
5492
|
+
))}
|
|
5493
|
+
</div>
|
|
5494
|
+
</DrawerCollapsible>
|
|
5495
|
+
)}
|
|
5496
|
+
|
|
5497
|
+
{finalText && (
|
|
5498
|
+
<DrawerCollapsible title="Result" hint={detail ? undefined : "preview"}>
|
|
5499
|
+
<div
|
|
5500
|
+
style={{
|
|
5501
|
+
fontFamily: "var(--font-mono)",
|
|
5502
|
+
fontSize: 11,
|
|
5503
|
+
color: "var(--af-text-secondary)",
|
|
5504
|
+
whiteSpace: "pre-wrap",
|
|
5505
|
+
wordBreak: "break-word",
|
|
5506
|
+
lineHeight: 1.5,
|
|
5507
|
+
background: "var(--af-surface)",
|
|
5508
|
+
border: "1px solid var(--af-border-subtle)",
|
|
5509
|
+
borderRadius: 4,
|
|
5510
|
+
padding: "8px 10px",
|
|
5511
|
+
}}
|
|
5512
|
+
>
|
|
5513
|
+
{finalText}
|
|
5514
|
+
</div>
|
|
5515
|
+
</DrawerCollapsible>
|
|
5516
|
+
)}
|
|
5517
|
+
</div>
|
|
5518
|
+
</div>
|
|
5519
|
+
);
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5522
|
+
/** Collapsible section inside the agent sheet — default collapsed. */
|
|
5523
|
+
function DrawerCollapsible({
|
|
5524
|
+
title,
|
|
5525
|
+
count,
|
|
5526
|
+
hint,
|
|
5527
|
+
defaultOpen = false,
|
|
5528
|
+
children,
|
|
5529
|
+
}: {
|
|
5530
|
+
title: string;
|
|
5531
|
+
count?: number;
|
|
5532
|
+
hint?: string;
|
|
5533
|
+
defaultOpen?: boolean;
|
|
5534
|
+
children: React.ReactNode;
|
|
5535
|
+
}) {
|
|
5536
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
5537
|
+
return (
|
|
5538
|
+
<div style={{ border: "1px solid var(--af-border-subtle)", borderRadius: 8, overflow: "hidden" }}>
|
|
5539
|
+
<button
|
|
5540
|
+
onClick={() => setOpen((v) => !v)}
|
|
5541
|
+
style={{
|
|
5542
|
+
width: "100%",
|
|
5543
|
+
display: "flex",
|
|
5544
|
+
alignItems: "center",
|
|
5545
|
+
gap: 8,
|
|
5546
|
+
padding: "9px 12px",
|
|
5547
|
+
background: open ? "var(--af-surface-subtle, rgba(120,115,108,0.05))" : "transparent",
|
|
5548
|
+
border: "none",
|
|
5549
|
+
cursor: "pointer",
|
|
5550
|
+
textAlign: "left",
|
|
5551
|
+
color: "inherit",
|
|
5552
|
+
}}
|
|
5553
|
+
>
|
|
5554
|
+
<span style={{ color: "var(--af-text-tertiary)", display: "inline-flex" }}>
|
|
5555
|
+
{open ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
5556
|
+
</span>
|
|
5557
|
+
<span style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.04em", color: "var(--af-text-secondary)" }}>
|
|
5558
|
+
{title}
|
|
5559
|
+
</span>
|
|
5560
|
+
{count !== undefined && (
|
|
5561
|
+
<span
|
|
5562
|
+
style={{
|
|
5563
|
+
fontSize: 10,
|
|
5564
|
+
fontWeight: 700,
|
|
5565
|
+
padding: "1px 7px",
|
|
5566
|
+
borderRadius: 100,
|
|
5567
|
+
background: "rgba(234,88,12,0.14)",
|
|
5568
|
+
color: "#EA580C",
|
|
5569
|
+
fontFamily: "var(--font-mono)",
|
|
5570
|
+
}}
|
|
5571
|
+
>
|
|
5572
|
+
{count}
|
|
5573
|
+
</span>
|
|
5574
|
+
)}
|
|
5575
|
+
{hint && (
|
|
5576
|
+
<span
|
|
5577
|
+
style={{
|
|
5578
|
+
marginLeft: "auto",
|
|
5579
|
+
fontSize: 10.5,
|
|
5580
|
+
color: "var(--af-text-tertiary)",
|
|
5581
|
+
fontFamily: "var(--font-mono)",
|
|
5582
|
+
overflow: "hidden",
|
|
5583
|
+
textOverflow: "ellipsis",
|
|
5584
|
+
whiteSpace: "nowrap",
|
|
5585
|
+
maxWidth: 280,
|
|
5586
|
+
}}
|
|
5587
|
+
>
|
|
5588
|
+
{hint}
|
|
5589
|
+
</span>
|
|
5590
|
+
)}
|
|
5591
|
+
</button>
|
|
5592
|
+
{open && <div style={{ padding: "0 12px 12px" }}>{children}</div>}
|
|
5593
|
+
</div>
|
|
5594
|
+
);
|
|
5595
|
+
}
|
|
5596
|
+
|
|
5597
|
+
/** One step row — click to reveal the full tool input (e.g. the complete
|
|
5598
|
+
* multi-line bash command) when it's longer than the one-line preview. */
|
|
5599
|
+
function WorkflowStepRow({ s }: { s: WfAgentDetail["steps"][number] }) {
|
|
5600
|
+
const [open, setOpen] = useState(false);
|
|
5601
|
+
const expandable = !!s.full;
|
|
5602
|
+
return (
|
|
5603
|
+
<div style={{ borderTop: s.index === 1 ? "none" : "1px solid var(--af-border-subtle)" }}>
|
|
5604
|
+
<div
|
|
5605
|
+
onClick={() => expandable && setOpen((v) => !v)}
|
|
5606
|
+
style={{
|
|
5607
|
+
display: "flex",
|
|
5608
|
+
alignItems: "baseline",
|
|
5609
|
+
gap: 8,
|
|
5610
|
+
padding: "4px 10px",
|
|
5611
|
+
fontFamily: "var(--font-mono)",
|
|
5612
|
+
fontSize: 11,
|
|
5613
|
+
cursor: expandable ? "pointer" : "default",
|
|
5614
|
+
}}
|
|
5615
|
+
>
|
|
5616
|
+
<span style={{ color: "var(--af-text-tertiary)", minWidth: 28, textAlign: "right", flexShrink: 0 }}>
|
|
5617
|
+
{s.index}
|
|
5618
|
+
</span>
|
|
5619
|
+
<span style={{ fontWeight: 600, color: s.isError ? "#EF4444" : "var(--af-text)", flexShrink: 0, minWidth: 104 }}>
|
|
5620
|
+
{s.isError ? "⚠ " : ""}
|
|
5621
|
+
{shortenToolName(s.tool)}
|
|
5622
|
+
</span>
|
|
5623
|
+
<span
|
|
5624
|
+
style={{ flex: 1, color: "var(--af-text-tertiary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
|
|
5625
|
+
title={s.preview}
|
|
5626
|
+
>
|
|
5627
|
+
{s.preview}
|
|
5628
|
+
</span>
|
|
5629
|
+
{expandable && (
|
|
5630
|
+
<span style={{ color: "var(--af-text-tertiary)", display: "inline-flex", flexShrink: 0 }}>
|
|
5631
|
+
{open ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
|
|
5632
|
+
</span>
|
|
5633
|
+
)}
|
|
5634
|
+
</div>
|
|
5635
|
+
{open && s.full && (
|
|
5636
|
+
<pre
|
|
5637
|
+
style={{
|
|
5638
|
+
margin: 0,
|
|
5639
|
+
padding: "6px 10px 10px 40px",
|
|
5640
|
+
fontFamily: "var(--font-mono)",
|
|
5641
|
+
fontSize: 11,
|
|
5642
|
+
whiteSpace: "pre-wrap",
|
|
5643
|
+
wordBreak: "break-word",
|
|
5644
|
+
color: "var(--af-text-secondary)",
|
|
5645
|
+
background: "var(--af-surface-subtle, rgba(120,115,108,0.05))",
|
|
5646
|
+
lineHeight: 1.5,
|
|
5647
|
+
}}
|
|
5648
|
+
>
|
|
5649
|
+
{s.full}
|
|
5650
|
+
</pre>
|
|
5651
|
+
)}
|
|
5652
|
+
</div>
|
|
5653
|
+
);
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
/** Collapsible coarse run log (▶ / ✓ task markers) — secondary to the
|
|
5657
|
+
* per-phase agent view. */
|
|
5658
|
+
function WorkflowRunLog({ logs }: { logs: string[] }) {
|
|
5659
|
+
const [open, setOpen] = useState(false);
|
|
5660
|
+
return (
|
|
5661
|
+
<div>
|
|
5662
|
+
<button
|
|
5663
|
+
onClick={() => setOpen((v) => !v)}
|
|
5664
|
+
style={{
|
|
5665
|
+
display: "inline-flex",
|
|
5666
|
+
alignItems: "center",
|
|
5667
|
+
gap: 5,
|
|
5668
|
+
fontSize: 10,
|
|
5669
|
+
fontWeight: 600,
|
|
5670
|
+
textTransform: "uppercase",
|
|
5671
|
+
letterSpacing: "0.04em",
|
|
5672
|
+
color: "var(--af-text-tertiary)",
|
|
5673
|
+
background: "transparent",
|
|
5674
|
+
border: "none",
|
|
5675
|
+
cursor: "pointer",
|
|
5676
|
+
padding: 0,
|
|
5677
|
+
}}
|
|
5678
|
+
>
|
|
5679
|
+
{open ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
|
5680
|
+
Run log ({logs.length})
|
|
5681
|
+
</button>
|
|
5682
|
+
{open && (
|
|
5683
|
+
<div
|
|
5684
|
+
style={{
|
|
5685
|
+
marginTop: 6,
|
|
5686
|
+
fontFamily: "var(--font-mono)",
|
|
5687
|
+
fontSize: 11.5,
|
|
5688
|
+
lineHeight: 1.7,
|
|
5689
|
+
color: "var(--af-text-secondary)",
|
|
5690
|
+
background: "var(--af-surface-subtle, rgba(120,115,108,0.05))",
|
|
5691
|
+
border: "1px solid var(--af-border-subtle)",
|
|
5692
|
+
borderRadius: 6,
|
|
5693
|
+
padding: "8px 12px",
|
|
5694
|
+
maxHeight: 240,
|
|
5695
|
+
overflowY: "auto",
|
|
5696
|
+
whiteSpace: "pre-wrap",
|
|
5697
|
+
}}
|
|
5698
|
+
>
|
|
5699
|
+
{logs.map((l, i) => (
|
|
5700
|
+
<div key={i}>{l}</div>
|
|
5701
|
+
))}
|
|
5702
|
+
</div>
|
|
5703
|
+
)}
|
|
5704
|
+
</div>
|
|
5705
|
+
);
|
|
5706
|
+
}
|
|
5707
|
+
|
|
5708
|
+
function WfMiniStat({ icon, value }: { icon?: React.ReactNode; value: string }) {
|
|
5709
|
+
return (
|
|
5710
|
+
<span
|
|
5711
|
+
style={{
|
|
5712
|
+
display: "inline-flex",
|
|
5713
|
+
alignItems: "center",
|
|
5714
|
+
gap: 4,
|
|
5715
|
+
fontSize: 11.5,
|
|
5716
|
+
color: "var(--af-text-secondary)",
|
|
5717
|
+
fontFamily: "var(--font-mono)",
|
|
5718
|
+
whiteSpace: "nowrap",
|
|
5719
|
+
}}
|
|
5720
|
+
>
|
|
5721
|
+
{icon && <span style={{ color: "var(--af-text-tertiary)", display: "inline-flex" }}>{icon}</span>}
|
|
5722
|
+
{value}
|
|
5723
|
+
</span>
|
|
5724
|
+
);
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5727
|
+
function SectionLabel({ children }: { children: React.ReactNode }) {
|
|
5728
|
+
return (
|
|
5729
|
+
<div
|
|
5730
|
+
style={{
|
|
5731
|
+
fontSize: 10,
|
|
5732
|
+
fontWeight: 600,
|
|
5733
|
+
textTransform: "uppercase",
|
|
5734
|
+
letterSpacing: "0.04em",
|
|
5735
|
+
color: "var(--af-text-tertiary)",
|
|
5736
|
+
marginBottom: 8,
|
|
5737
|
+
}}
|
|
5738
|
+
>
|
|
5739
|
+
{children}
|
|
5740
|
+
</div>
|
|
5741
|
+
);
|
|
5742
|
+
}
|
|
5743
|
+
|
|
4469
5744
|
function StatCell({ label, value }: { label: string; value: string }) {
|
|
4470
5745
|
return (
|
|
4471
5746
|
<div>
|
|
@@ -4903,8 +6178,11 @@ function formatDurationHeader(ms: number): string {
|
|
|
4903
6178
|
const s = Math.round(ms / 1000);
|
|
4904
6179
|
if (s < 60) return `${s}s`;
|
|
4905
6180
|
const m = Math.floor(s / 60);
|
|
4906
|
-
|
|
4907
|
-
|
|
6181
|
+
if (m < 60) return `${m}m ${s % 60}s`;
|
|
6182
|
+
// Roll up to hours — agent time can reach many hours once workflow
|
|
6183
|
+
// execution is folded in, and "1116m" reads poorly.
|
|
6184
|
+
const h = Math.floor(m / 60);
|
|
6185
|
+
return `${h}h ${m % 60}m`;
|
|
4908
6186
|
}
|
|
4909
6187
|
|
|
4910
6188
|
function EntryDayStrip({ entry }: { entry: Entry }) {
|