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>
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>
Worker 3 completed the assigned validation pass without source edits, recording backend pass evidence plus environment and contract findings for leader integration.
Tested: go vet ./controller ./model ./router; go test focused resource and Agnet grant cases; go test ./controller ./model ./router; git diff --check; focused docs secret scan.
Not-tested: frontend tsc/eslint require bun or full web dependencies; live Agnet curl checks require credentials and service URL.
Co-authored-by: OmX <omx@oh-my-codex.dev>