Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .trellis/spec/backend/change-plan-executor.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ faultPoints = before_managed_write,

## 3. Contracts

### Grok managed Codex source admission

- Codex switch admits a managed xAI source only when its explicit bound vault
record is ready, has Proxy purpose/consumer, and is FyAgent-owned. Recheck on
plan creation and apply; JSON token-file existence grants no capability.
- The closed managed shape has an empty auth object, selected `xai` provider,
local Responses wire protocol, canonical Grok CLI subscription base URL and
an explicit bounded model. The native proxy converts to the vendor's Chat
Completions protocol; the local wire declaration is not the upstream protocol.
Credentials remain in the vault. Preview uses the same local proxy projection
as the existing Provider writer; apply starts/adopts that listener and retains
target rollback and readback. No fourth adapter or new schema is introduced.
- A plan/configuration success proves native configuration, not live upstream
quota consumption. Synthetic vault + loopback upstream integration is separate
from actual subscription and Windows acceptance evidence.


### Wire version and phase model

- `CHANGE_PLAN_CONTRACT_VERSION = fyagent-change-plan/v2`.
Expand Down
32 changes: 32 additions & 0 deletions .trellis/spec/backend/managed-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,38 @@ returns a refresh token or SecretRef.

## 3. Contracts

### Grok subscription binding to local Agent providers

- `bind_xai_managed_provider` accepts exactly `{ app, accountId, modelId }`.
`accountId` is the public overview identity, not a default or caller-supplied
legacy token-store ID. Binding resolves an xAI `proxy_upstream` /
`fyagent_proxy` credential, requires `ready` and `refresh_owner=fyagent`, and
reads the matching SecretRef bundle before Provider mutation.
- Native Grok/OpenCode lineages are not eligible even when the same account
identity is ready. No upstream access/refresh token is copied into a Provider,
renderer, Change Plan, or Agent auth file. The existing proxy resolver remains
the only refresh owner and rejects credentials whose current status changed.
- The binding uses a stable target/account/model Provider identity; an existing
row with a different definition fails `provider_conflict` instead of being
overwritten. A new source name includes a short public account label and a
stable identity digest so equal model/display names remain distinguishable.
A saved name is preserved and excluded from binding identity; renaming the
source or changing an account's display name does not break idempotency.
Claude Code activates through the existing Provider transaction;
Codex saves a draft and uses Change Plan; Claude Desktop saves a draft for its
existing dedicated profile application, with `activated=false`.
- Result fields remain `providerId`, `providerName`, `app`, `alreadyBound`,
`activated`. Errors contain only a closed `code`: `invalid_request`,
`account_unavailable`, `provider_conflict`, `apply_failed_rolled_back`, or
`rollback_partial_state_unknown`. The last code never means restored.
- `get_xai_oauth_models(accountId)` checks the same explicit overview identity
and vault bundle, then returns the documented `grok-build` route suggestion.
No subscription catalog endpoint is established: do not send session tokens
to the API-key `/models` endpoint or label suggestions as account entitlement.
- New vault-created binding IDs never fall back to an in-memory legacy JSON
account when the vault account disappears or becomes unavailable.


### Identity versus credential session

- `ManagedIdentity` is keyed by `(provider, provider_subject, provider_tenant)`,
Expand Down
40 changes: 40 additions & 0 deletions .trellis/spec/backend/proxy-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,46 @@ backup body, or replacement routing implementation.

## 3. Contracts

### Managed Grok activation

