Skip to content
Open
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dev": "node scripts/dev-server.mjs",
"postinstall": "node scripts/fix-node-pty-permissions.mjs",
"start": "node dist/cli.js serve",
"test": "tsx src/config.test.ts && tsx src/cli-workspace.test.ts && tsx src/cli-output.test.ts && tsx src/open-workspace-capabilities.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-capabilities.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-resolution.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/review-checkpoints.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts && tsx src/workflow-contracts.test.ts && tsx src/workflow-errors.test.ts && tsx src/workflow-types.test.ts && tsx src/workflow-store.test.ts && tsx src/workflow-lifecycle.test.ts && tsx src/workflow-view.test.ts && tsx src/workflow-ui.test.ts && tsx src/workflow-tui.test.ts && tsx src/workflow-script.test.ts && tsx src/workflow-sandbox.test.ts && tsx src/workflow-engine.test.ts && tsx src/workflow-files.test.ts && tsx src/workflow-launch.test.ts && tsx src/workflow-replay.test.ts && tsx src/workflow-schema.test.ts",
"test": "tsx src/config.test.ts && tsx src/cli-workspace.test.ts && tsx src/cli-output.test.ts && tsx src/open-workspace-capabilities.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-capabilities.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-resolution.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/review-checkpoints.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts && tsx src/workflow-contracts.test.ts && tsx src/workflow-errors.test.ts && tsx src/workflow-types.test.ts && tsx src/workflow-store.test.ts && tsx src/workflow-lifecycle.test.ts && tsx src/workflow-view.test.ts && tsx src/workflow-summary.test.ts && tsx src/workflow-tui.test.ts && tsx src/workflow-script.test.ts && tsx src/workflow-sandbox.test.ts && tsx src/workflow-engine.test.ts && tsx src/workflow-files.test.ts && tsx src/workflow-launch.test.ts && tsx src/workflow-replay.test.ts && tsx src/workflow-schema.test.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"keywords": [],
Expand Down
33 changes: 33 additions & 0 deletions src/open-workspace-capabilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { z } from "zod";
import { loadConfig } from "./config.js";
import { openWorkspaceOutputSchema } from "./server.js";

Expand Down Expand Up @@ -39,4 +40,36 @@ assert.equal(workflowsOnly.has("agentProviders"), false);
assert.equal(workflowsOnly.has("agents"), false);
assert.equal(workflowsOnly.has("activeWorkflows"), true);

const enabledSchema = z.object(openWorkspaceOutputSchema(loadConfig({
...baseEnv,
DEVSPACE_SUBAGENTS: "1",
DEVSPACE_WORKFLOWS: "1",
})));
const parsed = enabledSchema.parse({
workspaceId: "workspace-1",
root: process.cwd(),
mode: "checkout",
agentsFiles: [],
availableAgentsFiles: [],
skills: [],
agentProviders: ["codex"],
agents: [{ name: "reviewer", description: "Review changes." }],
activeWorkflows: [{
id: "wfr_1",
name: "Review",
status: "running",
calls: { running: 1, completed: 2, failed: 0 },
}],
instruction: "Reuse this workspace.",
});
assert.deepEqual(parsed.agentProviders, ["codex"]);
assert.deepEqual(parsed.agents, [{ name: "reviewer", description: "Review changes." }]);
assert.deepEqual(parsed.activeWorkflows, [{
id: "wfr_1",
name: "Review",
status: "running",
calls: { running: 1, completed: 2, failed: 0 },
}]);
assert.equal("skillDiagnostics" in parsed, false);
Comment on lines +48 to +73

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Line 73 does not test what it intends to test.

The parsed input at lines 48-64 never contains skillDiagnostics. "skillDiagnostics" in parsed is therefore false no matter what the schema declares. The assertion passes even if openWorkspaceOutputSchema reintroduces the field.

Assert against the schema shape instead. The file already has a fields() helper at lines 16-18 that returns the shape keys.

💚 Proposed stronger assertion
-assert.equal("skillDiagnostics" in parsed, false);
+assert.equal(
+  fields({ ...baseEnv, DEVSPACE_SUBAGENTS: "1", DEVSPACE_WORKFLOWS: "1" }).has("skillDiagnostics"),
+  false,
+);

