Skip to content

feat(retry): ✨ Add model-switch retry for task/page flows#63

Merged
jorben merged 3 commits intomasterfrom
feat/retry-model-switch-detail-and-list-20260305
Mar 5, 2026
Merged

feat(retry): ✨ Add model-switch retry for task/page flows#63
jorben merged 3 commits intomasterfrom
feat/retry-model-switch-detail-and-list-20260305

Conversation

@jorben
Copy link
Collaborator

@jorben jorben commented Mar 5, 2026

Summary

  • Add local task-level retry support (task:retry) with optional provider/model override, and extend page retry payloads for model switching.
  • Add cloud retry model override support for both task retry and page retry (lite/pro/ultra) across service, IPC, preload, and context layers.
  • Update detail and list pages to support retry-with-model UX, including completed-task retry from the More Actions menu.
  • Ensure single-page local retry override is one-time by restoring page model/provider back to task defaults after completion.
  • Update i18n and tests; change npm test to vitest run so default test command exits instead of watch mode.

Test Plan

  • npm run test:unit -- --silent
  • npm run test:renderer -- --silent
  • npm run test:renderer -- --run src/renderer/pages/__tests__/CloudPreview.test.tsx src/renderer/pages/__tests__/Preview.test.tsx --silent

🤖 Generated with Codex Cli

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

AI Code Review Summary

PR: #63 (feat(retry): ✨ Add model-switch retry for task/page flows)
Preferred language: English

Overall Assessment

No blocking issue was detected in the reviewed diff; keep focused regression testing before merge.

Major Findings by Severity

No major issues identified from the reviewed diff.

Actionable Suggestions

  • Address the highest severity findings first and add targeted tests for changed logic.

Potential Risks

  • Potential hidden risks remain in edge cases not covered by the current diff context.

Test Suggestions

  • Add happy-path + boundary + failure-path tests for touched modules.

File-Level Coverage Notes

  • src/core/application/workers/ConverterWorker.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/core/application/workers/tests/ConverterWorker.test.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/core/infrastructure/services/CloudService.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/core/infrastructure/services/tests/CloudService.test.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/main/ipc/tests/handlers.test.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/main/ipc/handlers/tests/cloud.handler.test.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/main/ipc/handlers/tests/task.handler.test.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/main/ipc/handlers/tests/taskDetail.handler.test.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/main/ipc/handlers/cloud.handler.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/main/ipc/handlers/task.handler.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/main/ipc/handlers/taskDetail.handler.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/preload/electron.d.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/preload/index.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/renderer/contexts/CloudContext.tsx: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/renderer/contexts/CloudContextDefinition.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/renderer/contexts/tests/CloudContext.test.tsx: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/renderer/electron.d.ts: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/renderer/pages/CloudPreview.tsx: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/renderer/pages/List.tsx: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • src/renderer/pages/Preview.tsx: File was in scope but not fully reviewed before budget limits. (coverage_incomplete)
  • ... and 5 more file-level entries.

Inline Downgraded Items (processed but not inline)

  • None

Coverage Status

  • Target files: 25
  • Covered files: 0
  • Uncovered files: 25
  • No-patch/binary covered as file-level: 0
  • Findings with unknown confidence (N/A): 0

Uncovered list:

  • src/core/application/workers/ConverterWorker.ts: coverage_incomplete
  • src/core/application/workers/tests/ConverterWorker.test.ts: coverage_incomplete
  • src/core/infrastructure/services/CloudService.ts: coverage_incomplete
  • src/core/infrastructure/services/tests/CloudService.test.ts: coverage_incomplete
  • src/main/ipc/tests/handlers.test.ts: coverage_incomplete
  • src/main/ipc/handlers/tests/cloud.handler.test.ts: coverage_incomplete
  • src/main/ipc/handlers/tests/task.handler.test.ts: coverage_incomplete
  • src/main/ipc/handlers/tests/taskDetail.handler.test.ts: coverage_incomplete
  • src/main/ipc/handlers/cloud.handler.ts: coverage_incomplete
  • src/main/ipc/handlers/task.handler.ts: coverage_incomplete
  • src/main/ipc/handlers/taskDetail.handler.ts: coverage_incomplete
  • src/preload/electron.d.ts: coverage_incomplete
  • src/preload/index.ts: coverage_incomplete
  • src/renderer/contexts/CloudContext.tsx: coverage_incomplete
  • src/renderer/contexts/CloudContextDefinition.ts: coverage_incomplete
  • src/renderer/contexts/tests/CloudContext.test.tsx: coverage_incomplete
  • src/renderer/electron.d.ts: coverage_incomplete
  • src/renderer/pages/CloudPreview.tsx: coverage_incomplete
  • src/renderer/pages/List.tsx: coverage_incomplete
  • src/renderer/pages/Preview.tsx: coverage_incomplete
  • src/renderer/pages/tests/CloudPreview.test.tsx: coverage_incomplete
  • src/renderer/pages/tests/List.test.tsx: coverage_incomplete
  • src/renderer/pages/tests/Preview.test.tsx: coverage_incomplete
  • src/shared/ipc/channels.ts: coverage_incomplete
  • tests/setup.renderer.ts: coverage_incomplete

