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>
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>
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>
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>
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>
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>
Manager now exposes a Resource Grant manifest and the Agnet control-plane response carries runtime state, agent instances, and permission_manifest so frontend runs can submit bounded resource grants without plaintext credentials.
Constraint: Manager remains the user console while NewAPI stays independent and OpenBao is referenced through secret_ref only.
Rejected: platform-side high-risk approval | client approval is the product boundary; Agnet only validates approval evidence.
Confidence: medium
Scope-risk: moderate
Directive: Do not mix child Agnet runtime model selection with NewAPI billing or expose OpenBao as a public route.
Tested: git diff --check; jq empty locale JSON; go vet ./controller ./model ./router; go test -count=1 ./controller ./model ./router
Not-tested: frontend typecheck/build because local node_modules tooling is absent and user requested builds happen on the VM.
Co-authored-by: OmX <omx@oh-my-codex.dev>
Manager needs a platform-owned secret handoff path so resource bindings can keep only vault references while OpenBao stores tenant-scoped credential payloads.
Tested: go test ./controller ./model ./router && go vet ./controller ./model ./router
Co-authored-by: OmX <omx@oh-my-codex.dev>
- Add hide_from_user_ui on tokens; EnsureUserRelayToken on login and Agnet session
- List/search tokens: end-users see only visible keys; admins see all
- Add /available-models and sidebar entry; i18n en/zh + locales
- desktop download / router hooks if present under heicode/
Extend agnet SK sources with repo_ref and snapshot display; add authenticated
deployment sheet + API types; cockpit toolbar entry; locale strings; minor docs.
Made-with: Cursor
Manager web: Git sources workflow steps and copy; typecheck fixes for auth
and home sections; UsageLog type in usage-log-schema.ts (outside ignored data/).
docs: Agnet platform contract adds runtime_execution and sk_access_policy,
orchestration-plan and acceptance matrix aligned.
Made-with: Cursor
Backend (controller/heicode_agnet_session.go):
- Add HEICODE_ROOT_EMAILS / HEICODE_ADMIN_EMAILS whitelists for JIT role
assignment. Manager no longer trusts Agnet's role claim — admin / root
is granted only by local config.
- Default JIT-synced users to RoleCommonUser.
- Promote-only role sync on every login (never demote).
Frontend auth fixes:
- login() no longer hard-codes id=1; preserves the real manager user id
returned by /api/user/session/from-agnet so the New-Api-User header
matches the cookie session.
- After login, prefer local /api/user/self over Agnet /me so role /
status reflect actual manager state (e.g. whitelist promotion).
- lib/api.ts: scope 401 -> "Session expired" handling to identity
endpoints only; admin-only 401 no longer resets the session.
UI restructuring:
- Default sidebar shows only Code delivery + Console + Personal, plus a
single "System settings" entry for ROLE.ADMIN+.
- system-settings workspace now hosts the full Tenant administration
tree (Channels / Models / Subscriptions / Redemption codes / Tenants /
Templates / Agents / Vendors / All usage logs) for ROLE.ADMIN+, with
System Administration sub-tree gated to ROLE.SUPER_ADMIN.
- Workspace switch triggers on admin paths (channels, users, templates,
agents, subscriptions, models, redemption-codes) — not only
/system-settings.
- system-settings route now allows ROLE.ADMIN+ instead of root-only.
Branding cleanup:
- Drop orphan "NewAPI" i18n keys from web/default locales.
- Rename web/default workspace package newapi-web -> heicode-web.
Config:
- docker-compose.azure-vm.yml exposes HEICODE_ROOT_EMAILS /
HEICODE_ADMIN_EMAILS.
VERSION: 1.1.0-default-user-role
Made-with: Cursor
Replace password-based /api/user/login bridge after external auth with POST /api/user/session/from-agnet: verify access (and optional refresh) against Agnet /api/auth/me, upsert local user by email, then issue the Manager session cookie. Frontend sends bearer tokens only.
Includes HEICODE_AUTH_BASE_URL in compose defaults and .env.example.
Made-with: Cursor
Use a dedicated TwoFactorRequiredError and shared type guard to keep login flow checks type-safe and less brittle than matching magic strings.
Made-with: Cursor
External IdP login alone did not set Gin session; proxied API calls returned 401 and triggered session-expired toast. Call POST /api/user/login after token exchange, support Turnstile on sign-in, handle 2FA pending session, and clear Manager cookie on logout.
Made-with: Cursor