fix: resolve all ESLint errors breaking CI validate job
- Fix import sort order in 6 files (TeamDataService, TeamProvisioningService, ChatHistory, SortableTab, LeadThoughtsGroup, MemberDraftRow) - Replace slow regex with string manipulation in MemberStatsComputer - Use RegExp.exec() instead of String.match() in toolSummary - Fix non-backtracking regex pattern in toolSummary parser - Remove unnecessary type assertions in TeamDataService and TeamProvisioningService - Convert for-loop to for-of in TeamDataService - Fix no-param-reassign in TeamDataService.sendMessage - Convert function component to arrow function in LeadThoughtsGroup - Fix nested functions depth in MentionableTextarea tip rotation - Use optional chain in teamSlice
This commit is contained in:
parent
f61077d4ee
commit
baabae0594
10 changed files with 54 additions and 40 deletions
|
|
@ -9,11 +9,17 @@ import type { FileLineStats, MemberFullStats } from '@shared/types';
|
|||
|
||||
const logger = createLogger('Service:MemberStatsComputer');
|
||||
|
||||
const TRAILING_PUNCT = /[;.,]+$/;
|
||||
const TRAILING_PUNCT_CHARS = new Set([';', '.', ',']);
|
||||
const INVALID_NAMES = new Set(['null', 'undefined', 'None', 'false', 'true', '']);
|
||||
|
||||
function stripTrailingPunct(s: string): string {
|
||||
let end = s.length;
|
||||
while (end > 0 && TRAILING_PUNCT_CHARS.has(s[end - 1])) end--;
|
||||
return end === s.length ? s : s.slice(0, end);
|
||||
}
|
||||
|
||||
export function isValidFilePath(value: string): boolean {
|
||||
const cleaned = value.trim().replace(TRAILING_PUNCT, '');
|
||||
const cleaned = stripTrailingPunct(value.trim());
|
||||
return cleaned.length > 1 && !INVALID_NAMES.has(cleaned) && cleaned.includes('/');
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +139,7 @@ export class MemberStatsComputer {
|
|||
// Track last known content per file for accurate Write/NotebookEdit diffs
|
||||
const fileLastContent = new Map<string, string>();
|
||||
|
||||
const cleanPath = (fp: string): string => fp.trim().replace(TRAILING_PUNCT, '');
|
||||
const cleanPath = (fp: string): string => stripTrailingPunct(fp.trim());
|
||||
|
||||
const trackFile = (fp: string): void => {
|
||||
if (typeof fp === 'string') {
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ import type {
|
|||
TeamTask,
|
||||
TeamTaskStatus,
|
||||
TeamTaskWithKanban,
|
||||
UpdateKanbanPatch,
|
||||
ToolCallMeta,
|
||||
UpdateKanbanPatch,
|
||||
} from '@shared/types';
|
||||
|
||||
const logger = createLogger('Service:TeamDataService');
|
||||
|
|
@ -344,12 +344,12 @@ export class TeamDataService {
|
|||
// Find closest anchor by timestamp (binary-search-like scan from current position)
|
||||
let bestAnchor = anchors[0];
|
||||
let bestDist = Math.abs(msgTime - bestAnchor.time);
|
||||
for (let a = 0; a < anchors.length; a++) {
|
||||
const dist = Math.abs(msgTime - anchors[a].time);
|
||||
for (const anchor of anchors) {
|
||||
const dist = Math.abs(msgTime - anchor.time);
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestAnchor = anchors[a];
|
||||
} else if (dist > bestDist && anchors[a].time > msgTime) {
|
||||
bestAnchor = anchor;
|
||||
} else if (dist > bestDist && anchor.time > msgTime) {
|
||||
// Anchors are sorted by index (asc time) — once distance grows past the
|
||||
// message time, further anchors will only be farther.
|
||||
break;
|
||||
|
|
@ -1161,17 +1161,18 @@ export class TeamDataService {
|
|||
|
||||
async sendMessage(teamName: string, request: SendMessageRequest): Promise<SendMessageResult> {
|
||||
// Enrich with leadSessionId so session boundary separators work
|
||||
if (!request.leadSessionId) {
|
||||
let enrichedRequest = request;
|
||||
if (!enrichedRequest.leadSessionId) {
|
||||
try {
|
||||
const config = await this.configReader.getConfig(teamName);
|
||||
if (config?.leadSessionId) {
|
||||
request = { ...request, leadSessionId: config.leadSessionId };
|
||||
enrichedRequest = { ...enrichedRequest, leadSessionId: config.leadSessionId };
|
||||
}
|
||||
} catch {
|
||||
// non-critical
|
||||
}
|
||||
}
|
||||
return this.inboxWriter.sendMessage(teamName, request);
|
||||
return this.inboxWriter.sendMessage(teamName, enrichedRequest);
|
||||
}
|
||||
|
||||
private resolveLeadNameFromConfig(config: TeamConfig | null): string {
|
||||
|
|
@ -1528,8 +1529,8 @@ export class TeamDataService {
|
|||
if (b.type === 'tool_use' && typeof b.name === 'string' && b.name !== 'SendMessage') {
|
||||
const input = (b.input ?? {}) as Record<string, unknown>;
|
||||
toolCallsList.push({
|
||||
name: b.name as string,
|
||||
preview: extractToolPreview(b.name as string, input),
|
||||
name: b.name,
|
||||
preview: extractToolPreview(b.name, input),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import { getMemberColor } from '@shared/constants/memberColors';
|
|||
import { resolveLanguageName } from '@shared/utils/agentLanguage';
|
||||
import { isInboxNoiseMessage } from '@shared/utils/inboxNoise';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
import { extractToolPreview, formatToolSummaryFromCalls } from '@shared/utils/toolSummary';
|
||||
import { createCliAutoSuffixNameGuard } from '@shared/utils/teamMemberName';
|
||||
import { extractToolPreview, formatToolSummaryFromCalls } from '@shared/utils/toolSummary';
|
||||
import { spawn } from 'child_process';
|
||||
import { randomUUID } from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
|
|
@ -2971,8 +2971,8 @@ export class TeamProvisioningService {
|
|||
) {
|
||||
const input = (block.input ?? {}) as Record<string, unknown>;
|
||||
run.pendingToolCalls.push({
|
||||
name: block.name as string,
|
||||
preview: extractToolPreview(block.name as string, input),
|
||||
name: block.name,
|
||||
preview: extractToolPreview(block.name, input),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ const SCROLL_THRESHOLD = 300;
|
|||
/** Must match the `w-80` (320px) context panel width used in the layout below. */
|
||||
const CONTEXT_PANEL_WIDTH_PX = 320;
|
||||
|
||||
import { formatPercentOfTotal, sumContextInjectionTokens } from '@renderer/utils/contextMath';
|
||||
|
||||
import { ChatHistoryEmptyState } from './ChatHistoryEmptyState';
|
||||
import { ChatHistoryItem } from './ChatHistoryItem';
|
||||
import { ChatHistoryLoadingState } from './ChatHistoryLoadingState';
|
||||
|
||||
import { formatPercentOfTotal, sumContextInjectionTokens } from '@renderer/utils/contextMath';
|
||||
import type { ContextInjection } from '@renderer/types/contextInjection';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import { useCallback, useState } from 'react';
|
|||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { getTeamColorSet } from '@renderer/constants/teamColors';
|
||||
import { nameColorSet } from '@renderer/utils/projectColor';
|
||||
import { useStore } from '@renderer/store';
|
||||
import { nameColorSet } from '@renderer/utils/projectColor';
|
||||
import {
|
||||
Activity,
|
||||
Bell,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||
|
||||
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
||||
import { MemberBadge } from '@renderer/components/team/MemberBadge';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
||||
import {
|
||||
CARD_BG,
|
||||
CARD_BG_ZEBRA,
|
||||
|
|
@ -10,7 +11,6 @@ import {
|
|||
CARD_TEXT_LIGHT,
|
||||
} from '@renderer/constants/cssVariables';
|
||||
import { getTeamColorSet } from '@renderer/constants/teamColors';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
||||
import { useStore } from '@renderer/store';
|
||||
import { formatToolSummary, parseToolSummary } from '@shared/utils/toolSummary';
|
||||
|
||||
|
|
@ -102,13 +102,13 @@ function isRecentTimestamp(timestamp: string): boolean {
|
|||
return Date.now() - t <= LIVE_WINDOW_MS;
|
||||
}
|
||||
|
||||
function ToolSummaryTooltipContent({
|
||||
const ToolSummaryTooltipContent = ({
|
||||
toolCalls,
|
||||
toolSummary,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
toolCalls?: ToolCallMeta[];
|
||||
toolSummary?: string;
|
||||
}): JSX.Element {
|
||||
}>): JSX.Element => {
|
||||
if (toolCalls && toolCalls.length > 0) {
|
||||
return (
|
||||
<div className="flex max-h-[300px] flex-col gap-0.5 overflow-y-auto">
|
||||
|
|
@ -118,14 +118,14 @@ function ToolSummaryTooltipContent({
|
|||
{toolCalls.map((tc, i) => {
|
||||
const isAgent = tc.name === 'Agent' || tc.name === 'TaskCreate';
|
||||
return (
|
||||
<div key={i} className={`flex items-baseline gap-2 ${isAgent ? 'mt-0.5' : ''}`}>
|
||||
<div key={i} className={isAgent ? 'mt-0.5' : 'flex items-baseline gap-2'}>
|
||||
<span className={`shrink-0 font-semibold ${isAgent ? 'text-violet-400' : ''}`}>
|
||||
{isAgent ? '🤖 ' : ''}
|
||||
{tc.name}
|
||||
</span>
|
||||
{tc.preview && (
|
||||
<span
|
||||
className={`text-text-secondary ${isAgent ? 'whitespace-pre-wrap' : 'truncate'}`}
|
||||
className={`text-text-secondary ${isAgent ? 'mt-0.5 block text-[10px]' : 'truncate'}`}
|
||||
>
|
||||
{tc.preview}
|
||||
</span>
|
||||
|
|
@ -158,7 +158,7 @@ function ToolSummaryTooltipContent({
|
|||
}
|
||||
|
||||
return <span>{toolSummary ?? ''}</span>;
|
||||
}
|
||||
};
|
||||
|
||||
export const LeadThoughtsGroupRow = ({
|
||||
group,
|
||||
|
|
@ -268,7 +268,7 @@ export const LeadThoughtsGroupRow = ({
|
|||
});
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- scrollRef is stable
|
||||
}, []);
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
const el = scrollRef.current;
|
||||
|
|
@ -318,7 +318,7 @@ export const LeadThoughtsGroupRow = ({
|
|||
{totalToolSummary}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="font-mono text-[11px]">
|
||||
<TooltipContent side="bottom" className="max-w-[420px] font-mono text-[11px]">
|
||||
<ToolSummaryTooltipContent
|
||||
toolCalls={allToolCalls}
|
||||
toolSummary={totalToolSummary}
|
||||
|
|
@ -344,7 +344,7 @@ export const LeadThoughtsGroupRow = ({
|
|||
{chronologicalThoughts.map((thought, idx) => (
|
||||
<div key={thought.messageId ?? idx} className="thought-expand-in">
|
||||
{idx > 0 && (
|
||||
<div className="mx-auto flex w-[40%] items-center justify-center gap-[5px] py-px">
|
||||
<div className="mx-auto flex w-2/5 items-center justify-center gap-[5px] py-px">
|
||||
<hr
|
||||
className="flex-1 border-0"
|
||||
style={{
|
||||
|
|
@ -386,7 +386,11 @@ export const LeadThoughtsGroupRow = ({
|
|||
🔧 {thought.toolSummary}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="start" className="font-mono text-[11px]">
|
||||
<TooltipContent
|
||||
side="top"
|
||||
align="start"
|
||||
className="max-w-[420px] font-mono text-[11px]"
|
||||
>
|
||||
<ToolSummaryTooltipContent
|
||||
toolCalls={thought.toolCalls}
|
||||
toolSummary={thought.toolSummary}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { RoleSelect } from '@renderer/components/team/RoleSelect';
|
||||
import { TeamModelSelector } from '@renderer/components/team/dialogs/TeamModelSelector';
|
||||
import { RoleSelect } from '@renderer/components/team/RoleSelect';
|
||||
import { Button } from '@renderer/components/ui/button';
|
||||
import { Input } from '@renderer/components/ui/input';
|
||||
import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea';
|
||||
|
|
|
|||
|
|
@ -607,16 +607,18 @@ export const MentionableTextarea = React.forwardRef<HTMLTextAreaElement, Mention
|
|||
const [tipIndex, setTipIndex] = React.useState(0);
|
||||
const [tipVisible, setTipVisible] = React.useState(true);
|
||||
|
||||
const advanceTip = React.useCallback(() => {
|
||||
setTipIndex((prev) => (prev + 1) % rotatingTips.length);
|
||||
setTipVisible(true);
|
||||
}, [rotatingTips.length]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTipVisible(false);
|
||||
setTimeout(() => {
|
||||
setTipIndex((prev) => (prev + 1) % rotatingTips.length);
|
||||
setTipVisible(true);
|
||||
}, 300);
|
||||
setTimeout(advanceTip, 300);
|
||||
}, 10000);
|
||||
return () => clearInterval(interval);
|
||||
}, [rotatingTips.length]);
|
||||
}, [advanceTip]);
|
||||
|
||||
const resolvedHintText = hintText ?? rotatingTips[tipIndex];
|
||||
const showHintRow = showHint && (suggestions.length > 0 || enableFiles);
|
||||
|
|
|
|||
|
|
@ -607,7 +607,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
const prevByName = get().teamByName;
|
||||
const existingEntry = prevByName[teamName];
|
||||
const configColor = data.config.color;
|
||||
if (configColor && (!existingEntry || existingEntry.color !== configColor)) {
|
||||
if (configColor && (!existingEntry || existingEntry?.color !== configColor)) {
|
||||
const patched: TeamSummary = existingEntry
|
||||
? { ...existingEntry, color: configColor, displayName: data.config.name || teamName }
|
||||
: {
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ export function buildToolSummary(content: Record<string, unknown>[]): string | u
|
|||
|
||||
export function parseToolSummary(summary: string | undefined): ToolSummaryData | null {
|
||||
if (!summary) return null;
|
||||
const match = summary.match(/^(\d+)\s+tools?\s+\(([^)]+)\)$/);
|
||||
const match = /^(\d+)\s+tools?\s+\(([^)]+)\)$/.exec(summary);
|
||||
if (!match) return null;
|
||||
const byName: Record<string, number> = {};
|
||||
for (const part of match[2].split(', ')) {
|
||||
const m = part.match(/^(\d+)\s+(.+)$/);
|
||||
const m = /^(\d+)\s+(\S+(?:\s+\S+)*)$/.exec(part);
|
||||
if (m) {
|
||||
byName[m[2]] = parseInt(m[1], 10);
|
||||
} else {
|
||||
|
|
@ -96,9 +96,9 @@ export function extractToolPreview(
|
|||
case 'Agent':
|
||||
case 'TaskCreate':
|
||||
return typeof input.prompt === 'string'
|
||||
? truncateStr(input.prompt, 200)
|
||||
? input.prompt
|
||||
: typeof input.description === 'string'
|
||||
? truncateStr(input.description, 200)
|
||||
? input.description
|
||||
: undefined;
|
||||
case 'WebFetch':
|
||||
if (typeof input.url === 'string') {
|
||||
|
|
|
|||
Loading…
Reference in a new issue