Skip to content

Commit 57296a6

Browse files
committed
feat(ui): recognize workflow result cards
1 parent d3643e8 commit 57296a6

4 files changed

Lines changed: 76 additions & 0 deletions

File tree

src/ui/card-types.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { App } from "@modelcontextprotocol/ext-apps";
2+
import type { WorkflowRunSummaryView } from "../workflow-ui.js";
23

34
export type ToolName =
45
| "open_workspace"
6+
| "run_workflow"
7+
| "workflow_status"
58
| "show_changes"
69
| "apply_patch"
710
| "exec_command"
@@ -24,6 +27,8 @@ export interface ToolResultCard {
2427
path?: string;
2528
root?: string;
2629
status?: string;
30+
name?: string;
31+
runId?: string;
2732
summary?: Record<string, unknown>;
2833
files?: Array<{
2934
path?: string;
@@ -46,6 +51,28 @@ export interface ToolResultCard {
4651
description?: string;
4752
path?: string;
4853
}>;
54+
activeWorkflows?: WorkflowRunSummaryView[];
55+
callSummary?: {
56+
reused?: number;
57+
live?: number;
58+
failed?: number;
59+
running?: number;
60+
total?: number;
61+
};
62+
agentProviders?: Array<{
63+
name?: string;
64+
available?: boolean;
65+
reason?: string;
66+
}>;
67+
agents?: Array<{
68+
name?: string;
69+
description?: string;
70+
provider?: string;
71+
model?: string;
72+
effort?: string;
73+
providerAvailable?: boolean;
74+
providerUnavailableReason?: string;
75+
}>;
4976
skillDiagnostics?: unknown[];
5077
instruction?: string;
5178
}
@@ -66,6 +93,8 @@ export interface ToolPayload {
6693
export function isToolName(value: unknown): value is ToolName {
6794
return (
6895
value === "open_workspace" ||
96+
value === "run_workflow" ||
97+
value === "workflow_status" ||
6998
value === "show_changes" ||
7099
value === "apply_patch" ||
71100
value === "exec_command" ||
@@ -108,6 +137,10 @@ export function isReviewTool(tool: ToolName): boolean {
108137
return tool === "show_changes";
109138
}
110139

140+
export function isWorkflowTool(tool: ToolName): boolean {
141+
return tool === "run_workflow" || tool === "workflow_status";
142+
}
143+
111144
export function isToolResultCard(value: unknown): value is Omit<ToolResultCard, "tool"> {
112145
return Boolean(value && typeof value === "object");
113146
}
@@ -145,6 +178,8 @@ export function isExpandableCard(card: ToolResultCard): boolean {
145178
);
146179
}
147180

181+
if (isWorkflowTool(card.tool)) return Boolean(card.runId);
182+
148183
if (isReviewTool(card.tool)) return Boolean(card.files?.length || card.payload?.patch);
149184
if (isPatchTool(card.tool)) return Boolean(card.payload?.patch);
150185

src/ui/icons.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import {
99
FolderOpen,
1010
FolderTree,
1111
LoaderCircle,
12+
Maximize2,
13+
Minimize2,
1214
Search,
1315
SquareTerminal,
1416
Terminal,
17+
Workflow,
1518
createElement,
1619
type IconNode,
1720
} from "lucide";
@@ -25,10 +28,13 @@ export const toolIcons = {
2528
folderOpen: FolderOpen,
2629
folderTree: FolderTree,
2730
loading: LoaderCircle,
31+
maximize: Maximize2,
32+
minimize: Minimize2,
2833
readFile: FileText,
2934
search: Search,
3035
terminal: Terminal,
3136
terminalSquare: SquareTerminal,
37+
workflow: Workflow,
3238
writeFile: FilePlus,
3339
} as const satisfies Record<string, IconNode>;
3440

src/ui/tool-display.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { getToolDisplay, getToolHeaderSummary } from "./tool-display.js";
55

66
const displayCases: Array<[ToolResultCard, { title: string; tone: string }]> = [
77
[{ tool: "open_workspace", root: "/tmp/project" }, { title: "Opened workspace", tone: "workspace" }],
8+
[{ tool: "run_workflow", runId: "wfr_1", name: "Review" }, { title: "Started workflow", tone: "workflow" }],
9+
[{ tool: "workflow_status", runId: "wfr_1", name: "Review" }, { title: "Workflow status", tone: "workflow" }],
810
[{ tool: "read", path: "src/read.ts" }, { title: "Read file", tone: "read" }],
911
[{ tool: "write", path: "src/write.ts" }, { title: "Wrote file", tone: "write" }],
1012
[{ tool: "edit", path: "src/edit.ts" }, { title: "Edited file", tone: "edit" }],
@@ -25,6 +27,7 @@ for (const [card, expected] of displayCases) {
2527
}
2628

2729
assert.equal(getToolDisplay({ tool: "open_workspace", root: "/tmp/project" }).label, "/tmp/project");
30+
assert.equal(getToolDisplay({ tool: "run_workflow", runId: "wfr_1" }).label, "wfr_1");
2831
assert.equal(
2932
getToolDisplay({ tool: "grep", summary: { pattern: "needle", scope: "src" } }).label,
3033
"needle in src",
@@ -120,6 +123,14 @@ assert.deepEqual(
120123
getToolHeaderSummary({ tool: "open_workspace" }),
121124
{ kind: "empty" },
122125
);
126+
assert.deepEqual(
127+
getToolHeaderSummary({
128+
tool: "workflow_status",
129+
status: "running",
130+
callSummary: { running: 2, failed: 1 },
131+
}),
132+
{ kind: "text", text: "running · 2 running · 1 failed" },
133+
);
123134

124135
function pickDisplay(display: ReturnType<typeof getToolDisplay>) {
125136
return {

src/ui/tool-display.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
isPatchTool,
44
isReviewTool,
55
isShellTool,
6+
isWorkflowTool,
67
isWriteTool,
78
summaryNumber,
89
type ToolResultCard,
@@ -31,6 +32,20 @@ export function getToolDisplay(card: ToolResultCard): ToolDisplay {
3132
label: card.root ?? card.path,
3233
tone: "workspace",
3334
};
35+
case "run_workflow":
36+
return {
37+
icon: toolIcons.workflow,
38+
title: card.status === "completed" ? "Workflow completed" : "Started workflow",
39+
label: card.name ?? card.runId,
40+
tone: "workflow",
41+
};
42+
case "workflow_status":
43+
return {
44+
icon: toolIcons.workflow,
45+
title: "Workflow status",
46+
label: card.name ?? card.runId,
47+
tone: "workflow",
48+
};
3449
case "read":
3550
return {
3651
icon: toolIcons.readFile,
@@ -129,6 +144,15 @@ export function getToolHeaderSummary(card: ToolResultCard): ToolHeaderSummary {
129144
return parts.length > 0 ? { kind: "text", text: parts.join(" · ") } : { kind: "empty" };
130145
}
131146

147+
if (isWorkflowTool(card.tool)) {
148+
const parts = [
149+
card.status,
150+
card.callSummary?.running ? `${card.callSummary.running} running` : undefined,
151+
card.callSummary?.failed ? `${card.callSummary.failed} failed` : undefined,
152+
].filter((part): part is string => Boolean(part));
153+
return parts.length > 0 ? { kind: "text", text: parts.join(" · ") } : { kind: "empty" };
154+
}
155+
132156
if (isShellTool(card.tool)) {
133157
const parts = [
134158
countLabel(summaryNumber(summary, "lines"), "line"),

0 commit comments

Comments
 (0)