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
10 changes: 10 additions & 0 deletions .trellis/spec/frontend/agent-directory.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ or bypass flag.

### Runtime and readiness projection

- `useAgentDirectoryScan` distinguishes hidden from unmounted owners. Hidden
mounted pages buffer settled rows and reconcile on return; unmounted owners
ignore late success, rejection and aggregate completion without recording a
completion timestamp or dispatching UI state. Retained start/readback callbacks
do not restart or update a disposed view. This does not cancel native jobs.
- The synchronous scan admission ref keeps its newer `requestId` when StrictMode
replays an effect from an older render. One pending scan must not issue a second
set of readiness requests during effect replay. The existing scan hook tests
cover delayed success/rejection after unmount, retained callbacks, StrictMode
and hidden-result reconciliation.
- Runtime `detected`/`running` preserve `true | false | null`. Unknown is
rendered as unknown/unverified, not “not installed.”
- Readiness and inventory are separate queries keyed by canonical Agent ID and
Expand Down
131 changes: 131 additions & 0 deletions .trellis/spec/frontend/grok-subscription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Managed Grok Subscriptions

## 1. Scope / Trigger

Read before changing the Grok account/model picker or managed subscription
binding. `pages/models/XaiSubscriptionSection.tsx` is the single picker; 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`.

General write/draft/secret rules remain in [Models](./models.md); native
authority stays in [Managed Auth](../backend/managed-auth.md),
[Proxy Runtime](../backend/proxy-runtime.md) and
[Change Plan Executor](../backend/change-plan-executor.md).

## 2. Signatures

`ProvidersPort.bindXaiManaged` accepts the request and returns the result below.
`ProvidersPort.fetchXaiManagedModels(accountId)` uses the existing
`WorkBuddyFetchModelsResult` shape for discovery; sharing that shape does not
admit a WorkBuddy subscription workflow.

```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;
}
```

## 3. Contracts

- 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.

## 4. Validation & Error Matrix

These focused cases extend the general write, secret and lifecycle matrix in
[Models](./models.md).

| 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. |

## 5. Good / Base / Bad Cases

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.

## 6. Tests Required

`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.

## 7. Wrong vs Correct

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.
4 changes: 4 additions & 0 deletions .trellis/spec/frontend/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ focused feature owner. Apply [Component Guidelines](./component-guidelines.md),
| [External Agent Auth](./agent-auth.md) | Native auth observations, session ownership and safe handoff. |
| [Managed Auth](./managed-auth.md) | Accounts/connections/request sources, login and impact confirmation. |
| [Models](./models.md) | Drafts, connectivity, native save and existing model workflows. |
| [Managed Grok Subscriptions](./grok-subscription.md) | Explicit account/model binding, native readback and subscription scope. |
| [Assignments](./assignments.md) | Shared seven-target selection and serialized mutations. |
| [Skills](./skills.md) | Discovery, installed items, backups and assignment. |
| [MCP](./mcp.md) | Catalog/launch validation, CRUD, installation and assignment. |
Expand All @@ -59,6 +60,9 @@ unit tests. Run `mise run test:browser` for production boot and browser behavior
and `mise run test:performance` serially for actual motion/navigation costs.
Task/SPEC-only edits still run `mise run check:contracts`; active tasks use the
exact task exclusion at prearchive, then validate effective context references.
Required owner documents must fit `context_injection.max_file_bytes`: use
`task.py validate` to detect truncation and split a cohesive feature contract
rather than raising the limit or silently losing the end of a required spec.

Migrations preserve native commands and persisted identities, update effective
source/SPEC/CI/test references and explicitly account for retired UI assertions.
Expand Down
13 changes: 13 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,17 @@ an apply instruction.
The page sanitizes returned warning codes against the closed
`CodexProviderMutationWarning` union.

### Existing Grok subscription to a local Agent

[Managed Grok Subscriptions](./grok-subscription.md) owns the explicit
account/model picker, binding DTOs, target-local failure/readback rules and
subscription regression matrix. Read it when changing `bindXaiManaged`,
`fetchXaiManagedModels` or `XaiSubscriptionSection`. It extends this Models
contract rather than duplicating authentication or Change Plan ownership.
Claude Code applies only after confirmation and authoritative rereads; Codex
binding remains a draft before the existing Auth source-plan flow. WorkBuddy
keeps its API-key workflow, and a saved source is not live entitlement evidence.

### WorkBuddy flow

- WorkBuddy reads `getStatus()` and `getModelIds()` separately. A read is
Expand Down
7 changes: 7 additions & 0 deletions .trellis/spec/frontend/surfaces-responsive.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ dark palette and preference lifecycle are owned by [Appearance](./appearance.md)
surfaces with readable text; pages must not hardcode a theme's fills or white
foreground assumptions. Selection/control sheen also has paired token roles so
stacked translucent highlights do not wash out dark-mode text.
Agent directory cards use `--fy-surface-inset` as their readable backing, not
the low-alpha ambient `--fy-surface-soft`. The dark inset darkens a bright
composited parent instead of adding another bright translucent layer. Retain
4.5:1 for both headings and supporting text; `blue-themes.spec.ts` also replays
the bright RGB(111,141,164) backing observed in Linux WebKit CI. This focused
material regression supplements, rather than replaces, the unchanged whole-page
Chromium/WebKit composited contrast checks.
The content viewport is not a nested backdrop sampler.
CSS consumes the blur/rim/sheen tokens directly, including preference changes.

Expand Down
Loading