If you want to keep a parse-level check as well, pass the field in the input and confirm that z.object strips it:

+const stripped = enabledSchema.parse({
+  ...validInput,
+  skillDiagnostics: [{ path: "a", message: "b" }],
+});
+assert.equal("skillDiagnostics" in stripped, false);

Confirm the strip-by-default behavior of z.object in zod 4.4.3 before you rely on the second form.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parsed = enabledSchema.parse({
workspaceId: "workspace-1",
root: process.cwd(),
mode: "checkout",
agentsFiles: [],
availableAgentsFiles: [],
skills: [],
agentProviders: ["codex"],
agents: [{ name: "reviewer", description: "Review changes." }],
activeWorkflows: [{
id: "wfr_1",
name: "Review",
status: "running",
calls: { running: 1, completed: 2, failed: 0 },
}],
instruction: "Reuse this workspace.",
});
assert.deepEqual(parsed.agentProviders, ["codex"]);
assert.deepEqual(parsed.agents, [{ name: "reviewer", description: "Review changes." }]);
assert.deepEqual(parsed.activeWorkflows, [{
id: "wfr_1",
name: "Review",
status: "running",
calls: { running: 1, completed: 2, failed: 0 },
}]);
assert.equal("skillDiagnostics" in parsed, false);
const parsed = enabledSchema.parse({
workspaceId: "workspace-1",
root: process.cwd(),
mode: "checkout",
agentsFiles: [],
availableAgentsFiles: [],
skills: [],
agentProviders: ["codex"],
agents: [{ name: "reviewer", description: "Review changes." }],
activeWorkflows: [{
id: "wfr_1",
name: "Review",
status: "running",
calls: { running: 1, completed: 2, failed: 0 },
}],
instruction: "Reuse this workspace.",
});
assert.deepEqual(parsed.agentProviders, ["codex"]);
assert.deepEqual(parsed.agents, [{ name: "reviewer", description: "Review changes." }]);
assert.deepEqual(parsed.activeWorkflows, [{
id: "wfr_1",
name: "Review",
status: "running",
calls: { running: 1, completed: 2, failed: 0 },
}]);
assert.equal(
fields({ ...baseEnv, DEVSPACE_SUBAGENTS: "1", DEVSPACE_WORKFLOWS: "1" }).has("skillDiagnostics"),
false,
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/open-workspace-capabilities.test.ts` around lines 48 - 73, Update the
`skillDiagnostics` assertion in the `enabledSchema` test to inspect the schema
shape via the existing `fields()` helper, asserting that `skillDiagnostics` is
absent from the declared keys. Do not rely solely on `"skillDiagnostics" in
parsed`, since the parsed input does not include that field; optionally add a
parse-level stripping check only if supported by the project’s Zod version.


console.log("open-workspace-capabilities.test.ts: ok");
62 changes: 14 additions & 48 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ import { formatPathForPrompt } from "./skills.js";
import { createWorkspaceStore } from "./workspace-store.js";
import { formatAgentsPath, WorkspaceRegistry } from "./workspaces.js";
import { buildLocalAgentCatalog } from "./local-agent-catalog.js";
import { registerWorkflowTools } from "./workflow-tools.js";
import { startWorkflowReaper } from "./workflow-lifecycle.js";
import { createWorkflowStore } from "./workflow-store.js";
import { loadActiveWorkflowSummaries } from "./workflow-ui.js";
import { loadActiveWorkflowSummaries } from "./workflow-summary.js";
import {
formatLocalAgentProviderAvailabilitySummary,
getLocalAgentProviderAvailabilitySnapshot,
Expand Down Expand Up @@ -205,17 +204,6 @@ function serverInstructions(config: ServerConfig): string {
return `Use DevSpace as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree to obtain a workspaceId. Reuse that same workspaceId for all later file, search, edit, write, show-changes, and shell tools in that folder; do not call ${toolNames.openWorkspace} again unless switching folders/worktrees, changing checkout/worktree mode, the workspaceId is rejected as unknown, or the user explicitly asks to reopen. ${agentsMd}${skills}${inspection}Prefer ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not create or modify files with ${toolNames.shell}; avoid shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or any command whose purpose is to write project files.${showChangesInstruction}`;
}

function formatVisibleAgent(agent: {
name: string;
provider: string;
model?: string;
effort?: string;
}): string {
const model = agent.model ? `, model ${agent.model}` : "";
const effort = agent.effort ? `, effort ${agent.effort}` : "";
return `${agent.name} (${agent.provider}${model}${effort})`;
}

function resultOutputSchema(extra: z.ZodRawShape = {}): z.ZodRawShape {
return {
result: z
Expand All @@ -241,22 +229,6 @@ const workspaceAgentsFileOutputSchema = z.object({
const workspaceLocalAgentOutputSchema = z.object({
name: z.string(),
description: z.string(),
provider: z.string(),
model: z.string().optional(),
effort: z.string().optional(),
});

const workspaceLocalAgentProviderOutputSchema = z.object({
name: z.string(),
model: z.object({
supported: z.boolean(),
discovery: z.enum(["provider_static", "model_dependent", "session_dynamic"]),
}),
effort: z.object({
supported: z.boolean(),
semantics: z.enum(["reasoning_effort", "thinking_level", "model_variant"]),
discovery: z.enum(["provider_static", "model_dependent", "session_dynamic"]),
}),
});

export function openWorkspaceOutputSchema(config: ServerConfig): z.ZodRawShape {
Expand All @@ -280,11 +252,10 @@ export function openWorkspaceOutputSchema(config: ServerConfig): z.ZodRawShape {
skills: z.array(workspaceSkillOutputSchema),
...(config.subagents
? {
agentProviders: z.array(workspaceLocalAgentProviderOutputSchema),
agentProviders: z.array(z.string()),
agents: z.array(workspaceLocalAgentOutputSchema),
}
: {}),
skillDiagnostics: z.array(z.unknown()),
...(config.workflows
? { activeWorkflows: z.array(workflowRunSummaryOutputSchema) }
: {}),
Expand All @@ -299,19 +270,14 @@ const workspaceAvailableAgentsFileOutputSchema = z.object({
const workflowCallCountsOutputSchema = z.object({
running: z.number(),
completed: z.number(),
cached: z.number(),
failed: z.number(),
cancelled: z.number(),
observed: z.number(),
});

const workflowRunSummaryOutputSchema = z.object({
id: z.string(),
name: z.string(),
status: z.enum(["starting", "running", "completed", "failed", "cancelled"]),
currentPhase: z.string().optional(),
status: z.enum(["starting", "running"]),
calls: workflowCallCountsOutputSchema,
updatedAt: z.string(),
});

const reviewFileOutputSchema = z.object({
Expand Down Expand Up @@ -831,8 +797,11 @@ function createMcpServer(
const agentCatalog = config.subagents
? buildLocalAgentCatalog(workspace.agentProfiles, localAgentProviders)
: undefined;
const visibleAgentProviders = agentCatalog?.providers ?? [];
const visibleAgents = agentCatalog?.profiles ?? [];
const visibleAgentProviders = agentCatalog?.providers.map((provider) => provider.name) ?? [];
const visibleAgents = agentCatalog?.profiles.map((agent) => ({
name: agent.name,
description: agent.description,
})) ?? [];
const loadedAgentsFiles = agentsFiles.map((file) => ({
path: formatAgentsPath(file.path, workspace.root),
content: file.content,
Expand All @@ -844,7 +813,10 @@ function createMcpServer(
? (() => {
const workflowStore = createWorkflowStore(config);
try {
return loadActiveWorkflowSummaries(workflowStore, workspace.root);
return loadActiveWorkflowSummaries(workflowStore, {
workspaceId: workspace.id,
workspaceRoot: workspace.root,
});
Comment thread
Waishnav marked this conversation as resolved.
} finally {
workflowStore.close();
}
Expand All @@ -870,10 +842,10 @@ function createMcpServer(
? `Available skills: ${visibleSkills.map((skill) => skill.name).join(", ")}`
: undefined,
visibleAgentProviders.length > 0
? `Available subagent providers: ${visibleAgentProviders.map((provider) => provider.name).join(", ")}`
? `Available subagent providers: ${visibleAgentProviders.join(", ")}`
: undefined,
visibleAgents.length > 0
? `Available subagent profiles: ${visibleAgents.map(formatVisibleAgent).join(", ")}`
? `Available subagent profiles: ${visibleAgents.map((agent) => `${agent.name} — ${agent.description}`).join(", ")}`
: undefined,
instruction,
].filter(Boolean).join("\n"),
Expand Down Expand Up @@ -909,7 +881,6 @@ function createMcpServer(
agents: visibleAgents.length,
}
: {}),
skillDiagnostics: workspace.skillDiagnostics.length,
},
},
},
Expand All @@ -929,7 +900,6 @@ function createMcpServer(
agents: visibleAgents,
}
: {}),
skillDiagnostics: workspace.skillDiagnostics,
instruction,
},
};
Expand Down Expand Up @@ -1637,10 +1607,6 @@ function createMcpServer(
registerCodexProcessTools(server, config, workspaces, processSessions);
Comment thread
Waishnav marked this conversation as resolved.
}

if (config.workflows) {
registerWorkflowTools(server, config, workspaces);
}

return server;
}

Expand Down
7 changes: 0 additions & 7 deletions src/ui/card-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ for (const tool of [
"apply_patch",
"exec_command",
"write_stdin",
"run_workflow",
"workflow_status",
]) {
assert.equal(isToolName(tool), true, `${tool} should be a recognized card tool`);
}
Expand All @@ -29,7 +27,6 @@ assert.equal(
true,
);
assert.equal(isExpandableCard({ tool: "apply_patch" }), false);
assert.equal(isExpandableCard({ tool: "run_workflow", runId: "wfr_1" }), true);
assert.equal(
isExpandableCard({
tool: "open_workspace",
Expand All @@ -41,12 +38,8 @@ assert.equal(
calls: {
running: 1,
completed: 0,
cached: 0,
failed: 0,
cancelled: 0,
observed: 1,
},
updatedAt: "2026-07-26T00:00:00.000Z",
},
],
}),
Expand Down
45 changes: 4 additions & 41 deletions src/ui/card-types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { App } from "@modelcontextprotocol/ext-apps";
import type { WorkflowRunSummaryView } from "../workflow-ui.js";
import type { ActiveWorkflowSummary } from "../workflow-summary.js";

export type ToolName =
| "open_workspace"
| "run_workflow"
| "workflow_status"
| "show_changes"
| "apply_patch"
| "exec_command"
Expand Down Expand Up @@ -36,9 +34,6 @@ export interface ToolResultCard {
detached?: boolean;
managed?: boolean;
};
status?: string;
name?: string;
runId?: string;
summary?: Record<string, unknown>;
files?: Array<{
path?: string;
Expand All @@ -61,34 +56,12 @@ export interface ToolResultCard {
description?: string;
path?: string;
}>;
activeWorkflows?: WorkflowRunSummaryView[];
callSummary?: {
reused?: number;
live?: number;
failed?: number;
running?: number;
total?: number;
};
agentProviders?: Array<{
name?: string;
model?: {
supported?: boolean;
discovery?: string;
};
effort?: {
supported?: boolean;
semantics?: string;
discovery?: string;
};
}>;
activeWorkflows?: ActiveWorkflowSummary[];
agentProviders?: string[];
agents?: Array<{
name?: string;
description?: string;
provider?: string;
model?: string;
effort?: string;
}>;
Comment on lines +59 to 64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Strict ActiveWorkflowSummary type applied to an unvalidated host payload. The card adopts the server-side contract type, which requires id, name, status, and calls. The value arrives through structuredContent from the host, and isToolResultCard only checks that the value is an object. The dashboard then dereferences the required fields, so a malformed entry throws a TypeError before container.replaceChildren(root) runs and the whole workspace dashboard fails to render.

  • src/ui/card-types.ts#L59-L64: declare a loose card-side shape for activeWorkflows with optional id, name, status, and a Partial calls; keep ActiveWorkflowSummary as the server-side contract.
  • src/ui/workflow-dashboard.ts#L127-L141: apply ?? fallbacks for run.name and run.status, and change summaryCounts to accept an optional partial calls object.
📍 Affects 2 files
  • src/ui/card-types.ts#L59-L64 (this comment)
  • src/ui/workflow-dashboard.ts#L127-L141
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/card-types.ts` around lines 59 - 64, Use a loose card-side shape for
activeWorkflows in src/ui/card-types.ts lines 59-64, with optional id, name,
status, and Partial calls, while preserving ActiveWorkflowSummary as the server
contract. In src/ui/workflow-dashboard.ts lines 127-141, add nullish fallbacks
when reading run.name and run.status and update summaryCounts to accept optional
partial calls.

skillDiagnostics?: unknown[];
instruction?: string;
}

