Merge pull request #80 from IronRookieCoder/fix/issues-31-32

fix: 修复任务列表数字ID乱序和内存轮询导致滚动跳动
This commit is contained in:
geroge 2026-05-13 19:37:25 +08:00 committed by GitHub
commit d3d0dd25d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 26 additions and 5 deletions

View File

@ -69,7 +69,7 @@ export const TaskCreateTool = buildTool({
return isTodoV2Enabled()
},
isConcurrencySafe() {
return true
return false
},
toAutoClassifierInput(input) {
return input.subject

View File

@ -58,7 +58,7 @@ const RSS_UPDATE_INTERVAL_MS = 5_000;
type RssState = { text: string; level: 'normal' | 'warning' | 'error' };
function useRssDisplay(): RssState | null {
function useRssDisplay(isLoading: boolean): RssState | null {
const [state, setState] = useState<RssState | null>(null);
useEffect(() => {
function update(): void {
@ -68,9 +68,10 @@ function useRssDisplay(): RssState | null {
setState(prev => (prev?.text === text ? prev : { text, level }));
}
update();
if (!isLoading) return;
const timer = setInterval(update, RSS_UPDATE_INTERVAL_MS);
return () => clearInterval(timer);
}, []);
}, [isLoading]);
return state;
}
@ -279,7 +280,7 @@ function ModeIndicator({
}
}, [voiceEnabled, voiceHintUnderCap]);
const isKillAgentsConfirmShowing = useAppState(s => s.notifications.current?.key === 'kill-agents-confirm');
const rssState = useRssDisplay();
const rssState = useRssDisplay(isLoading);
// Derive team info from teamContext (no filesystem I/O needed)
// Match the same logic as TeamStatus to avoid trailing separator

View File

@ -46,6 +46,7 @@ import {
setLeaderTeamName,
clearLeaderTeamName,
isTodoV2Enabled,
compareTaskIds,
type Task,
} from '../tasks'
@ -349,6 +350,11 @@ describe('listTasks', () => {
const subjects = tasks.map(t => t.subject).sort()
expect(subjects).toEqual(['A', 'B'])
})
test('sorts task IDs numerically', () => {
const ids = ['3', '10', '2', '1']
expect(ids.sort(compareTaskIds)).toEqual(['1', '2', '3', '10'])
})
})
// ---------------------------------------------------------------------------

View File

@ -1,6 +1,7 @@
import { randomUUID } from 'crypto'
import type { SDKMessage } from 'src/entrypoints/agentSdkTypes.js'
import type { Task } from './tasks.js'
import { compareTaskIds } from './tasks.js'
export type TaskStateItem = Pick<
Task,
@ -37,7 +38,7 @@ function toTaskStateItem(task: Task): TaskStateItem {
}
function compareTaskStateItems(a: TaskStateItem, b: TaskStateItem): number {
return a.id.localeCompare(b.id)
return compareTaskIds(a.id, b.id)
}
export function buildTaskStateSnapshot(

View File

@ -230,6 +230,18 @@ export function getTaskPath(taskListId: string, taskId: string): string {
return join(getTasksDir(taskListId), `${sanitizePathComponent(taskId)}.json`)
}
export function compareTaskIds(a: string, b: string): number {
const aIsNumeric = /^\d+$/.test(a)
const bIsNumeric = /^\d+$/.test(b)
if (aIsNumeric && bIsNumeric) {
return Number(a) - Number(b)
}
if (aIsNumeric !== bIsNumeric) {
return aIsNumeric ? -1 : 1
}
return a.localeCompare(b, undefined, { numeric: true })
}
export async function ensureTasksDir(taskListId: string): Promise<void> {
const dir = getTasksDir(taskListId)
try {
@ -451,6 +463,7 @@ export async function listTasks(taskListId: string): Promise<Task[]> {
const taskIds = files
.filter(f => f.endsWith('.json'))
.map(f => f.replace('.json', ''))
.sort(compareTaskIds)
const results = await Promise.all(taskIds.map(id => getTask(taskListId, id)))
return results.filter((t): t is Task => t !== null)
}