Skip to content

Commit cc13e60

Browse files
authored
🤖 fix: prevent infinite retry loop for non-retryable errors (#485)
## Problem When users hit non-retryable errors (e.g., `api_key_not_found`, `authentication`, `quota`), the system would infinitely retry without user intervention. UI showed "Retrying..." indefinitely with no error message, and the manual retry button was non-functional. ## Root Causes **Auto-retry didn't distinguish error types:** Two error systems existed (`SendMessageError` from resumeStream failures, `StreamErrorType` in stream-error messages). `isEligibleForAutoRetry()` only checked the latter, missing `api_key_not_found` errors stored in `RetryState.lastError`. **RetryBarrier didn't receive updates:** `useResumeManager` used `localStorage.setItem()` directly while `RetryBarrier` used `usePersistedState` with `listener: true` expecting custom events. **Manual retry was blocked:** `isEligibleForResume()` treated manual retries the same as auto-retries, blocking user intent after fixing issues. ## Solution **Split retry eligibility logic** into three functions in `src/utils/messages/retryEligibility.ts`: - `hasInterruptedStream()` - Shows UI for ALL errors (user may have fixed it) - `isEligibleForAutoRetry()` - Filters non-retryable stream errors (authentication, quota, model_not_found, context_exceeded, aborted) - `isNonRetryableSendError()` - Checks SendMessageError types (api_key_not_found, provider_not_supported, invalid_model_string) **Fixed event synchronization:** `useResumeManager` now uses `updatePersistedState()` which dispatches custom events that listeners receive. **Made RetryBarrier self-contained:** Following `AgentStatusIndicator` pattern, accepts only `workspaceId` prop, fetches workspace state directly, manages own `autoRetry` state, computes `effectiveAutoRetry` internally. Removed ~37 lines from AIView.tsx. **Added manual retry bypass:** `RESUME_CHECK_REQUESTED` events now include `isManual` flag. Manual retry bypasses eligibility checks, respecting user intent. **Centralized event type system:** Created `CustomEventPayloads` interface and `createCustomEvent()` helper in `src/constants/events.ts` for type-safe event handling. **Centralized error formatting:** Created `formatSendMessageError()` in `src/utils/errors/formatSendError.ts`, eliminating duplicate error message strings. ## Behavior After Fix **Non-retryable errors:** Auto-retry stops immediately, UI shows manual "Retry" button (not "Retrying..."), error message displayed clearly, user can fix and retry. **Retryable errors:** Auto-retry with exponential backoff (1s → 2s → 4s → ... → 60s max), UI shows countdown, user can stop with Ctrl+C. ## Testing All 30 tests in `retryEligibility.test.ts` pass, verifying correct filtering of non-retryable errors and edge cases. _Generated with `cmux`_
1 parent 31a3c6a commit cc13e60

File tree

19 files changed

+765
-144
lines changed

19 files changed

+765
-144
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,5 @@ storybook-static/
106106
*.tgz
107107
src/test-workspaces/
108108
terminal-bench-results/
109+
nodemon.json
110+
test_hot_reload.sh

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,19 @@ dev: node_modules/.installed build-main ## Start development server (Vite + tsgo
9696
"bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \
9797
"vite"
9898

99+
dev-server: node_modules/.installed build-main ## Start server mode with hot reload (backend :3000 + frontend :5173). Use VITE_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0 for remote access
100+
@echo "Starting dev-server..."
101+
@echo " Backend (IPC/WebSocket): http://$(or $(BACKEND_HOST),localhost):$(or $(BACKEND_PORT),3000)"
102+
@echo " Frontend (with HMR): http://$(or $(VITE_HOST),localhost):$(or $(VITE_PORT),5173)"
103+
@echo ""
104+
@echo "For remote access: make dev-server VITE_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0"
105+
@bun x concurrently -k \
106+
"bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \
107+
"bun x nodemon --watch dist/main.js --watch dist/main-server.js --delay 500ms --exec 'node dist/main.js server --host $(or $(BACKEND_HOST),localhost) --port $(or $(BACKEND_PORT),3000)'" \
108+
"CMUX_VITE_HOST=$(or $(VITE_HOST),127.0.0.1) CMUX_VITE_PORT=$(or $(VITE_PORT),5173) VITE_BACKEND_URL=http://$(or $(BACKEND_HOST),localhost):$(or $(BACKEND_PORT),3000) vite"
109+
110+
111+
99112
start: node_modules/.installed build-main build-preload build-static ## Build and start Electron app
100113
@bun x electron --remote-debugging-port=9222 .
101114

bun.lock

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@tailwindcss/vite": "^4.1.15",
4949
"@testing-library/react": "^16.3.0",
5050
"@types/bun": "^1.2.23",
51+
"@types/commander": "^2.12.5",
5152
"@types/cors": "^2.8.19",
5253
"@types/diff": "^8.0.0",
5354
"@types/escape-html": "^1.0.4",
@@ -84,6 +85,7 @@
8485
"eslint-plugin-tailwindcss": "4.0.0-beta.0",
8586
"jest": "^30.1.3",
8687
"mermaid": "^11.12.0",
88+
"nodemon": "^3.1.10",
8789
"playwright": "^1.56.0",
8890
"postcss": "^8.5.6",
8991
"posthog-js": "^1.276.0",
@@ -708,6 +710,8 @@
708710

709711
"@types/chai": ["@types/[email protected]", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
710712

713+
"@types/commander": ["@types/[email protected]", "", { "dependencies": { "commander": "*" } }, "sha512-YXGZ/rz+s57VbzcvEV9fUoXeJlBt5HaKu5iUheiIWNsJs23bz6AnRuRiZBRVBLYyPnixNvVnuzM5pSaxr8Yp/g=="],
714+
711715
"@types/connect": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
712716

713717
"@types/cors": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
@@ -1186,7 +1190,7 @@
11861190

11871191
"comma-separated-tokens": ["[email protected]", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
11881192

1189-
"commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
1193+
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
11901194

11911195
"commondir": ["[email protected]", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
11921196

@@ -1724,6 +1728,8 @@
17241728

17251729
"ignore": ["[email protected]", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
17261730

1731+
"ignore-by-default": ["[email protected]", "", {}, "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="],
1732+
17271733
"immediate": ["[email protected]", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
17281734

17291735
"import-fresh": ["[email protected]", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
@@ -2210,6 +2216,8 @@
22102216

22112217
"node-releases": ["[email protected]", "", {}, "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA=="],
22122218

2219+
"nodemon": ["[email protected]", "", { "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" } }, "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw=="],
2220+
22132221
"normalize-path": ["[email protected]", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
22142222

22152223
"normalize-range": ["[email protected]", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="],
@@ -2358,6 +2366,8 @@
23582366

23592367
"proxy-from-env": ["[email protected]", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
23602368

2369+
"pstree.remy": ["[email protected]", "", {}, "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="],
2370+
23612371
"pump": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
23622372

23632373
"punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
@@ -2674,6 +2684,8 @@
26742684

26752685
"toidentifier": ["[email protected]", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
26762686

2687+
"touch": ["[email protected]", "", { "bin": { "nodetouch": "bin/nodetouch.js" } }, "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA=="],
2688+
26772689
"tree-kill": ["[email protected]", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
26782690

26792691
"trim-lines": ["[email protected]", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
@@ -2724,6 +2736,8 @@
27242736

27252737
"unbox-primitive": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
27262738

2739+
"undefsafe": ["[email protected]", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="],
2740+
27272741
"undici": ["[email protected]", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="],
27282742

27292743
"undici-types": ["[email protected]", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
@@ -3042,8 +3056,6 @@
30423056

30433057
"find-process/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
30443058

3045-
"find-process/commander": ["[email protected]", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
3046-
30473059
"foreground-child/signal-exit": ["[email protected]", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
30483060

30493061
"form-data/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
@@ -3184,6 +3196,10 @@
31843196

31853197
"mlly/pkg-types": ["[email protected]", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
31863198

3199+
"nodemon/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
3200+
3201+
"nodemon/supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
3202+
31873203
"nyc/convert-source-map": ["[email protected]", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
31883204

31893205
"nyc/find-up": ["[email protected]", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
@@ -3252,6 +3268,8 @@
32523268

32533269
"ts-jest/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
32543270

3271+
"tsc-alias/commander": ["[email protected]", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
3272+
32553273
"unzip-crx-3/mkdirp": ["[email protected]", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
32563274

32573275
"vite/fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -3524,6 +3542,8 @@
35243542

35253543
"mlly/pkg-types/confbox": ["[email protected]", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
35263544

3545+
"nodemon/supports-color/has-flag": ["[email protected]", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
3546+
35273547
"nyc/find-up/locate-path": ["[email protected]", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
35283548

35293549
"nyc/yargs/cliui": ["[email protected]", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,14 @@
8282
"devDependencies": {
8383
"@eslint/js": "^9.36.0",
8484
"@playwright/test": "^1.56.0",
85+
"@storybook/addon-docs": "^10.0.0",
8586
"@storybook/addon-links": "^10.0.0",
8687
"@storybook/react-vite": "^10.0.0",
8788
"@storybook/test-runner": "^0.24.0",
8889
"@tailwindcss/vite": "^4.1.15",
8990
"@testing-library/react": "^16.3.0",
9091
"@types/bun": "^1.2.23",
92+
"@types/commander": "^2.12.5",
9193
"@types/cors": "^2.8.19",
9294
"@types/diff": "^8.0.0",
9395
"@types/escape-html": "^1.0.4",
@@ -120,9 +122,11 @@
120122
"eslint": "^9.36.0",
121123
"eslint-plugin-react": "^7.37.5",
122124
"eslint-plugin-react-hooks": "^5.2.0",
125+
"eslint-plugin-storybook": "10.0.0",
123126
"eslint-plugin-tailwindcss": "4.0.0-beta.0",
124127
"jest": "^30.1.3",
125128
"mermaid": "^11.12.0",
129+
"nodemon": "^3.1.10",
126130
"playwright": "^1.56.0",
127131
"postcss": "^8.5.6",
128132
"posthog-js": "^1.276.0",
@@ -146,9 +150,7 @@
146150
"typescript-eslint": "^8.45.0",
147151
"vite": "^7.1.11",
148152
"vite-plugin-svgr": "^4.5.0",
149-
"vite-plugin-top-level-await": "^1.6.0",
150-
"eslint-plugin-storybook": "10.0.0",
151-
"@storybook/addon-docs": "^10.0.0"
153+
"vite-plugin-top-level-await": "^1.6.0"
152154
},
153155
"files": [
154156
"dist/**/*.js",

src/browser/api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import { IPC_CHANNELS, getChatChannel } from "@/constants/ipc-constants";
55
import type { IPCApi } from "@/types/ipc";
66

7-
const API_BASE = window.location.origin;
7+
// Backend URL - defaults to same origin, but can be overridden via VITE_BACKEND_URL
8+
// This allows frontend (Vite :8080) to connect to backend (:3000) in dev mode
9+
const API_BASE = import.meta.env.VITE_BACKEND_URL ?? window.location.origin;
810
const WS_BASE = API_BASE.replace("http://", "ws://").replace("https://", "wss://");
911

1012
interface InvokeResponse<T> {

src/components/AIView.tsx

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -74,20 +74,11 @@ const AIViewInner: React.FC<AIViewProps> = ({
7474
undefined
7575
);
7676

77-
// Auto-retry state (persisted per workspace, with cross-component sync)
78-
// Semantics:
79-
// true (default): System errors should auto-retry
80-
// false: User stopped this (Ctrl+C), don't auto-retry until user re-engages
81-
// State transitions are EXPLICIT only:
82-
// - User presses Ctrl+C → false
83-
// - User sends a message → true (clear intent: "I'm using this workspace")
84-
// - User clicks manual retry button → true
85-
// No automatic resets on stream events - prevents initialization bugs
86-
const [autoRetry, setAutoRetry] = usePersistedState<boolean>(
87-
getAutoRetryKey(workspaceId),
88-
true, // Default to true
89-
{ listener: true } // Enable cross-component synchronization
90-
);
77+
// Auto-retry state - minimal setter for keybinds and message sent handler
78+
// RetryBarrier manages its own state, but we need this for Ctrl+C keybind
79+
const [, setAutoRetry] = usePersistedState<boolean>(getAutoRetryKey(workspaceId), true, {
80+
listener: true,
81+
});
9182

9283
// Use auto-scroll hook for scroll management
9384
const {
@@ -408,14 +399,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
408399
);
409400
})}
410401
{/* Show RetryBarrier after the last message if needed */}
411-
{showRetryBarrier && (
412-
<RetryBarrier
413-
workspaceId={workspaceId}
414-
autoRetry={autoRetry}
415-
onStopAutoRetry={() => setAutoRetry(false)}
416-
onResetAutoRetry={() => setAutoRetry(true)}
417-
/>
418-
)}
402+
{showRetryBarrier && <RetryBarrier workspaceId={workspaceId} />}
419403
</>
420404
)}
421405
<PinnedTodoList workspaceId={workspaceId} />

src/components/ChatInputToasts.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Toast } from "./ChatInputToast";
33
import { SolutionLabel } from "./ChatInputToast";
44
import type { ParsedCommand } from "@/utils/slashCommands/types";
55
import type { SendMessageError as SendMessageErrorType } from "@/types/errors";
6+
import { formatSendMessageError } from "@/utils/errors/formatSendError";
67

78
/**
89
* Creates a toast message for command-related errors and help messages
@@ -146,55 +147,63 @@ export const createCommandToast = (parsed: ParsedCommand): Toast | null => {
146147
*/
147148
export const createErrorToast = (error: SendMessageErrorType): Toast => {
148149
switch (error.type) {
149-
case "api_key_not_found":
150+
case "api_key_not_found": {
151+
const formatted = formatSendMessageError(error);
150152
return {
151153
id: Date.now().toString(),
152154
type: "error",
153155
title: "API Key Not Found",
154156
message: `The ${error.provider} provider requires an API key to function.`,
155-
solution: (
157+
solution: formatted.providerCommand ? (
156158
<>
157159
<SolutionLabel>Quick Fix:</SolutionLabel>
158-
/providers set {error.provider} apiKey YOUR_API_KEY
160+
{formatted.providerCommand}
159161
</>
160-
),
162+
) : undefined,
161163
};
164+
}
162165

163-
case "provider_not_supported":
166+
case "provider_not_supported": {
167+
const formatted = formatSendMessageError(error);
164168
return {
165169
id: Date.now().toString(),
166170
type: "error",
167171
title: "Provider Not Supported",
168-
message: `The ${error.provider} provider is not supported yet.`,
172+
message: formatted.message,
169173
solution: (
170174
<>
171175
<SolutionLabel>Try This:</SolutionLabel>
172176
Use an available provider from /providers list
173177
</>
174178
),
175179
};
180+
}
176181

177-
case "invalid_model_string":
182+
case "invalid_model_string": {
183+
const formatted = formatSendMessageError(error);
178184
return {
179185
id: Date.now().toString(),
180186
type: "error",
181187
title: "Invalid Model Format",
182-
message: error.message,
188+
message: formatted.message,
183189
solution: (
184190
<>
185191
<SolutionLabel>Expected Format:</SolutionLabel>
186192
provider:model-name (e.g., anthropic:claude-opus-4-1)
187193
</>
188194
),
189195
};
196+
}
190197

191198
case "unknown":
192-
default:
199+
default: {
200+
const formatted = formatSendMessageError(error);
193201
return {
194202
id: Date.now().toString(),
195203
type: "error",
196204
title: "Message Send Failed",
197-
message: error.raw || "An unexpected error occurred while sending your message.",
205+
message: formatted.message,
198206
};
207+
}
199208
}
200209
};

src/components/CommandPalette.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useCommandRegistry } from "@/contexts/CommandRegistryContext";
44
import type { CommandAction } from "@/contexts/CommandRegistryContext";
55
import { formatKeybind, KEYBINDS, isEditableElement, matchesKeybind } from "@/utils/ui/keybinds";
66
import { getSlashCommandSuggestions } from "@/utils/slashCommands/suggestions";
7-
import { CUSTOM_EVENTS } from "@/constants/events";
7+
import { CUSTOM_EVENTS, createCustomEvent } from "@/constants/events";
88

99
interface CommandPaletteProps {
1010
getSlashContext?: () => { providerNames: string[]; workspaceId?: string };
@@ -189,9 +189,7 @@ export const CommandPalette: React.FC<CommandPaletteProps> = ({ getSlashContext
189189
shortcutHint: `${formatKeybind(KEYBINDS.SEND_MESSAGE)} to insert`,
190190
run: () => {
191191
const text = s.replacement;
192-
window.dispatchEvent(
193-
new CustomEvent(CUSTOM_EVENTS.INSERT_TO_CHAT_INPUT, { detail: { text } })
194-
);
192+
window.dispatchEvent(createCustomEvent(CUSTOM_EVENTS.INSERT_TO_CHAT_INPUT, { text }));
195193
},
196194
})),
197195
},

0 commit comments

Comments
 (0)