- `xai_oauth` inference is pinned to the official CLI session route
`https://cli-chat-proxy.grok.com/v1/chat/completions`. Ordinary API-key
providers retain `api.x.ai`. Claude Messages and Codex Responses reuse the
existing Chat converters, including streaming tool calls; Codex's tool
catalog uses the matching `ProxyChat` profile.
- After model mapping and header overrides, write `X-XAI-Token-Auth:
xai-grok-cli` and `x-grok-model-override` from the final outbound model.
Replace incoming copies; JSON `model` alone does not select the CLI route.
The integration fixture must assert vendor host/path before redirecting I/O
to loopback. Its streaming tests inspect tool arguments and terminal events
in both downstream protocols; synthetic success is not real quota evidence.
- Claude Code/Codex Grok subscription activation composes the existing Provider
transaction with the one `ProxyService`. Acquire the target mutation lock,
then the shared managed-activation guard, then any listener-start guard.
Hold activation ownership from snapshots through commit or compensation;
another target cannot adopt a listener whose owner is still rolling back.
Manual per-app takeover uses the same guard after its existing app lock.
Activation requires a loopback address and
a successfully bound listener before publishing local configuration.
- Capture live file preimages, Provider/current markers, existing backup and
the target proxy configuration before mutation. Keep an existing restore
backup; otherwise capture the outgoing native configuration. Do not backfill
an outgoing API key into the new managed Provider.
- Write/read back the local endpoint using the target owner. Preserve Claude
permissions/unrelated environment and Codex native auth/MCP/unrelated source
configuration. Set only the selected target enabled; disable its automatic
failover so an expired subscription cannot silently use a paid API source.
- Failure restores files, row/current selection, backup, and target proxy flags.
Stop a newly created listener only when no other takeover uses it; never stop
a listener that was already running. Report incomplete compensation as
state unknown. Read back restored target/global configuration and listener
state before confirming compensation. Port-conflict regression asserts all
original flags/files; a gated two-target test proves a failed activation
cannot stop the subsequently committed target's listener.
- Existing quit/restore/next-start behavior stays authoritative; this feature
does not add a daemon, Docker, cloud service, or system-wide proxy.


### Command and state ownership

- `commands/proxy.rs` is transport only: parse bounded wire input, acquire
Expand Down
108 changes: 108 additions & 0 deletions .trellis/spec/frontend/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ interface ProvidersPort {
fetchModels(baseUrl: string, apiKey: string): Promise<FetchedModelRef[]>;
checkReachability(baseUrl: string): Promise<ReachabilityResult>;
checkModel(request: ModelProbeRequest): Promise<ModelProbeResult>;
bindXaiManaged(request: BindXaiManagedRequest): Promise<BindXaiManagedResult>;
fetchXaiManagedModels(accountId: string): Promise<WorkBuddyFetchModelsResult>;
}

