Files
paseo/packages/app/src/utils/tool-call-detail-state.ts
T
Mohamed BoudraandGitHub 60531b5e1b refactor: long-tail type-aware sweep (T3.c) (#759)
* refactor: long-tail type-aware sweep (T3.c)

Cluster T3.c — 15 files, 24 errors cleared.

Rules fixed:
- no-useless-default-assignment: 6 → 0 (mcp-server.ts — Zod .default() makes destructuring defaults redundant)
- no-unnecessary-type-assertion: 1 → 0 (workspace-registry-model.ts — remove redundant ! on string[])
- no-unsafe-enum-comparison: 3 → 0 (use-push-token-registration.ts, use-image-attachment-picker.ts — use PermissionStatus enum values)
- no-redundant-type-constituents: 6 → 0 (workspace-setup.ts, tool-call-detail-state.ts, workspace-draft-agent-tab.tsx, stream.test.ts, desktop-settings-commands.ts — unknown | T → unknown)
- no-unnecessary-type-parameters: 4 → 0 (indexeddb-attachment-store.test.ts, use-archive-agent.ts, class-mocks.ts, agent-stream-coalescer.ts — narrow interface / remove unused generics)
- no-unnecessary-type-conversion: 4 → 0 (session-context.tsx — remove String() on already-string values; class-mocks.ts — remove String(prop) after symbol guard)
- no-implied-eval: 1 → 0 (agent-stream-coalescer.test.ts — narrow timer interface to function-only, eliminating string overload)

Deferred (need caller updates or extra files beyond 15-file cap):
- no-unnecessary-type-parameters: host.ts:onResized/onDragDropEvent, electron/events.ts (callers pass typed handlers; removing generic breaks contravariance without cast)
- no-base-to-string: ~21 errors, 18 files
- restrict-template-expressions: ~11 errors, 9 files
- await-thenable: ~7 errors, 4 files
- no-unnecessary-type-conversion: ~9 remaining errors, 9 files

* fix(server): restore MCP tool handler defaults that the linter mis-flagged

The `no-useless-default-assignment` rule fires because Zod's `.default()`
chain makes the TypeScript type non-optional, so the linter treats the
destructuring defaults as redundant. But the MCP framework invokes
handlers with raw (untransformed) params — Zod defaults are not applied
before the call. The destructuring defaults are the runtime safety net.

Reverts the three handler changes from T3.c:
- send_prompt: background = false, notifyOnFinish = false
- list_agents: includeArchived = false, sinceHours = 48, limit = 50
- get_terminal_lines: stripAnsi = true

The no-useless-default-assignment errors on these lines remain and are
deferred to a future cluster that addresses the MCP framework integration.
2026-05-05 22:38:55 +08:00

104 lines
3.4 KiB
TypeScript

import type { ToolCallDetail } from "@server/server/agent/agent-sdk-types";
function hasMeaningfulUnknownValue(value: unknown): boolean {
if (value === null || value === undefined) {
return false;
}
if (typeof value === "string") {
return value.trim().length > 0;
}
if (typeof value === "number" || typeof value === "boolean") {
return true;
}
if (Array.isArray(value)) {
return value.some(hasMeaningfulUnknownValue);
}
if (typeof value === "object") {
return Object.values(value).some(hasMeaningfulUnknownValue);
}
return true;
}
function hasMeaningfulSearchDetail(detail: Extract<ToolCallDetail, { type: "search" }>): boolean {
if (detail.query.trim().length > 0) return true;
if (detail.content) return true;
if (detail.filePaths && detail.filePaths.length > 0) return true;
if (detail.webResults && detail.webResults.length > 0) return true;
if (detail.annotations && detail.annotations.length > 0) return true;
return false;
}
function hasMeaningfulReadLikeDetail(detail: { filePath?: unknown; content?: unknown }): boolean {
return Boolean(detail.filePath || detail.content);
}
function hasMeaningfulEditDetail(detail: Extract<ToolCallDetail, { type: "edit" }>): boolean {
return Boolean(detail.filePath || detail.unifiedDiff || detail.oldString || detail.newString);
}
function hasMeaningfulFetchDetail(detail: Extract<ToolCallDetail, { type: "fetch" }>): boolean {
return Boolean(detail.url || detail.result || detail.codeText);
}
function hasMeaningfulWorktreeSetupDetail(
detail: Extract<ToolCallDetail, { type: "worktree_setup" }>,
): boolean {
return Boolean(detail.branchName || detail.worktreePath || detail.log);
}
function hasMeaningfulSubAgentDetail(
detail: Extract<ToolCallDetail, { type: "sub_agent" }>,
): boolean {
return Boolean(detail.subAgentType || detail.description || detail.log);
}
function hasMeaningfulPlainTextDetail(
detail: Extract<ToolCallDetail, { type: "plain_text" }>,
): boolean {
return Boolean(detail.label || detail.text);
}
function hasMeaningfulUnknownDetail(detail: Extract<ToolCallDetail, { type: "unknown" }>): boolean {
return hasMeaningfulUnknownValue(detail.input) || hasMeaningfulUnknownValue(detail.output);
}
export function hasMeaningfulToolCallDetail(detail: ToolCallDetail | undefined): boolean {
if (!detail) {
return false;
}
switch (detail.type) {
case "shell":
return true;
case "read":
return hasMeaningfulReadLikeDetail(detail);
case "edit":
return hasMeaningfulEditDetail(detail);
case "write":
return hasMeaningfulReadLikeDetail(detail);
case "search":
return hasMeaningfulSearchDetail(detail);
case "fetch":
return hasMeaningfulFetchDetail(detail);
case "worktree_setup":
return hasMeaningfulWorktreeSetupDetail(detail);
case "sub_agent":
return hasMeaningfulSubAgentDetail(detail);
case "plain_text":
return hasMeaningfulPlainTextDetail(detail);
case "plan":
return detail.text.trim().length > 0;
case "unknown":
return hasMeaningfulUnknownDetail(detail);
}
}
export function isPendingToolCallDetail(params: {
detail: ToolCallDetail | undefined;
status: "executing" | "running" | "completed" | "failed" | "canceled";
error: unknown;
}): boolean {
const isRunning = params.status === "running" || params.status === "executing";
return isRunning && params.error == null && !hasMeaningfulToolCallDetail(params.detail);
}