Expand All @@ -108,8 +81,6 @@ export interface ToolPayload {
export function isToolName(value: unknown): value is ToolName {
return (
value === "open_workspace" ||
value === "run_workflow" ||
value === "workflow_status" ||
value === "show_changes" ||
value === "apply_patch" ||
value === "exec_command" ||
Expand Down Expand Up @@ -152,10 +123,6 @@ export function isReviewTool(tool: ToolName): boolean {
return tool === "show_changes";
}

export function isWorkflowTool(tool: ToolName): boolean {
return tool === "run_workflow" || tool === "workflow_status";
}

export function isToolResultCard(value: unknown): value is Omit<ToolResultCard, "tool"> {
return Boolean(value && typeof value === "object");
}
Expand Down Expand Up @@ -185,19 +152,15 @@ export function isExpandableCard(card: ToolResultCard): boolean {
return (
Number(card.summary?.agentsFiles ?? 0) > 0 ||
Number(card.summary?.skills ?? 0) > 0 ||
Number(card.summary?.skillDiagnostics ?? 0) > 0 ||
Boolean(card.agentsFiles?.length) ||
Boolean(card.availableAgentsFiles?.length) ||
Boolean(card.skills?.length) ||
Boolean(card.activeWorkflows?.length) ||
Boolean(card.agentProviders?.length) ||
Boolean(card.agents?.length) ||
Boolean(card.skillDiagnostics?.length)
Boolean(card.agents?.length)
);
}

if (isWorkflowTool(card.tool)) return Boolean(card.runId);

if (isReviewTool(card.tool)) return Boolean(card.files?.length || card.payload?.patch);
if (isPatchTool(card.tool)) return Boolean(card.payload?.patch);

Expand Down
2 changes: 0 additions & 2 deletions src/ui/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
Search,
SquareTerminal,
Terminal,
Workflow,
createElement,
type IconNode,
} from "lucide";
Expand All @@ -34,7 +33,6 @@ export const toolIcons = {
search: Search,
terminal: Terminal,
terminalSquare: SquareTerminal,
workflow: Workflow,
writeFile: FilePlus,
} as const satisfies Record<string, IconNode>;

Expand Down
12 changes: 0 additions & 12 deletions src/ui/tool-display.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { getToolDisplay, getToolHeaderSummary } from "./tool-display.js";

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

assert.equal(getToolDisplay({ tool: "open_workspace", root: "/tmp/project" }).label, "/tmp/project");
assert.equal(getToolDisplay({ tool: "run_workflow", runId: "wfr_1" }).label, "wfr_1");
assert.equal(
getToolDisplay({ tool: "grep", summary: { pattern: "needle", scope: "src" } }).label,
"needle in src",
Expand Down Expand Up @@ -123,15 +120,6 @@ assert.deepEqual(
getToolHeaderSummary({ tool: "open_workspace" }),
{ kind: "empty" },
);
assert.deepEqual(
getToolHeaderSummary({
tool: "workflow_status",
status: "running",
callSummary: { running: 2, failed: 1 },
}),
{ kind: "text", text: "running · 2 running · 1 failed" },
);

function pickDisplay(display: ReturnType<typeof getToolDisplay>) {
return {
title: display.title,
Expand Down
Loading
Loading