interface WorkBuddyPort {
Expand Down Expand Up @@ -210,6 +212,112 @@ an apply instruction.
The page sanitizes returned warning codes against the closed
`CodexProviderMutationWarning` union.

### Existing Grok subscription to a local Agent

`pages/models/XaiSubscriptionSection.tsx` is the single account/model picker
for the existing Grok integration; it composes the current Models panels and
Managed Auth overview, not a second authentication page. Agent Grok model
entries link to the Claude/Codex Models target with the existing validated
Agent-return tuple. Login and account recovery use `/auth?view=accounts`.

```ts
interface BindXaiManagedRequest {
app: "claude" | "claude-desktop" | "codex";
accountId: string; // Explicit overview ma1 identity, never legacy/default ID.
modelId: string;
}
interface BindXaiManagedResult {
providerId: string;
providerName: string;
app: "claude" | "claude-desktop" | "codex";
alreadyBound: boolean;
activated: boolean;
}
```

- The overview is Query-owned under `featureKeys.managedAuthOverview` and
pauses automatic reads while hidden. The user explicitly selects an xAI
account. Only `health=ready` is selectable; health does not prove the native
proxy-purpose credential exists, so native revalidates it during discovery
and binding. No default-account fallback or renderer-held credential exists.
- Account selection reads `get_xai_oauth_models` with that exact overview
identity and renders the existing selectable model chips. Native validates
the vault credential and returns documented CLI route suggestions; it does
not send the subscription token to the API-key `/models` catalog. The UI
labels these as official-example options, not an account entitlement list,
and labels this integration experimental. Changing accounts clears the old
selection/list; no model is hardcoded in the renderer or selected implicitly.
A failed/empty options read exposes manual input. Model IDs are 1–128 ASCII
characters, start with an alphanumeric character and otherwise admit only
alphanumeric, `.`, `_`, `-` and `:`. Discovery is not entitlement/use proof.
- The bind request has exactly the three keys above. Native response parsing
checks exact keys, the submitted target identity, bounded provider ID/name,
boolean fields and `activated === (app === "claude")`. Invalid or unknown
responses fail closed; no raw native diagnostic enters product copy.
- Claude Code confirmation discloses the native write targets and shares the
Provider panel's synchronous write guard. A positive result requires native
application plus provider-summary/current-ID and managed-auth rereads.
Any failed reread or unknown write result blocks further writes to that
target through the existing Models parent block. Other targets remain usable.
This target block still applies when switching targets unmounts the picker
while a bind is pending; mounted guards may suppress only local UI updates.
- Codex binding saves a draft only. The result links to the existing Auth
`consumer=codex&view=connections` source workspace, where the user selects
the named saved source, previews and confirms the existing Change Plan.
Models never mounts another source-switch workspace. This picker exposes
only Claude Code and Codex CLI binding. The native contract retains Desktop
draft compatibility, but Models provides no Desktop action until its own
authoritative saved-source readback and application path are integrated.
- Bind errors are the closed `{code}` values `invalid_request`,
`account_unavailable`, `provider_conflict`, `apply_failed_rolled_back`, and
`rollback_partial_state_unknown`. The last value and malformed failures
block writes; known preflight/confirmed-restoration failures retain a safe
retry path. Once binding returned, subsequent owner-read errors are always
unconfirmed regardless of their error shape.
- Successful binding invalidates/rereads the affected Provider summary and
managed-auth overview. Account/login changes reuse the same overview key.
The UI states that using the subscription requires FyAgent running in the
background, and separates saved config from actual calls/quota use.
- WorkBuddy has no subscription picker: CLI route suggestions do not describe
models supported by its configured API-key service. Its existing service/key
input, model discovery and save-plan behavior stay intact.

Required regressions: `XaiSubscriptionSection.test.tsx` covers explicit
account/model selection, changed/expired accounts, native failure/readback,
per-target draft/application copy, navigation and hidden reads; Models Page
coverage verifies that WorkBuddy has no unsupported subscription picker;
`xaiSubscriptionPort.test.ts` covers exact vault identity payloads, invalid
fields, target/result agreement, closed errors and browser native-only behavior.
`tests/browser/xai-subscription.spec.ts` covers the real renderer's Claude
confirmation and Codex source preview/apply using synthetic IPC fixtures.
These are not native-file, live-subscription or Windows evidence.

The subscription boundary adds these focused validation cases; general write,
secret and lifecycle failures remain in the matrix in section 4.

| Condition | Required renderer behavior |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| No explicit account/model, or selected account is no longer ready | Disable confirmation; never select the default account or another model. |
| Discovery fails or returns no usable IDs | Offer explicit manual input; do not manufacture a model or change the upstream source. |
| Bind result names another target, has excess fields, or contradicts activation semantics | Reject the result and mark target state unconfirmed. |
| Native rejects unavailable account/conflict, or confirms restoration | Show the closed failure and retain an explicit retry/recovery entry. |
| Native cannot confirm restoration, or either post-bind owner read fails | Block further target writes; no optimistic success or automatic write retry. |
| Codex draft is saved | State that it is a draft and expose the existing Auth source-plan continuation. |
| Claude Code picker is open | Offer only its own target; do not save a Desktop source then read the Claude Code summary. |
| WorkBuddy is selected | Keep its service/key workflow; do not offer subscription route suggestions. |

Good: a user chooses an existing xAI account and a suggested model, confirms
Claude's native file disclosure, and sees applied copy only after native and
owner readback. Base: suggestions are unavailable, so the user enters a model ID
and native still revalidates the selected account. Bad: use the first/default
account, resurrect `auth_get_status`, or call a draft a working subscription.

Wrong: `bindXaiManaged({ app: "codex", accountId: defaultAccountId })` followed
by immediate "已切换" copy. Correct: pass the exact selected
`{app, accountId, modelId}`, reread the saved source, then hand off to Auth's
existing preview/apply workspace. A model fetch or saved draft is never live
usage evidence.

### WorkBuddy flow

- WorkBuddy reads `getStatus()` and `getModelIds()` separately. A read is
Expand Down
5 changes: 5 additions & 0 deletions .trellis/tasks/08-31-grok-login-trichotomy/check.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"file": ".trellis/spec/backend/external-agent-p0.md", "reason": "No verified Grok login"}
{"file": ".trellis/spec/frontend/models.md", "reason": "Reject login controls on Models Quick Setup"}
{"file": ".trellis/tasks/08-31-grok-login-trichotomy/research/current-login-surfaces.md", "reason": "Do not start device-code from Agent Auth"}
{"file": ".trellis/tasks/archive/2026-09/08-31-grok-first-class-iteration/summary.md", "reason": "Reject copy that mixes the three roads"}
{"file": ".trellis/tasks/archive/2026-09/08-31-grok-first-class-iteration/research/hil-matrix.md", "reason": "Login live cases on both machines"}
41 changes: 41 additions & 0 deletions .trellis/tasks/08-31-grok-login-trichotomy/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
> Historical subplan: the 2026-09-08 parent task artifacts supersede these execution instructions. Retained acceptance items are not evidence of completion.

