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.
This commit is contained in:
@@ -45,7 +45,7 @@ interface WorkspaceSetupDaemonClient {
|
|||||||
}>;
|
}>;
|
||||||
fetchAgent(agentId: string): Promise<{
|
fetchAgent(agentId: string): Promise<{
|
||||||
agent: { id: string; cwd: string } | null;
|
agent: { id: string; cwd: string } | null;
|
||||||
project: unknown | null;
|
project: unknown;
|
||||||
} | null>;
|
} | null>;
|
||||||
listTerminals(cwd: string): Promise<{
|
listTerminals(cwd: string): Promise<{
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { createIndexedDbAttachmentStore } from "./indexeddb-attachment-store";
|
|||||||
|
|
||||||
type Listener = () => void;
|
type Listener = () => void;
|
||||||
|
|
||||||
class FakeRequest<T = unknown> {
|
class FakeRequest {
|
||||||
result!: T;
|
result!: unknown;
|
||||||
error: Error | null = null;
|
error: Error | null = null;
|
||||||
private listeners = new Map<string, Listener[]>();
|
private listeners = new Map<string, Listener[]>();
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ describe("indexeddb attachment store", () => {
|
|||||||
const store = new FakeObjectStore((record) => {
|
const store = new FakeObjectStore((record) => {
|
||||||
storedRecord = record;
|
storedRecord = record;
|
||||||
});
|
});
|
||||||
const request = new FakeRequest<FakeDatabase>();
|
const request = new FakeRequest();
|
||||||
request.result = new FakeDatabase(store);
|
request.result = new FakeDatabase(store);
|
||||||
queueMicrotask(() => request.emit("success"));
|
queueMicrotask(() => request.emit("success"));
|
||||||
return request;
|
return request;
|
||||||
|
|||||||
@@ -230,10 +230,10 @@ function getArchivedAgentListCacheSnapshot(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreCachedQuerySnapshot<T>(
|
function restoreCachedQuerySnapshot(
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
queryKey: readonly unknown[],
|
queryKey: readonly unknown[],
|
||||||
snapshot: T | undefined,
|
snapshot: unknown,
|
||||||
): void {
|
): void {
|
||||||
if (snapshot === undefined) {
|
if (snapshot === undefined) {
|
||||||
queryClient.removeQueries({ queryKey, exact: true });
|
queryClient.removeQueries({ queryKey, exact: true });
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export function useImageAttachmentPicker(): UseImageAttachmentPickerResult {
|
|||||||
const ensurePermission = useCallback(async () => {
|
const ensurePermission = useCallback(async () => {
|
||||||
let currentPermission = mediaPermission;
|
let currentPermission = mediaPermission;
|
||||||
|
|
||||||
if (!currentPermission || currentPermission.status === "undetermined") {
|
if (
|
||||||
|
!currentPermission ||
|
||||||
|
currentPermission.status === ImagePicker.PermissionStatus.UNDETERMINED
|
||||||
|
) {
|
||||||
currentPermission = await requestMediaPermission();
|
currentPermission = await requestMediaPermission();
|
||||||
} else if (!currentPermission.granted) {
|
} else if (!currentPermission.granted) {
|
||||||
currentPermission = await requestMediaPermission();
|
currentPermission = await requestMediaPermission();
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ function getExpoProjectId(): string | null {
|
|||||||
|
|
||||||
async function ensurePushPermission(): Promise<boolean> {
|
async function ensurePushPermission(): Promise<boolean> {
|
||||||
const existing = await Notifications.getPermissionsAsync();
|
const existing = await Notifications.getPermissionsAsync();
|
||||||
if (existing.status === "granted") return true;
|
if (existing.status === Notifications.PermissionStatus.GRANTED) return true;
|
||||||
if (!existing.canAskAgain) return false;
|
if (!existing.canAskAgain) return false;
|
||||||
const requested = await Notifications.requestPermissionsAsync();
|
const requested = await Notifications.requestPermissionsAsync();
|
||||||
return requested.status === "granted";
|
return requested.status === Notifications.PermissionStatus.GRANTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePushTokenRegistration(params: { client: DaemonClient; serverId: string }): void {
|
export function usePushTokenRegistration(params: { client: DaemonClient; serverId: string }): void {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { shouldAutoFocusWorkspaceDraftComposer } from "@/screens/workspace/works
|
|||||||
import type { AgentCapabilityFlags } from "@server/server/agent/agent-sdk-types";
|
import type { AgentCapabilityFlags } from "@server/server/agent/agent-sdk-types";
|
||||||
import type { AgentSnapshotPayload } from "@server/shared/messages";
|
import type { AgentSnapshotPayload } from "@server/shared/messages";
|
||||||
import type { DaemonClient } from "@server/client/daemon-client";
|
import type { DaemonClient } from "@server/client/daemon-client";
|
||||||
import type { ComposerAttachment, WorkspaceComposerAttachment } from "@/attachments/types";
|
import type { WorkspaceComposerAttachment } from "@/attachments/types";
|
||||||
import {
|
import {
|
||||||
useWorkspaceAttachments,
|
useWorkspaceAttachments,
|
||||||
useWorkspaceAttachmentScopeKey,
|
useWorkspaceAttachmentScopeKey,
|
||||||
@@ -147,7 +147,7 @@ async function submitDraftCreateRequest(input: {
|
|||||||
attempt: { clientMessageId: string };
|
attempt: { clientMessageId: string };
|
||||||
text: string;
|
text: string;
|
||||||
images?: UserMessageImageAttachment[];
|
images?: UserMessageImageAttachment[];
|
||||||
attachments?: ComposerAttachment[] | unknown;
|
attachments?: unknown;
|
||||||
client: DaemonClient | null;
|
client: DaemonClient | null;
|
||||||
workspaceDirectory: string | null;
|
workspaceDirectory: string | null;
|
||||||
workspaceExecutionAuthority: { workspaceId: string } | null;
|
workspaceExecutionAuthority: { workspaceId: string } | null;
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ function canonicalToolTimeline(params: {
|
|||||||
callId: string;
|
callId: string;
|
||||||
name: string;
|
name: string;
|
||||||
status: CanonicalToolStatus;
|
status: CanonicalToolStatus;
|
||||||
input?: unknown | null;
|
input?: unknown;
|
||||||
output?: unknown | null;
|
output?: unknown;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
detail?: ToolCallDetail;
|
detail?: ToolCallDetail;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function hasMeaningfulToolCallDetail(detail: ToolCallDetail | undefined):
|
|||||||
export function isPendingToolCallDetail(params: {
|
export function isPendingToolCallDetail(params: {
|
||||||
detail: ToolCallDetail | undefined;
|
detail: ToolCallDetail | undefined;
|
||||||
status: "executing" | "running" | "completed" | "failed" | "canceled";
|
status: "executing" | "running" | "completed" | "failed" | "canceled";
|
||||||
error: unknown | null | undefined;
|
error: unknown;
|
||||||
}): boolean {
|
}): boolean {
|
||||||
const isRunning = params.status === "running" || params.status === "executing";
|
const isRunning = params.status === "running" || params.status === "executing";
|
||||||
return isRunning && params.error == null && !hasMeaningfulToolCallDetail(params.detail);
|
return isRunning && params.error == null && !hasMeaningfulToolCallDetail(params.detail);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { DesktopSettingsStore } from "./desktop-settings.js";
|
import type { DesktopSettingsStore } from "./desktop-settings.js";
|
||||||
|
|
||||||
export type DesktopCommandHandler = (args?: Record<string, unknown>) => Promise<unknown> | unknown;
|
export type DesktopCommandHandler = (args?: Record<string, unknown>) => unknown;
|
||||||
|
|
||||||
export function createDesktopSettingsCommandHandlers({
|
export function createDesktopSettingsCommandHandlers({
|
||||||
settingsStore,
|
settingsStore,
|
||||||
|
|||||||
@@ -481,13 +481,11 @@ describe("AgentStreamCoalescer", () => {
|
|||||||
test("stale timers cannot flush reused agent ids", () => {
|
test("stale timers cannot flush reused agent ids", () => {
|
||||||
const scheduled: Array<() => void> = [];
|
const scheduled: Array<() => void> = [];
|
||||||
const flushes: AgentStreamCoalescerFlush[] = [];
|
const flushes: AgentStreamCoalescerFlush[] = [];
|
||||||
const setTimeoutShim: AgentStreamCoalescerTimers["setTimeout"] = (callback, delay, ...args) => {
|
const setTimeoutShim: AgentStreamCoalescerTimers["setTimeout"] = (callback, delay) => {
|
||||||
if (typeof callback === "function") {
|
scheduled.push(() => {
|
||||||
scheduled.push(() => {
|
callback();
|
||||||
callback(...args);
|
});
|
||||||
});
|
return setTimeout(callback, delay);
|
||||||
}
|
|
||||||
return setTimeout(callback, delay, ...args);
|
|
||||||
};
|
};
|
||||||
const coalescer = new AgentStreamCoalescer({
|
const coalescer = new AgentStreamCoalescer({
|
||||||
timers: {
|
timers: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type CoalescableTimelineEvent = Extract<AgentStreamEvent, { type: "timeline" }>
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface AgentStreamCoalescerTimers {
|
export interface AgentStreamCoalescerTimers {
|
||||||
setTimeout: typeof setTimeout;
|
setTimeout: (callback: () => void, ms?: number) => ReturnType<typeof setTimeout>;
|
||||||
clearTimeout: typeof clearTimeout;
|
clearTimeout: typeof clearTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function createStub<T extends object>(stubs: { [K in keyof T]?: unknown }
|
|||||||
if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
|
if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
|
||||||
if (typeof prop === "symbol") return undefined;
|
if (typeof prop === "symbol") return undefined;
|
||||||
return (..._args: unknown[]): never => {
|
return (..._args: unknown[]): never => {
|
||||||
throw new Error(`createStub: "${String(prop)}" was called but not stubbed`);
|
throw new Error(`createStub: "${prop}" was called but not stubbed`);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}) as unknown as T; // one justified cast — rationale in file-level comment
|
}) as unknown as T; // one justified cast — rationale in file-level comment
|
||||||
@@ -52,6 +52,6 @@ export function createStub<T extends object>(stubs: { [K in keyof T]?: unknown }
|
|||||||
* assertion — narrowing from the top type is safe, unlike `as unknown as T`
|
* assertion — narrowing from the top type is safe, unlike `as unknown as T`
|
||||||
* which short-circuits structural checking on a concrete source type.
|
* which short-circuits structural checking on a concrete source type.
|
||||||
*/
|
*/
|
||||||
export function asInternals<T>(obj: unknown): T {
|
export function asInternals<T>(obj: T extends object ? unknown : unknown): T {
|
||||||
return obj as T;
|
return obj as T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export function deriveProjectGroupingName(projectKey: string): string {
|
|||||||
return pathSegments.slice(-2).join("/");
|
return pathSegments.slice(-2).join("/");
|
||||||
}
|
}
|
||||||
if (pathSegments.length === 1) {
|
if (pathSegments.length === 1) {
|
||||||
return pathSegments[0]!;
|
return pathSegments[0];
|
||||||
}
|
}
|
||||||
return projectKey;
|
return projectKey;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user