No-patch covered list:

  • None

Runtime/Budget

  • Rounds used: 1/4
  • Planned batches: 4
  • Executed batches: 4
  • Sub-agent runs: 4
  • Planner calls: 1
  • Reviewer calls: 8
  • Model calls: 9/64
  • Structured-output summary-only degradation: YES

Reasons:

  • reviewer_structured_output_failed_round_1_general: Structured output failed after repair: Invalid output type
  • reviewer_structured_output_failed_round_1_general: Structured output failed after repair: Invalid output type
  • reviewer_structured_output_failed_round_1_general: Structured output failed after repair: Invalid output type
  • reviewer_structured_output_failed_round_1_general: Structured output failed after repair: Invalid output type

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Automated PR review completed.

  • Findings kept: 16
  • Findings with unknown confidence: 0
  • Inline comments attempted: 16
  • Target files: 25
  • Covered files: 25
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

const handleRetryTask = (id: string) => {
handleUpdateTaskStatus(id, 1, t('actions.retry'));
// 本地任务重试(支持切换模型)
const handleRetryTask = async (task: Task | CloudTask) => {

This comment was marked as outdated.

const { modelId, providerId } = parseModelValue(selectedModelValue);
const shouldOverride = selectedModelValue !== defaultModelValue;
const result = await window.api.task.retry({
taskId: id,

This comment was marked as outdated.

@@ -284,28 +314,60 @@ const Preview: React.FC = () => {

// 重试任务(全部重试)
const handleRetryTask = async () => {

This comment was marked as outdated.

pages: 10,
completed_count: 5,
failed_count: 0,
provider: 1,

This comment was marked as outdated.

})
})

it('retryTask/retryPage send model override payload when model is provided', async () => {

This comment was marked as outdated.

it('should update page status to COMPLETED', async () => {
let pageUpdateData: any;

vi.mocked(prisma.$transaction).mockImplementation(async (callback: any) => {

This comment was marked as outdated.

})
})

it('retryTask/retryPage send model override payload when model is provided', async () => {

This comment was marked as outdated.

expect(mockCloudService.deleteTask).toHaveBeenCalledWith('t1')
})

it('passes model override for retryTask and retryPage', async () => {

This comment was marked as outdated.

})
})

it('should retry page with model override', async () => {

This comment was marked as outdated.

ipcRenderer.invoke("taskDetail:getAllByTask", taskId),
retry: (pageId: number) =>
ipcRenderer.invoke("taskDetail:retry", pageId),
retry: (params: number | { pageId: number; providerId?: number; modelId?: string }) =>

This comment was marked as outdated.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Automated PR review completed.

  • Findings kept: 18
  • Findings with unknown confidence: 0
  • Inline comments attempted: 18
  • Target files: 25
  • Covered files: 25
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

provider: targetProvider,
model: targetModel,
model_name: targetModelName,
status: detailCount > 0 ? TaskStatus.PROCESSING : TaskStatus.PENDING,
Copy link

Choose a reason for hiding this comment

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

[HIGH] Task retry sets status=PROCESSING without assigning workers, risking stuck tasks

On retry, tasks with existing page details are moved directly to PROCESSING while all worker assignments are cleared. If the orchestrator only picks up PENDING tasks/pages, this can leave retried tasks in PROCESSING but never re-queued.

Suggestion: Set retried tasks to PENDING consistently (and let scheduler transition to PROCESSING when actually claimed), or explicitly enqueue/emit an event path that guarantees PROCESSING tasks with reset pages are picked up immediately.

Risk: Retried tasks can appear active but make no progress, causing user-facing stalls and requiring manual intervention.

Confidence: 0.92

[From SubAgent: general]

})
})

describe('task:retry', () => {
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] Missing negative-path tests for model/provider validation in task retry handler

New tests do not cover key failure branches likely present in retry override logic (invalid provider/model combinations), leaving regressions undetected.

Suggestion: Add cases for: provider not found, provider inactive, model not found, model-provider mismatch, and DB errors during validation/update. Assert exact error payloads and that no event is emitted on failure.

Risk: Invalid override inputs could be accepted or fail unclearly in production without tests catching regressions.

Confidence: 0.90

[From SubAgent: testing]

})
})

it('should retry page with model override', async () => {
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] Task detail retry tests lack assertions for status/progress/event side effects

The added test validates return shape but not orchestration side effects that are critical for worker scheduling and UI updates.

Suggestion: Assert tx.task.update invocation (expected status/progress transitions), taskDetail.update payload fields, and event bus emissions (or explicit non-emission) for retry operations.

Risk: Retry may return success while silently skipping task state/event updates, causing stuck queues or stale UI.

Confidence: 0.84

[From SubAgent: testing]

IPC_CHANNELS.TASK.RETRY,
async (
_,
params: string | { taskId: string; providerId?: number; modelId?: string }
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] Missing compatibility test for legacy task retry invocation shape

New dual input API (string or object) plus paired override validation adds branches that are easy to regress without explicit tests.

Suggestion: Add IPC handler tests for task:retry covering: (1) legacy string input success, (2) object input with no override success, (3) only providerId provided -> error, (4) only modelId provided -> error, (5) both provided with invalid provider/model -> error, (6) both valid -> provider/model switched on task and details.

Risk: Renderer/preload or older callers may silently break after refactors; partial override handling may regress and produce inconsistent retry behavior.

Confidence: 0.93

[From SubAgent: testing]

});