# Design — Grok login trichotomy

先读父任务 [summary.md](../archive/2026-09/08-31-grok-first-class-iteration/summary.md)。事实和行号见 `research/current-login-surfaces.md`。用例见 [use-cases.md](./use-cases.md)。

## 边界

本子任务只立三条登录路标。不写 Claude / Desktop / Codex / WorkBuddy,不新做「查 Grok 登没登」。

三条路已经存在,只是散在三处。不要合成一个控件。

| 路 | 现有主人 | 这轮改什么 |
|---|---|---|
| 官方 `grok login` / `logout` | V2 Agent 配置页 `AgentAuthStatusPanel` → `start_agent_auth_session` → `launch_auth_action(GrokBuild)` | 文案点名终端命令;终点仍是 `handoff_complete` + `handoff_only` |
| SuperGrok 扫码 | v1 认证中心 `AuthCenterPanel` / `XaiOAuthSection` → `auth_start_login("xai_oauth")` | Agent / Codex 认证区指路到认证中心;不在 Agent 页启动扫码 |
| API 钥匙 | V2 模型页 Quick Setup `fyagent-v2-quick-setup-grokbuild` | **默认不改**。这里不要出现 `grok login` |

## 合同(不得破)

- Grok 官方登录:**禁止**出现「已验证」「已登录」「认证结果已验证」。权威是 `unverified`。
- Claude 的 `claude auth status` 验证环保持原样。
- Codex Agent 认证保持 `fyagent_managed`,没有登录按钮。
- 禁止读/写 `~/.grok/auth.json` 来证明已登录。额度查询可以继续读,登录成功不能靠它。
- 禁止从 Agent 配置页调用 `auth_start_login`。
- 禁止把 v1 `AuthCenterPanel` / `XaiOAuthSection` 进口到 `src/v2`。
- 没改模型草稿则 #141 B7 标 `not touched`。默认不要动 `ProviderPanel` / `quickSetup.ts`。

## 数据流

1. 人在 Grok Agent 配置页点登录 → 终端跑 `grok login` → 会话立刻 `handoff_complete`。
2. 人要扫码 → 被指到 v1 设置「认证」页的 `xAI (Grok OAuth)`。
3. 人要填钥匙 → 还在模型页,和上面两路无关。

## 兼容

ChatGPT 登录(`codex_oauth`)不动。Grok 安装/升级不动。

## 回滚

只撤文案和指路。不要动 `auth_sessions.rs` 的 handoff 短路径,除非测试证明字改了但状态机坏了。
7 changes: 7 additions & 0 deletions .trellis/tasks/08-31-grok-login-trichotomy/implement.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"file": ".trellis/tasks/archive/2026-09/08-31-grok-first-class-iteration/summary.md", "reason": "Shared plain-language plan"}
{"file": ".trellis/spec/backend/external-agent-p0.md", "reason": "Grok remains handoff_only"}
{"file": ".trellis/spec/frontend/reuse.md", "reason": "Edit AgentAuthStatusPanel, do not fork"}
{"file": ".trellis/spec/frontend/models.md", "reason": "Models Quick Setup is API key only; do not add login"}
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "Reuse current auth owners"}
{"file": ".trellis/tasks/08-31-grok-login-trichotomy/research/current-login-surfaces.md", "reason": "Current login surfaces and copy"}
{"file": ".trellis/tasks/archive/2026-09/08-31-grok-first-class-iteration/research/hil-matrix.md", "reason": "AT1-AT5 H1-H4 belong to this window"}
Loading
Loading