Try getHeicodeBalance() (mcp-server /api/user/balance per product-package §4)
before falling back to local /api/user/self. Aligns Wallet page with the
Heicode product-package contract so balance numbers come from the same source
the desktop sidecar uses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs/Heicode-对接进度与待办.md §7.5 point 7 named the Manager team as
responsible for wiring P1 resource UI to mcp-server (§2 ResourceBinding,
§3 ResourceGrant) — without this, the deeplink that the desktop client
puts on the task card (`/manager/resources?from=task`) ends in a 404.
Changes:
1. lib/heicode-mcp.ts: typed wrappers for the 9 P1 endpoints
- §2 ResourceBinding: list / get / create / update / revoke
- §3 ResourceGrant: list / get / create / revoke
- Field shape verified against live mcp-server with test account
55@55.com — 7 smoke cases pass including the 422 sensitive-keyword
enforcement and the §3 subset rule.
2. features/agnet-console/pages.tsx AgnetSKSourcesPage rewritten to
read /api/resources (filtered to status=active) instead of the
legacy Heicode-local git_sources controller:
- Card 1 代码 = resources filter type='git'
- Card 2 文档SK = resources filter type∈{sk,project_doc}
- Card 3 云账号 = resources filter type∈{cloud_account,
cloud_resource}, "auto-discovery coming soon"
hint shown when empty (current state)
- Card 4 推荐摘要 = unchanged
3. Advanced sheet form rewritten for mcp-server ResourceBinding shape:
{type, name, external_ref, metadata, permission_scope, constraints,
secret_ref, status}. Old (provider, repo_url, ref, paths, usage,
tenant_id) maps in:
name → name
repo_url → external_ref
provider → metadata.provider
ref → metadata.default_branch + constraints.ref
paths → constraints.allowed_paths (comma-joined)
usage → type ('git'/'sk'/'project_doc')
tenant_id → dropped (server uses auth.user_id)
— → permission_scope ['repo:read'] minimal default
— → secret_ref blank for now (server fills once
OpenBao Secret Broker lands per §2.1 TODO)
Form also surfaces the §2.1 422 RESOURCE_GRANT_SECRET_REJECTED
server-side error to the user.
4. Removed unused imports (GitSource{,Payload,Usage}, createGitSource,
deleteGitSource, listGitSources) — legacy git_sources controller is
still in the Go backend for now but the Manager no longer consumes it.
5. RecommendationSummaryDialog now takes ResourceBinding[] for project /
sk source counters instead of GitSource[].
Smoke verified end-to-end against live mcp-server:
list / create (incl. metadata+constraints+permission_scope) / get /
delete-binding all 200 with expected shapes; 422 secret rejection
fires on metadata.{name containing 'token'}; §3 subset rule on
allowed_actions outside binding.permission_scope returns
RESOURCE_GRANT_INVALID.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-to-end smoke against mcp-server (test account 55@55.com) revealed
the actual task.card shape:
{ goal: string,
scope: string[],
generated_artifacts: string[],
manager_actions: Array<{label, deeplink}> }
My earlier TS types and TaskCardView assumed
{objective, first_version_scope, auto_generated, pending_context} —
keys that don't exist in the real response. Tasks created via the
intent flow would have rendered with empty bullet lists.
Changes:
- lib/heicode-mcp.ts: rewrite HeicodeTaskCard to the live shape, add
HeicodeManagerAction type
- features/tasks/task-card-view.tsx:
- read card.goal / card.scope / card.generated_artifacts
- new readManagerActions() helper renders mcp-server's
{label, deeplink} buttons in place of the hardcoded action row,
with normalizeDeeplink() mapping /manager/resources →
/sk-sources etc. to Manager-side routes
- dropped pending_context (no such field); follow-up "Pending
context" footnote is now a plain explanatory line per docs §10
Smoke verified end-to-end:
login (POST /api/auth/login) → 200 + JWT
intent (POST /api/user/tasks/intent) → 200 + configuring task
list (GET /api/user/tasks) → items shape matches type
detail (GET /api/user/tasks/{id}) → follow-ups parse correctly
answer × 2 (POST .../answer) → state machine flips to running,
card materialises with the 4
actual fields above
audit (GET /api/agnet/audit-logs) → {items, total, next_cursor}
agnet (GET /api/agnet/deployments) → {items, total}
balance (GET /api/user/heicode/balance) → HEICODE_USER_NOT_FOUND
(test account, expected; my code catches this and returns null)
UTF-8 body through the proxy works fine (earlier "parse body" error
was a Windows shell quoting issue, not a proxy bug).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After reading the Heicode-接口契约文档 v2.2 in
~/Desktop/taijigit/taiji-AI-PAD/Docs/ the right data sources are clear:
§1 /api/auth/* — already wired (features/auth/api.ts)
§4 /api/user/heicode/* — model balance + usage + logs
§5 /api/agnet/* — Agnet platform stub (deployments / audit / etc)
§6 /api/user/tasks/* — HeicodeTask intent → followups → card
The first wave UI work (commit 2df233b) used Heicode-local controllers
as the data source (AgnetDeployment.orchestration_plan as a stand-in
for the task object). That was wrong — the contract document is clear
that HeicodeTask (§6) is the canonical user-facing task object, and the
mcp-server stub at §5 is the canonical deployment source.
This commit redirects the data plumbing without touching the UI shells:
1. New lib/heicode-mcp.ts — typed client that calls mcp-server through
the existing same-origin /api/heicode-auth/* proxy. Implements the
subset of §4/§5/§6 the Manager UI needs:
createTaskFromIntent / listHeicodeTasks / getHeicodeTask / answer
getHeicodeBalance / getHeicodeModels / getHeicodeUsage / getHeicodeLogs
listMcpAgnetDeployments / listMcpAuditLogs
2. HomeHero (features/dashboard/components/home-hero.tsx):
- Idea input now POSTs /api/user/tasks/intent and routes the user
to /tasks/$id once the server returns the new task with its first
round of follow-ups. Previously it only stashed the idea in
localStorage which the docs §10 didn't actually require.
- ContinueTasks + TodayFocus now consume listHeicodeTasks output
(HeicodeTask.status / status_caption / updated_at:ms) instead of
AgnetDeployment shape.
3. TaskCardView (features/tasks/task-card-view.tsx):
- Reads getHeicodeTask(id) from mcp-server (refetch every 15s).
- When status=configuring renders the open follow-ups from the most
recent heicode thread entry as clickable option chips; clicking
POSTs answer to /api/user/tasks/$id/answer and the server-side
state machine advances. high-risk options get a red badge per §6.
- When status=running (followups answered, card materialised) the
four blocks docs §10 任务卡 mandates are rendered from task.card:
目标 / 第一版范围 / 自动生成 / 待确认上下文.
4. AgnetAuditPage (features/agnet-console/pages.tsx):
- Switched queryFn from local getAgnetAuditLogs to mcp-server
listMcpAuditLogs. The redacted-card renderer already accepts any
{resource_id, allowed_actions, constraints, secret_ref} shape so
no UI change needed; banner still announces no plaintext.
Notes:
- /wallet refactor to §4 deferred — it pulls multiple legacy series
from the local NewAPI controllers and the rewrite is a separate
pass. Manager users see local data for now; the call is identical
shape so swap is mechanical once we get there.
- Local TS check clean. Not deployed.
- Earlier 2df233b's UI structures (HomeHero shape, TaskCard layout,
recommendation dialog, audit redacted view) stay verbatim — only
the data fetching layer moved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five gaps closed against the updated product-package spec
(http://gitee.ath.cx:3000/xiaohei/heicode/src/branch/main/docs/product-package):
P1. /dashboard hero rewritten per §10 §"初始首页"
features/dashboard/components/home-hero.tsx replaces the technical
CockpitView with four blocks the spec mandates: 主输入 / 继续任务 /
今日焦点 / 辅助入口. Main input is "你想把什么想法变成可以上线的软件?".
Submit only stashes the idea to localStorage + toast — the actual task
conversation belongs in the desktop client per §13 §5.1.
P2. /tasks/$id TaskCard route per §10 §"任务卡" + §11 §3
features/tasks/task-card-view.tsx renders one AgnetDeployment as the
user-facing task object: 目标 / 第一版范围 / 自动生成 / 待确认上下文 +
Manager 辅助按钮. Linked from Home hero's 继续任务 list.
P3. /sk-sources 推荐摘要 dialog per §10 §"推荐确认卡"
features/agnet-console/pages.tsx RecommendationSummaryDialog. Five
blocks (本次会做 / 本次允许使用 / 本次不会做 / 高危规则 / 预计消耗) +
Launch Agnet button with "参数由 Heicode 自动生成" caption. No JSON
editor, no permission manifest — §10 高级展开禁令.
P4. /audit redacted card view per §10 §"任务用量与审计" + §6
features/agnet-console/pages.tsx AgnetAuditPage. Old裸 table replaced
with脱敏 cards exposing only the fields docs allows: resource_id /
resource_type / allowed_actions / constraints / secret_ref. Helper
function maskIfSecret() catches any stray plaintext credential the
backend might leak. Banner says explicitly "明文密钥从不展示".
P5. Login screen filters Claude Official provider per §8
cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx hides the
legacy `official` preset so the login carries Heicode brand alone.
i18n fix (BIG):
i18next defaults to `defaultNS = 'translation'`. The earlier custom
keys had been written to the JSON root, NOT into translation, so
every t('Preparation checklist') was returning the English key as
fallback all along. Moved 67 orphan keys (zh+en, both files) into
the translation namespace where they're actually resolvable. Verified
by loading i18next + zh.json in bun and confirming all keys resolve
to the expected Chinese strings.
Cache-busting from earlier session (already deployed via SFTP, never
committed): index.html / constants.ts / footer.tsx now hold the
?v=h-glass-2 suffixed asset URLs in git, so future docker rebuilds
preserve them.
Per user directive: tested locally only (TS check clean, i18next
resolves correctly). NOT deploying to the VM in this commit — user
asked to keep production untouched until they verify the changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier UserPill landed in the TitleBar tab strip, but the main
desktop layout (HeicodeTasksHome shell) renders its own chrome without
that strip — so the user info ended up invisible despite mcpAuth
having the right data after a2deeb6. Pull the same identity (avatar
gradient + name + email + role badge) straight into the bottom of
Sidebar.tsx, right above the logout/settings rows, so it's visible
on every screen regardless of which top bar is mounted.
Two layouts:
- expanded: 36px gradient avatar + name + email + admin/root badge
- collapsed: 36px avatar circle only, title tooltip carries the email
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resolveBundledCliPathFromExecPath / resolveClaudeCliLauncher hardcoded
startsWith('claude-sidecar') / startsWith('claude-server') checks. After
the 3a358ba rebrand the bundled binary is heicode-sidecar-*.exe — the
match fails, the function returns null, and resolveCliArgs falls back
to the Windows --preload script branch which spawns the bun-compiled
sidecar with arguments it doesn't understand. The process exits 2
silently (empty stderr), surfacing in the UI as:
Error: CLI 进程启动失败。
CLI exited during startup with code 2.
Symptom user report: pilac69779@codoteam.com freshly registered, OAuth
login succeeded but every chat attempt failed at sidecar startup.
Fix: accept both `heicode-` and legacy `claude-` prefixes for sidecar /
server / cli binaries so MSI #15+ launchers work AND
CLAUDE_CLI_PATH=/path/to/legacy/claude-sidecar still works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of the recurring "I don't see the logged-in user info in the
desktop client" complaint: the credentials-login path builds an mcpAuth
record with email / displayName / role from the mcp-server /api/auth/login
response and the TitleBar UserPill renders fine. But the OAuth-login path
(the typical browser-redirect flow) only received an sk- API key in the
callback query string — no user fields. So:
status.user == null
UserPill: if (!user) return null
→ blank space where the user pill should be.
Fix on backend (heicode_oauth.go HeicodeOAuthAuthorize):
- After issuing the sk- token, load the authenticated user from the
session and embed email / name / role (root|admin|user) / channel_id /
user_id as query params on the redirect URI.
Fix on client (cc-haha/src/server/api/heicode-auth.ts handleOAuthCallback):
- Read those query params (pickUserFromQuery), build an mcpAuth record
(buildMcpAuthFromOAuthQuery), and pass it through loginAndActivate the
same way the credentials path does. The accessToken slot holds the sk-
key as a placeholder — OAuth flow doesn't deliver a refreshable JWT
pair, and this mcpAuth exists purely to surface identity on the
TitleBar.
After this, OAuth-route users see the same gradient-avatar pill with
their email / name / role badge that credentials-route users already see.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bin/azure_vm_deploy.sh pulled from `origin` (xiaohei/heicode.git) when
GIT_REF was set. Production VM now tracks heicode-win/main and that's
where the rebrand + bug fixes ship — pulling from origin would silently
revert all of it on the next deploy. New GIT_REMOTE env var (defaults
to heicode-win) lets operators override for one-off cherry-pick deploys.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. /api/heicode-auth/* proxy: CriticalRateLimit (20/20min) → GlobalAPIRateLimit
(180/180s). The Heicode external-identity proxy is hit on every page
render for /me + /refresh plus the login burst — CriticalRateLimit is
sized for sensitive ops (password reset, 2FA) and trips at ~5 quick
page loads, returning 429 to a normal user. APIM upstream rate-limits
itself, so a second tight layer here adds no security and just
manufactures 429s.
2. JIT-create user group: seed "default" instead of me.Data.ChannelID.
Companion to 578a68f which only patched the every-login overwrite
path. New users (yj2824269760@gmail.com et al, JIT-created after
578a68f) still landed in a UUID group → empty /v1/models response →
desktop client showed the static 3-Claude fallback list.
Both fixes were applied on the production VM directly today (sed +
python patch) — committing them so the next docker rebuild on VM keeps
them instead of reverting to the buggy file via git checkout.
DB hot-fix already applied: 6 affected users moved to group=default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beyond the i18n strings rebrand (b55d103) this pass cleans up everything
that still surfaced "Claude" in the built artifact:
Theme tokens (cc-haha/desktop/src/theme/globals.css)
- light-mode text-selection-bg rgba(197,165,114,*) → rgba(123,107,227,*)
(selecting ANY text in light mode used to paint a gold highlight)
- dark + light diff-highlight-bg / -gutter switched from gold to brand
violet rgba(123,107,227,*) — keeps semantic "highlight" without leaving
the brand
Sidecar binary rename: claude-sidecar → heicode-sidecar
- desktop/sidecars/claude-sidecar.ts renamed (git mv)
- internal log prefix strings (12 occurrences) updated
- desktop/scripts/build-sidecars.ts: entrypoint / outfileBase / productName
('Heicode Sidecar') / publisher ('Heicode') — these last two are embedded
in the .exe metadata you see in File Properties
- src-tauri/capabilities/default.json: 9 binaries/claude-sidecar references
rewritten + description "Default capabilities for Heicode Desktop"
- src-tauri/src/lib.rs: 2 .sidecar("claude-sidecar") + 3 packaged sidecar
exe filename strings + 1 doc comment
- desktop/sidecars/launcherRouting{,.test}.ts
After this rebuild, Task Manager shows heicode-sidecar-*.exe child
processes (was claude-sidecar-*.exe) and right-click → Properties on
that binary shows "Heicode Sidecar" / "Heicode" instead of "Claude Code".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[data-theme=light] block in globals.css still defined --color-primary as
#8E7547 with gold gradient buttons and gold focus rings. Dark mode was
correctly on the brand violet #7B6BE3 since the Iris overhaul, but the
moment a user switched to (or was OS-defaulted to) light theme they got
the legacy gold mark on login button, focus ring, sidebar active state,
shadow-button-primary, etc.
Rewrote the light override to mirror the dark brand palette:
- --color-primary #7B6BE3, primary-container #5B4FB8, fixed #9A8DEC
- --color-secondary #6B7CE0 (was slate #5A6B82)
- --gradient-btn-primary 3-stop violet→blue (matches dark mode + Manager)
- --gradient-brand-wordmark also added in light scope (was inheriting
but explicit avoids cascade surprises)
- All rgba(142,117,71,*) shadows / borders rewritten to rgba(123,107,227,*)
Code-syntax / diff highlight golds left alone (those tint code text, not
brand UI).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Theme tokens (theme.css)
- --primary retuned to the brand violet oklch(0.58 0.17 278) ≈ #7B6BE3
(was a saturated pink oklch ... 286 that read off-brand against the H
glass logo)
- New tokens: --brand-from #B888E5, --brand #7B6BE3, --brand-to #6B7CE0,
--gradient-brand (135deg three-stop), --gradient-brand-btn for primary
CTAs. Both light and dark modes hold the same brand identity.
Login page (auth-layout.tsx + sign-in/index.tsx + user-auth-form.tsx)
- Inlined H glass mark SVG replaces the abstract ShieldCheck pictogram
- Brand wordmark uses gradient text-fill so the word "Heicode" reads as
the same gradient as the logo
- "Tenant access" pill and h2 heading both pick up brand violet via
border / bg / gradient text
- Sign-in button switches from solid var(--primary) to the three-stop
--gradient-brand-btn with violet drop shadow + lift-on-hover
Header user info (profile-dropdown.tsx)
- The right-side trigger used to be a bare 36px avatar — invisible user
identity unless you click. Now it is a pill: gradient-filled avatar
initials + display name + email + role badge, always visible on >=sm
- Avatar fallback fills with --gradient-brand so even pre-image, the
user pill carries Heicode color identity
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per docs §10 "禁止普通用户体验出现:模型供应商配置 / 模型提供方
选择" — the dashboard landing hero showed "OpenAI / Claude /
Gemini / DeepSeek / Qwen / Llama" as a marketing matrix, which
leaks upstream provider branding into the user-facing surface.
Replaced with Heicode capability tags (通用 / 长上下文 / 推理 /
代码 / 多模态 / 高性价比). This is the landing the user lands on
after login; admins still see real upstream provider names in
the system-settings → models tabs (those are technically the
channel protocol names and removing them would mislead admins).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CORS unblock — add /api/heicode-auth/*proxyPath backend proxy to
HEICODE_AUTH_BASE_URL. Frontend defaults to same-origin path so
the browser never hits APIM directly.
Sidebar — replace backend jargon (Git sources / Deployments /
Events / Wallet / Available models / Profile) with the user-facing
labels docs §10 mandates: 总览 / 准备清单 / 任务总览 / 审计 /
模型与余额 / 客户端 / 账号安全.
/sk-sources rewritten as 4-card preparation wizard with progress
meter; full Git form moves into a 高级补充 sheet. Drops JSON
editor, permission manifest, snapshots and resource-grant pills.
/deployments simplified to 任务总览: objective + status + last
update. Drops risk / budget / scope / secret_ref pills and the
RunDetailPanel; manifest details only in audit/advanced views.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
index.html: add png/ico/apple-touch favicon links, og:image,
and brand theme-color (#7B6BE3) so all icon surfaces use the
H glass mark.
Hide unused auth UI:
- profile page: drop PasskeyCard + TwoFACard
- system-settings/auth: drop Passkey Authentication section
These features aren't part of the Heicode platform flow (auth
is delegated to the identity service / SSO); leaving them in
the UI confuses tenants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match Heicode desktop client app icon: violet→blue gradient
rounded-square with white H. Replaces favicon.ico, logo.png
(used in footer / system info), heicode-logo.svg (browser
favicon link), and the inline Logo component.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 1 was design tokens + login screen. Visible-but-subtle. Wave 2
ships the meaningful in-app changes a user actually sees after they
log in. Aggressive enough that the post-login surface feels like a
2024-vintage product, not a 2018 utility.
User chat bubble (src/components/chat/UserMessage.tsx)
- Background: violet-tinted vertical gradient
(rgba(123,107,227,0.10 → 0.04)) over surface-user-msg
- 1px violet-18% border + soft violet-8% drop shadow
- Asymmetric corner (18/4/18/18) preserved
- Effect: user messages read distinctly from assistant messages
at a glance, no need to look at the avatar.
Assistant chat bubble (src/components/chat/AssistantMessage.tsx)
- Top-down white wash (rgba 0.025 → 0) on container surface
- Two-stop shadow (1px ambient + 4px lift) so the bubble lifts
off the page instead of disappearing into the chat background
- Border tightened to full --color-border (was 60% opacity)
Glass-panel composer surface (src/theme/globals.css)
- Top violet wash (5% → 0% over 35%) over glass surface
- Inset top hairline (white 4%) for lit-from-above feel
- Hover: violet-18% border tint
- Focus-within: violet-45% border + 3px violet-18% halo + dropdown
shadow, so typing feels obviously "on"
- Smooth 200ms transitions on border + shadow
Sidebar active session (src/components/layout/Sidebar.tsx)
- Left edge: 0.5px × 20px violet→blue gradient rail with 8px
violet glow — instant focus anchor
- Active dot: 8px violet glow shadow
- Active row: 1px violet-24% border + inset top hairline
- Inactive: hover lifts text to primary (was secondary→primary on
hover, now also adds violet-6% bg tint via token change)
Tasks home intent box + recent task cards + workspace reply box
(src/pages/HeicodeTasksHome.tsx)
- Intent box: violet wash gradient + transition + hover/focus
border tint
- Recent task cards: hover -translate-y-px lift + violet-32%
border + violet-10% drop shadow → clearly clickable
- Workspace reply box: same violet wash + focus halo
Type-check: bunx tsc -b --noEmit clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the desktop one-click login flow opens
/heicode/oauth/authorize?state=...&redirect_uri=...
in the browser and the user isn't signed in, Manager renders the
"please log in" bridge page that links to /sign-in?redirect=<authUrl>.
After login, useAuthRedirect's `handleLoginSuccess` calls TanStack
Router's `navigate({ to: targetPath })` to send the user back to
that authorize URL.
But TanStack Router only knows about React routes; backend bridges
(`/heicode/oauth/...`, `/api/...`) have no matching route, so the
SPA renders 404. The user has to manually re-enter the URL, at
which point the backend handles it and 302s to the loopback
callback. This produced the 500 → 404 → success symptom users hit
on first-time desktop login.
Fix: detect backend prefixes (`/heicode/oauth/`, `/api/`) and use
`window.location.assign()` to force a full-page navigation so the
server gets the request directly. React-route paths still go
through `navigate()` as before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#1 Login wordmark color
The big "HEICODE" wordmark on the login page was rendered in the
gold brand color (#C5A572), which clashed visually with the
purple→blue glassy H app icon shown in the window/taskbar/dock.
Now the wordmark uses the same linear-gradient(135deg, #B888E5,
#6B7CE0) as the logo so the login surface matches the icon.
Backdrop radial glow tinted to match. Other surfaces keep gold
primary so buttons/accents stay consistent app-wide.
#3 User pill in TitleBar
Logged-in users had no in-app affordance to confirm "I'm signed
in as X" or to know which account they're using. Add a pill on
the right of TitleBar showing initial avatar (logo gradient) +
display name + admin/root role badge.
Wiring:
- cc-haha/src/server/types/provider.ts: McpAuth gains optional
email / displayName / role fields. Backwards-compatible (all
optional, existing saved providers stay valid).
- cc-haha/src/server/api/heicode-auth.ts: handleLoginWithCredentials
now reads user.{name|display_name, email, role} from the
/api/auth/login response and persists them on the provider's
mcpAuth record. /api/heicode-auth/status returns a `user` field
synthesized from the active provider.
- cc-haha/desktop/src/api/heicodeAuth.ts: HeicodeAuthStatus type
gains the `user` shape.
- cc-haha/desktop/src/components/layout/TitleBar.tsx: new UserPill
component reads from useHeicodeAuthStore; hidden until status
loads so the bar doesn't flicker on boot.
Type-check: bunx tsc -b --noEmit clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tauri's macOS bundler validates that every PNG referenced from
src-tauri/icons/ is RGBA. Our previous rebuild-icons.py wrote RGB PNGs
because the trim/pad pipeline lives in RGB to keep transparent edges
out of the glassy logo. macOS build failed with:
error: proc macro panicked
--> src/lib.rs:1073:16
message: icon /.../desktop/src-tauri/icons/32x32.png is not RGBA
Fix: scripts/rebuild-icons.py now does `.convert("RGBA")` on every
PNG output before saving. Alpha is fully opaque (255) — the rounded
glass edges retain their look on any background since we paint on
solid white in load_trimmed_square. Same files keep working for
Windows .ico embedding (which accepts RGBA fine).
Regenerate all 17 PNG outputs (32/64/128/128@2x/icon.png + 9 Square*
+ StoreLogo + public/app-icon.png) and the derived .ico/.icns.
Mac side: pull this commit and re-run `bun run build:macos-arm64`.
Windows side: no change in behavior (Tauri's NSIS bundler doesn't
care about PNG color mode).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ModelSelector and PermissionMode changes trigger a server-side CLI
restart (handler.ts:521 restartSessionWithRuntimeConfig). The server
emits status:'thinking' with verb 'Switching provider and model...' or
'Restarting session...' to keep the spinner spinning during the ~3s
restart window. ChatInput's isActive=`chatState !== 'idle' && hasMessages`
guard let the run button flash to a red 「stop」 affordance during that
window for any session with prior messages — confusing because hitting
stop has nothing to interrupt.
Add a statusVerb prefix check so isActive stays false during these
system transitions:
isSystemRestartTransition =
statusVerb.startsWith('Switching provider and model') ||
statusVerb.startsWith('Restarting session')
isActive = chatState !== 'idle' && hasMessages && !isSystemRestartTransition
Match by prefix so any future suffix (e.g. " (CLI starting...)") still
trips the guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#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>