feat(desktop): the payer at Start and the account default
Merged
CH
5d ago+16613+16613+01234567890123456789012345678901234567890123456789-64-64-01234567890123456789
P14 of the native-harness rollout (task "Bringing Codex or Claude Code Max plans into Luke"). It builds on P11 (#4313, agentRuntimeChoice) and P13 (#4298, the Claude plan row).
What changed
- Account default. A new
account_provider_key.for_agents boolean NOT NULL DEFAULT truecolumn. The migration is additive: oneADD COLUMN. Connecting a plan turns the default on. Replacing the token keeps the choice, because the upsert leaves the column alone. Disconnecting deletes the row, so connecting again turns it back on. The service answers it asforAgentsonsubscriptions[]. A newPATCH /api/account/provider-keys{provider, kind, forAgents}turns it, andkindmust be a subscription kind. The PATCH goes through every layer: wire, contract, client, service handler, store, host actproviderKeys.forAgents, and gateway. - Settings. Under the Claude plan row, once a plan is connected, a switch: "Use my Claude plan for coding agents".
- Start sheet. The new-agent draft tab gets a "Who pays" picker beside the model chip. It covers Fork and Continue-in-draft too. It shows only when the account is in the cohort, a Claude plan is connected, and the model is Claude and not a fast variant. The choices are "Claude plan" and eve's payer. eve's payer is labelled "API key" when an Anthropic key is saved and "Luke" otherwise, because that is the key eve actually bills (
account-keys.ts). The picker starts on the account default. A pick is sent aspayeronly for that one agent. - Recording.
codingAgentStartRequestFieldstakes an optionalpayer, andstart_agenttakes one too, in the same schema style (claude_plan|api_key|luke).agentRuntimeChoice(userId, model, picked?)works like this:- When the plan is offered (cohort, Claude model, not fast, native layer, setup-token row), the pick wins; with no pick, the row's
for_agentsdecides. claude_plangoes to Claude Code. Anything else goes to eve and recordsapi_keyif an Anthropic key exists,lukeotherwise.- When the plan isn't offered, the agent goes to eve with no payer recorded, whatever was picked. Nothing changes outside the cohort or for GPT models.
- Continue, Fork, and
start_agentfollow the account default unless a payer is passed. The payer is written once on the agent, as before.
- When the plan is offered (cohort, Claude model, not fast, native layer, setup-token row), the pick wins; with no pick, the row's
Judgment calls
- The default is stored on the plan's own row rather than in account preferences. It is per account, and P25 gets the ChatGPT row for free. Approved by the orchestrator.
- The panel's Start button (the split button with no draft) sends no payer, so the account default decides. The per-agent choice is in the draft tab.
- A pick the offer no longer holds is dropped, so the default decides. Example: the API key was removed after the pick.
Screenshots
Synthetic data, rendered from the panel in a throwaway harness.
Settings switch · Start sheet · picker open:

Tests
runtime-sessions.test.ts:- The API key override records
api_keyon eve. - With no key, it records
luke. - With the toggle off, it records
api_keyorluke. - With the toggle off and the plan picked, it records
claude_planon Claude Code. - Outside the cohort, picking the plan gives eve with no payer.
- The existing case already covers default on giving
claude_planon Claude Code.
- The API key override records
provider-key-store.test.ts: the default is on when connected, turned per account, kept across a replaced token, and back on after reconnecting.provider-keys-procedures.test.ts: the PATCH changes only the bearer's account, a plan the account doesn't hold changes nothing, andapi_keyis refused.coder-agent-tools.test.ts:start_agenttakespayer, and an unknown payer starts nothing.claude-plan-section.test.ts: the switch stands on, turning it off sends the change, and it is absent without a plan.draft-agent-tab.test.ts:- The picker shows the default, and the default sends no payer.
- A pick sends it.
- With the toggle off, the picker stands on the API key.
- Outside the cohort or on a GPT model there is no picker and no keys read.
payer-model.test.ts(new).
Checks
pnpm check:affected could not finish in the agent's 8 GB sandbox: @luke/service#typecheck was OOM-killed (exit 137) under turbo's parallel run, twice. I ran its parts one at a time instead, and all passed:
tscfor service, panel, host, features, database, gateway, web and desktop- biome on the changed files
check:migrationsrepository-checks.sh- vitest over the touched packages: 288 files, 2739 tests
CI runs the full check.
The developer waived Preview runs for this rollout, so this PR merges on green.
Made with Luke