if (detailCount > 0) {
await tx.taskDetail.updateMany({
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] No explicit tests for retry state-reset invariants and emitted events

Retry now performs a broad state reset (counts/tokens/content/provider/model) and emits two task events; these are critical invariants but not evidenced by tests in this diff.

Suggestion: Add transactional handler tests asserting post-retry invariants for both branches (detailCount > 0 and detailCount == 0), including status/progress counters reset, detail fields reset, provider/model propagation, and exact event emissions (TASK_UPDATED, TASK_STATUS_CHANGED) with expected payloads.

Risk: Without invariant/event tests, subtle regressions can cause stuck tasks, incorrect UI progress, or missing realtime updates despite successful API responses.

Confidence: 0.90

[From SubAgent: testing]

})
})

it('retryTask/retryPage send model override payload when model is provided', async () => {
Copy link

Choose a reason for hiding this comment

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

[LOW] CloudService override payload tests miss boundary case when model is omitted

Only the provided-model branch is tested; omission behavior (no body vs empty body) remains unguarded.

Suggestion: Add tests for retryTask(id) and retryPage(id,page) without model to assert exact request options and prevent accidental API contract drift.

Risk: A subtle request-shape regression could break backward compatibility with server endpoints expecting optional model fields.

Confidence: 0.85

[From SubAgent: testing]

expect(window.api.cloud.getTaskPages).toHaveBeenCalledWith({ taskId: 't1', page: 1, pageSize: 2 })
expect(window.api.cloud.cancelTask).toHaveBeenCalledWith('t1')
expect(window.api.cloud.retryTask).toHaveBeenCalledWith('t1')
expect(window.api.cloud.retryTask).toHaveBeenCalledWith({ id: 't1', model: 'pro' })
Copy link

Choose a reason for hiding this comment

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

[LOW] Multiple toHaveBeenCalledWith assertions on same mock can hide call-order/intent issues

The test now asserts two different argument shapes on the same mock with repeated toHaveBeenCalledWith, which only checks that each call happened at least once and may miss ordering or accidental extra calls.

Suggestion: Use toHaveBeenNthCalledWith (or inspect mock.calls) to verify exact sequence and payload per invocation. Optionally assert total call count to guard against unexpected extra calls.

Risk: False positives in tests can allow regressions in retry argument mapping/order to pass unnoticed.

Confidence: 0.88

[From SubAgent: general]

'actions.cancel': 'Cancel',
'actions.retry': 'Retry',
'actions.delete': 'Delete',
'retry.confirm_with_model': 'Retry Task (Choose Model)',
Copy link

Choose a reason for hiding this comment

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

[LOW] List page mocks expanded for retry-model UX but no behavioral assertions added

Test setup now includes retry-with-model strings and methods, but no new assertions verify that UI uses them correctly.

Suggestion: Add tests for opening retry dialog, loading model list, handling load failure (retry.load_models_failed), empty models (retry.no_models_available), and submitting selected model.

Risk: Localization or retry dialog logic could regress with tests still passing due to unused mocks.

Confidence: 0.89

[From SubAgent: testing]

expect(screen.queryByText('Retry All')).not.toBeInTheDocument()
})

it('should show retry all in More Actions menu for completed tasks', async () => {
Copy link

Choose a reason for hiding this comment

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

[LOW] No assertion of retry-all action behavior after menu visibility in local Preview

The new test validates visibility of 'Retry All' but not that clicking it triggers task.retry with expected payload/model selection.

Suggestion: Extend test to click 'Retry All' and assert window.api.task.retry invocation shape, plus success/error feedback expectations.

Risk: Menu option can render correctly while being disconnected or incorrectly wired to backend action.

Confidence: 0.91

[From SubAgent: testing]

label: t(`retry_model.${tier}`),
}));
const taskTier = (task?.model_tier || 'lite') as CloudModelTier;
let selectedModel: CloudModelTier = CLOUD_MODEL_TIERS.includes(taskTier) ? taskTier : 'lite';
Copy link

Choose a reason for hiding this comment

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

[LOW] Insufficient tests for cloud retry model defaulting/fallback logic

New fallback from task.model_tier to 'lite' lacks visible tests for invalid/unknown tiers and for preserving selected tier through modal interaction.

Suggestion: Add tests for: undefined model_tier, invalid model_tier, expected default selection, and API being called with user-selected tier after onChange.

Risk: Wrong model tier can be submitted silently, leading to unexpected cost/performance behavior for users.

Confidence: 0.87

[From SubAgent: testing]

@jorben jorben merged commit 9d28004 into master Mar 5, 2026
3 checks passed
@jorben jorben deleted the feat/retry-model-switch-detail-and-list-20260305 branch March 5, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant