#1 — Untitled session can't be deleted
src/server/services/sessionService.ts:deleteSession is now idempotent.
Previously placeholder sessions whose JSONL file hadn't been flushed
yet would 404 the delete and stay stuck in the sidebar list. Now we
succeed silently if the file's gone (and treat ENOENT during unlink
the same way), matching how the frontend already optimistically
removes the row.
#2 — Run button turns red 「stop」 on idle when switching model
desktop/src/components/chat/ChatInput.tsx — gate isActive on
hasMessages. ModelSelector / runtime config changes briefly flip
chatState off-idle (CLI reconnect / startup). Without messages
there's nothing to stop, so the button should stay disabled
gradient, not turn into a red stop affordance.
#3 — Draft input bleeds across session switches
desktop/src/components/chat/ChatInput.tsx — ChatInput is mounted
once at app shell level; switching tabs doesn't re-mount it, so
the local `input` useState carried over. Add a useEffect keyed on
activeTabId that resets input + attachments + open menus + filter
buffers. composerPrefill path (rewind) keeps owning its own reset
via the existing prefill effect.
#4 — Close (×) minimized to tray instead of quitting
desktop/src-tauri/src/lib.rs — drop the prevent_close + hide
pathway on the main window's CloseRequested. Close now actually
quits; users who want to keep the app running can minimize via
the existing window controls. Tray icon stays available for
re-open + explicit quit.
#5 — Tray menu hardcoded "Claude Code Haha"
desktop/src-tauri/src/lib.rs — rename tray menu items, tray
tooltip, and macOS app submenu to "Heicode" / "显示 Heicode" /
"退出 Heicode" / "关于 Heicode".
#6 — Skills page silently empty when one source crashes
src/server/api/skills.ts:listSkills uses Promise.allSettled so a
single failed source (user / project / plugin) returns a partial
list + structured errors[] instead of tanking the whole response.
desktop/src/api/skills.ts + stores/skillStore.ts thread the
errors through; SkillList only shows the hard-error wall when
skills.length === 0.
Verification:
bunx tsc -b --noEmit (desktop) — clean
bunx tsc --noEmit (cc-haha root) — clean
cargo check (src-tauri) — clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire mcp-server's §7.8.3 single SSE channel (GET /api/user/events/stream,
shipped 2026-05-08 in image heicode-7.8.3-sse-v2-20260508) into the client:
cc-haha/src/server/api/heicode-tasks.ts
+ GET /api/heicode-tasks/events/stream (long-lived SSE proxy)
+ GET /api/heicode-tasks/approvals (startup pull)
+ POST /api/heicode-tasks/approvals/:id/decision
cc-haha/desktop/src/lib/heicodeEventsClient.ts (new)
EventSource wrapper with exponential backoff reconnect (1s..30s) and
a 60s heartbeat watchdog that force-reconnects on stream silence.
cc-haha/desktop/src/lib/heicodeEventsRouter.ts (new)
Routes the 5 event names (approval.requested, approval.resolved,
task.status_changed, task.execution_progress, heartbeat) into the
matching stores. Ignores unknown events so future server-side adds
don't crash the client.
cc-haha/desktop/src/stores/heicodeTaskStore.ts
+ applyStatusChange(taskId, status, caption?)
+ applySubStepProgress(taskId, subStepId, status, caption?)
cc-haha/desktop/src/main.tsx
Subscribe to auth store → start/stop the stream as loggedIn flips.
Type-check: bunx tsc -b --noEmit clean.
Bun bundle: bun build src/server/api/heicode-tasks.ts → 86 modules, 0.55 MB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the email + password form on the desktop login card with a single
"一键登录 Heicode Manager" button that drives the existing OAuth bridge:
desktop click
→ POST /api/heicode-auth/oauth/start (server stages state + builds
authorize URL pointing at https://code.xinghanlab.com/heicode/oauth/authorize)
→ Tauri shell.open() the authorize URL in the system browser
→ user signs in via Manager (which now also routes /sign-in?redirect=...)
→ Manager 302s back to http://127.0.0.1:<port>/api/heicode-auth/oauth/callback?token=sk-…
→ callback handler activates the provider; status flips loggedIn=true
→ AppShell unmounts the login page
The OAuth start/callback endpoints already existed (handleOAuthStart /
handleOAuthCallback) so this is a UI-only swap; no auth-store changes.
loginWithCredentials remains exported in case we ever need a fallback,
but it's no longer wired into any UI surface.
i18n: tweak login.oauth.button to "一键登录 Heicode Manager", add
login.oauth.waiting for the polling state.
Aligns with upstream xiaohei/heicode commits 5bd8276 / e60e74b /
34a87a4 (Manager-as-only-identity) without breaking the slice 11-14
heicode-tasks proxy that still depends on the provider abstraction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NewAPI's `users.group` is the local model-access bucket — it must match a
row in the `abilities` / `channels` group column to expose any models. The
Agnet channelId returned by mcp-server's /api/auth/me is a cross-platform
identity that almost never matches a NewAPI-side group, so blindly assigning
it on every login left users with `data: []` from /v1/models and the
desktop client silently fell back to the static 3-Claude default list.
Symptoms fixed: 4 users (xiaohei, 55@55.com, uwktn, test1) had UUID groups
with zero abilities, so /v1/models returned empty for them. cc-haha desktop
falls back to preset.defaultModels, hiding the 28 real models the channels
expose under group=default.
Change: drop the unconditional overwrite branch. The JIT-create path above
still seeds group from channelId on first login (kept for backward
compat), but admin-set group on existing users is preserved. mcp-server
already tracks Agnet channelId separately (see markBillingProviderNewapi),
so we don't need to mirror it into NewAPI's users.group anymore.
DB hot-fix already applied: 4 affected users moved to group=default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements Heicode's choice (b) for §7.7.1 ②: in syncLocalUserFromAgnet,
fire-and-forget PUT mcp-server's internal /api/auth/internal/billing-provider
{email, billing_provider:"newapi"} so mcp-server's User table billing_provider
column lands as 'newapi' for users that came in via Heicode Manager (vs the
default 'litellm' for native taijiagent users).
- Goroutine: never blocks login on this side-effect; mcp-server endpoint is
idempotent so retries from repeat logins are harmless.
- Token via env MCP_SERVER_INTERNAL_TOKEN (K8s/compose secret); empty env
silently skips (dev-friendly).
- Reuses agnetHTTPClient + common.Marshal + common.GetUUID per repo
conventions.
Spec: docs/Heicode-对接进度与待办.md §7.8.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add userRoute.GET /api/user/desktop-downloads/file/:platform with
TokenOrUserAuth middleware. A plain browser <a href> can hit this with
just the session cookie; New-Api-User header isn't required (the SPA's
axios layer still injects it for the metadata endpoint on selfRoute).
Closes the 401 "无权进行此操作,未提供 New-Api-User" case from the
Heicode Manager desktop installer download path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per docs/product-package/10-frontend-detail-spec.md and the wireframes
in 11-product-prototype-wireframes.md §2-§3, the client's first-class
surface is no longer "code-companion empty state" but a Heicode task
driving cabin: input an idea, answer Heicode's follow-ups, see the
auto-generated task card, hand off to Manager for resource binding /
deployment.
This slice ships the surface as a mock skeleton — the data layer
(useHeicodeTaskStore) is seeded with two demo tasks so the wireframe
can be reviewed end-to-end before backend wiring lands.
New files:
- stores/heicodeTaskStore.ts
HeicodeTask shape (id / name / status / status_caption / thread /
card), HeicodeTaskStatus enum, ChatTurn (user | heicode), and
FollowupQuestion (with optional 'high-risk' option flag).
Actions: submitIntent, openTask, closeTask, answerFollowup,
appendMessage. Two seeded tasks ("小团队任务管理 SaaS" running
with full task card; "企业微信通知集成" awaiting approval).
- pages/HeicodeTasksHome.tsx
Two layouts behind a single route. When currentTaskId is null
we render the Home (wireframe §2):
- Header line "当前任务:未选择"
- Big intent prompt + textarea + Send (⌘/Ctrl+Enter shortcut)
- Recent tasks grid (status pill + caption + relative time)
- Manager auxiliary footer hint
When a task is open we render the Workspace (wireframe §3):
- Header with back button + task name + status pill
- Conversation thread (user bubble right-aligned, Heicode
left-aligned with "H" avatar; follow-up questions render
as chip groups with high-risk dot indicators)
- Reply textarea at bottom
- Right-side TaskCardPanel (lg breakpoint+) with goal /
scope / generated-artifacts / Manager actions / footer
buttons (修改目标 / 去 Manager 准备)
Plumbing:
- tabStore.ts: HEICODE_TASKS_TAB_ID + 'heicode_tasks' TabType,
treated like settings/scheduled in dedupe rules
- Sidebar.tsx: new "我的任务" entry between "新建会话" and
"定时任务" with a target icon
- ContentRouter.tsx: route 'heicode_tasks' → HeicodeTasksHome
- i18n: tasks.* (~24 keys per locale) + sidebar.heicodeTasks
Backend wiring TODO: when mcp-server publishes the task-orchestration
contract, swap submitIntent / answerFollowup / appendMessage for real
calls and keep the same shapes. Status updates can come in via SSE
or polling and be merged onto useHeicodeTaskStore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns the client with docs/product-package/{08,11}.md.
6a — Login surface trimmed to wireframe §1:
- Brand wordmark + tagline "从一个想法,到可上线的软件产品"
- Footer line "登录后,客户端会使用 Heicode 提供的模型。"
- ProviderLoginCard now shows ONLY: sign-in target host (read-only),
email + password, sign-in button. The local-network warning tag,
raw baseUrl pill, promo paragraph, "or via browser" alt link, and
the "RECOMMENDED" badge — all dropped per the wireframe's "登录目
标只有 Heicode" intent.
- Removed dead handleOAuth / shellOpen / isLocalBaseUrl helpers.
6b — High-risk approval dialog (08-client-guide.md §"高危审批体验"
+ 11-product-prototype-wireframes.md §9):
- New zustand store stores/approvalStore.ts with a FIFO queue of
ApprovalRequest items + decide(id, 'approve' | 'reject' |
'postpone') action. Idempotent enqueue (dedupe by id).
- New components/approval/ApprovalDialog.tsx renders queue[0] as a
modal with the 6 spec fields (task / operation / target / role /
impact / credential), a Heicode-suggestion sidebar, a risk-level
pill, and 3 actions: 拒绝 / 稍后提醒 / 批准 N 分钟. Queue depth
badge appears at the bottom when more requests are pending.
- AppShell renders <ApprovalDialog /> alongside ToastContainer so it
overlays any surface (sessions, settings, etc.).
- Backend wiring pending — for now main.tsx calls
installApprovalMock() which exposes window.__heicodeMockApproval()
for DevTools-driven demos. Real backend hook lands when
mcp-server / agent-manager publish the approval-stream contract.
i18n: added login.tagline / login.signInTarget /
login.footer.heicodeProvidesModels and a full approval.* set
(zh + en, ~17 keys per locale).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 2026-05-08 product package (docs/product-package/) redraws the
client / Manager boundary. Per 08-client-guide.md §1-7, the client
explicitly does NOT carry resource binding, permission grants, or
any account / security surface — those move entirely to Manager.
This commit removes the client-side resources surface that landed in
slices 2-4 (commits d1db2c1, c0363be, a28c900):
Deleted:
- cc-haha/desktop/src/api/heicodeResources.ts (API client)
- cc-haha/desktop/src/stores/resourceStore.ts (zustand)
- cc-haha/desktop/src/pages/ResourceBindings.tsx (page)
- cc-haha/desktop/src/components/resources/Modals.tsx (3 modals)
- cc-haha/src/server/api/heicode-resources.ts (proxy)
Reverted:
- Sidebar.tsx: drop the Resources nav item + RESOURCES_TAB_ID import
- ContentRouter.tsx: drop the 'resources' branch + import
- tabStore.ts: drop RESOURCES_TAB_ID + 'resources' from TabType
- router.ts: drop 'heicode-resources' case + handler import
- i18n zh.ts + en.ts: strip ~63 keys (sidebar.resources +
resources.* + grants.*)
Kept (still useful for the new spec's Manager-side data needs):
- mcpAuth schema in types/provider.ts
- mcpAuth wired through CreateProviderInput / UpdateProviderInput
- providerService persistence of mcpAuth on add/update
- Path A login flow that decodes JWT exp claims and stores the
pair on the saved provider
Why keep token persistence even though the client doesn't expose
binding/grant UI any more? Per product spec the Manager will surface
余额 / 模型 / 用量 / 调用日志 (§2.3.1 in mcp-server's 待办 doc), and
the client will surface high-risk approvals (08-client-guide.md
§5). Both flows need a JWT pair we can refresh without re-prompting
for password — that machinery is already in place.
Next slice candidates per product spec (08 + 10 + 11):
- High-risk approval dialog (新增 Tier 1, mock-wired UI first)
- Task card + intent input as main client surface
- Execution feedback panel (Agnet sub-stage status)
- Delivery result panel
None of those are in this commit; this commit is purely cleanup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What ships:
Bindings tab:
- Create binding modal (type / name / external_ref /
permission_scope / secret_ref / status). Permission scope is a
newline-or-comma textarea that splits into a string[].
- Soft-delete (status = revoked) with red confirm dialog.
Grants tab:
- List with binding name + role + allowed actions + scope + status
+ expires.
- Create grant modal: pick binding → checkbox-select allowed
actions from THAT binding's permission_scope (auto-cleared when
binding changes), set binding_scope / role / expires_at.
- Revoke with red confirm dialog.
Shared:
- Tab switcher with active-state underline + count badge.
- Refresh button per tab (independent fetch state).
- Error banners with dismiss; mutation errors surface in modals.
- i18n: ~30 new keys per locale (zh + en).
Backend (no change):
Slice 3's /api/heicode-resources/* proxy already handles POST /
PUT / DELETE because it forwards verb + body verbatim.
The Authorization-header refresh logic (60s buffer) automatically
keeps mutations working across the 24h JWT boundary.
mcp-server safety nets the user can rely on (already enforced):
- 422 RESOURCE_GRANT_SECRET_REJECTED if metadata/constraints/scope
contains plaintext credential keys
- 400 RESOURCE_GRANT_INVALID if grant.allowed_actions ⊄ binding.scope
- 403 FORBIDDEN_SCOPE on cross-user binding/grant access
Cosmetic notes:
- GrantFormModal hooks were reordered to satisfy React's "hooks
before any early return" rule.
- useEffect that prunes allowed_actions when the picked binding
changes uses an internal `changed` flag to avoid a setState loop.
Slice 5 candidates (not in this commit):
- Edit binding (PUT)
- metadata + constraints power-user JSON editor
- Grant suspend/unsuspend
- inline filtering (type / status)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend:
1. Extend SavedProvider schema with optional mcpAuth field
(accessToken / refreshToken / accessExpiresAt / refreshExpiresAt /
managerLoginUrl / userId / channelId). Wired through
CreateProviderInput and UpdateProviderInput so providerService
persists tokens to providers.json.
2. handleLoginWithCredentials (Path A) now decodes the JWT exp claim
of both tokens (no signature verification — issuer just authed us)
and stores the resulting mcpAuth object on the saved provider.
Documented TTL (24h access / 7d refresh) used as fallback if exp
claim missing.
3. New handler api/heicode-resources.ts — proxy for the local server
route /api/heicode-resources/{*path}. It:
- Reads mcpAuth from the active provider (401 if missing)
- Refreshes the access token if < 60s from expiry by calling
<managerLoginUrl>/api/auth/refresh; persists the new pair
back to providers.json before forwarding
- Returns 401 if refresh token is also expired (re-login needed)
- Forwards request to <managerLoginUrl>/api/resources or
/api/resource-grants with Authorization: Bearer <accessToken>
- Passes status + body through
4. router.ts: register case 'heicode-resources'.
5. errorHandler: add ApiError.unauthorized(401) and badGateway(502)
factories used by the proxy.
Desktop:
6. New api/heicodeResources.ts client + types (ResourceBinding,
ResourceGrant, etc. mirroring mcp-server contract). Slice 3 only
exposes listBindings + getBinding.
7. New stores/resourceStore.ts (zustand) with bindings, isLoading,
hasFetched, error + fetchBindings action.
8. pages/ResourceBindings.tsx upgraded from shell to a real list:
- Auto-fetches on mount
- Shows loading skeleton, error banner with dismiss, empty state,
or a 5-column table (Name / Type / External ref / Status /
Permission scope)
- Refresh button in the header
- Footer note about CRUD coming in slice 4
9. i18n: 14 new keys (common.dismiss + resources.refresh / refreshing
/ col.* / error.title / footer.cruComingSoon) in both zh + en.
E2E behaviour after install: log in via 55@55.com / By@123456., open
Resources tab — local server proxies to apimtaiji and lists whatever
ResourceBindings the user has on mcp-server. New test account 55@55.com
has 0 bindings, so empty state shows up.
Slice 4 next: Create / Edit / Delete binding modals + Grants UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per docs/vision-heicode-full-stack-agentic-dev.md and plan.md P1, Heicode
is a full-stack agentic dev platform — not a chat-only client. cc-haha
needs a "Resources" entry where users bind Git repos / SK / project docs
/ cloud accounts and grant them to sub-agents.
mcp-server team has the P1 9 endpoints live (POST/GET/PUT/DELETE
/api/resources, POST/GET/DELETE /api/resource-grants — see
Heicode-接口契约文档.md §2-§3). cc-haha has not consumed them yet.
This commit ships slice 2 (page shell):
- tabStore: new TabType 'resources' + RESOURCES_TAB_ID export
- Sidebar: new nav entry between 'scheduled' and 'terminal' (link icon)
- ContentRouter: route 'resources' tab to <ResourceBindings />
- pages/ResourceBindings.tsx: header + "coming soon" placeholder card
- i18n: sidebar.resources + resources.* keys (zh + en)
Slice 3 (next): persist mcp-server JWT in provider record so the local
Bun server can proxy /api/heicode-resources/* to apimtaiji with a fresh
Authorization Bearer header. Refresh logic on the 24h boundary.
Slice 4 (next): actual Bindings list + Create/Delete + Grants list +
Create/Revoke modals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per heicode.md / heicode-runtime-auth-newapi-secret-design.md /
Heicode-登录接口对接文档.md, identity is owned by the Manager
(mcp-server), NewAPI is just the model gateway. The previous local
flow hit NewAPI's /api/user/login directly, which deviates from the
documented design — that endpoint is the legacy upstream NewAPI password
login that the current production web frontend already bypasses.
New flow inside POST /api/heicode-auth/login-with-credentials:
1. POST <managerLoginUrl>/api/auth/login (mcp-server)
Body: {email, password, role: "user"}
→ 200 {success, data{token, refreshToken, user{id, channelId,
role, email,
name}}}
2. POST <baseUrl>/api/user/session/from-agnet (heicode 后端)
Body: {access_token, refresh_token}
→ 200 + Set-Cookie: session=...
JIT-syncs the local NewAPI user from the Agnet identity:
users.group becomes the channelId returned by mcp-server,
which matches NewAPI's abilities/channel routing model.
3. GET <baseUrl>/heicode/oauth/authorize?... (heicode 后端)
Headers: Cookie + New-Api-User
redirect: 'manual' to capture the 302 Location header
→ token=sk-XXXX is parsed out and handed to the existing
loginAndActivate pipeline (which probes /v1/models and
persists the active provider).
Provider preset gains an optional managerLoginUrl field (default
https://apimtaiji.azure-api.net/api/mcp for taijiaicloud), with an
env override HEICODE_TAIJIAICLOUD_MANAGER_LOGIN_URL for dev.
End-to-end verified locally with the documented test account
55@55.com / By@123456.: each step returns 200, /heicode/oauth/authorize
mints a sk- token tied to channelId 6e6fc470-76f8-4bb1-8ea4-625dc5b12bc6,
and /v1/models returns the full live model catalogue under that channel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This reverts commit 992a965. After re-reading the upstream Heicode
design docs (heicode.md, heicode-runtime-auth-newapi-secret-design.md,
plan.md), it is clear that:
1. users.group = channelId is the correct upstream behaviour. Agnet's
/me is the source of truth for which NewAPI channel a user belongs
to. Forking that logic in NewAPI to special-case role>=root breaks
the documented "Manager owns identity, NewAPI is just the model
gateway" boundary.
2. The empty-abilities symptom isn't a NewAPI fork bug. It's that
chenchen was created by raw SQL INSERT into NewAPI's users table —
a path that doesn't exist in the design. Real users get their
channelId from Manager (mcp-server) at login, and ability rows for
that channelId are provisioned out-of-band by platform operations
when the channel goes live.
3. Patching NewAPI to silently keep an admin's hand-edited group hides
the real provisioning gap and pollutes the upstream sync logic for
every future user.
Restoring upstream behaviour. Out-of-band fixes (whether to
provision abilities, route mcp-server logins, etc.) belong elsewhere.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
syncLocalUserFromAgnet rewrites users.group with the channelId returned
by Agnet's /me on every web /sign-in. That's correct for normal users —
their channel membership is owned by the Agnet identity service. But
platform administrators (RoleRootUser) are provisioned out-of-band:
operators set their group to "default" (or whichever billing tier)
manually, and their NewAPI abilities exist there.
When a root admin logs in via the web, Agnet returns a stub channelId
that has no abilities rows. The current code overwrites users.group
with that stub, and the next /v1/models call returns an empty list —
the desktop client then falls back to providerPresets.defaultModels,
hiding the real model catalogue from the operator.
Add a role guard so the rewrite only fires for users below root. Root
admins keep whatever group an operator set in the DB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-supplied screenshot of the Heicode H circuit logo (heicode-logo.png
at repo root) was background-removed via flood-fill from the image
borders, edge-feathered with a 0.7px Gaussian on the alpha channel, and
upscaled to 1024x1024 as the master.
Replaced everywhere the icon is referenced:
src-tauri/icons/
32x32.png, 128x128.png, 128x128@2x.png — Tauri build inputs
icon.ico — multi-res 16/24/32/48/64/128/256 (Windows installer +
taskbar)
icon.icns — multi-res 16/32/64/128/256/512/1024 (macOS bundle)
Square*.png + StoreLogo.png — Windows store sizes (kept in sync)
public/app-icon.png — splash icon shown by HeicodeLoginPage,
ActiveSession.tsx, EmptySession.tsx,
Settings.tsx (1024x1024)
The H mark sits on transparent alpha now; on dark window chrome it
appears as the floating logo without a white card. Source resolution
(273x276) means 16/24px renderings are slightly soft; adequate for
taskbar/tray and crisp at 32px+.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per docs/integration/Heicode-登录接口对接文档.md, the original Heicode
desktop is supposed to take email+password directly, hand them to the
Manager (POST /api/user/login), and use the resulting session to
acquire an LLM access token. The previous flow opened a system browser
and redirected through /heicode/oauth/authorize, which works but
deviates from the design and forces an extra round trip.
This commit adds the documented in-process flow as the primary login
path while keeping browser OAuth as a fallback link:
POST /api/heicode-auth/login-with-credentials
1. POST <baseUrl>/api/user/login (username + password)
2. Capture Set-Cookie from the response
3. GET <baseUrl>/heicode/oauth/authorize?... with that cookie and
redirect: 'manual'
4. Parse Location: ...?token=sk-XXXX, hand it to loginAndActivate
The whole chain stays inside the local cc-haha server — no browser is
opened, no token leaves the user's machine.
UI changes:
- ProviderLoginCard now shows email + password fields as the primary
form, with the existing "or via browser" OAuth path demoted to a
small link below.
- Added store action loginWithCredentials and matching API client
method.
- i18n keys: login.creds.{email,password,submit,submitting} +
login.oauth.altLink (zh + en).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three concrete pieces:
1. Provider preset taijiaicloud now points at https://code.xinghanlab.com
instead of the old api.taijiaicloud.com. Together with the stock
resolveOAuthConfig fallback (<baseUrl>/heicode/oauth/authorize), this
flips oauthEnabled on for the login card and turns the existing
browser-redirect bridge into the default flow. Card name + promo
updated to reflect that this is "log in via Heicode Manager".
2. loginAndActivate softens its model probe. /v1/models is best-effort:
only hard 401/403 auth failures abort login. 5xx / panics / empty
lists fall back to preset.defaultModels so the user lands inside the
app even if the gateway transiently misbehaves; they can re-pick
models from Settings later.
3. heicode_oauth.go fallback page: /login → /sign-in (matches the
actual SPA route), title/copy de-branded from "HeiCode/新 API 控制台"
to plain "Heicode 控制台".
Also picks up the prior unstaged Windows polish: WindowControls (min/
max/close + drag region) on the login screen, ProviderLoginCard +
globals.css refinements that landed in earlier MSI builds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend listLoginProviders() was iterating ['taijiaicloud','clawdrouter']
and throwing 500 because clawdrouter preset was already removed from
providerPresets.json. Narrowing SUPPORTED_LOGIN_PROVIDER_IDS and the two
Zod enums to ['taijiaicloud'] only, plus tightening the desktop
HeicodeProviderId type to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>