Skip to content

Commit 69defe1

Browse files
committed
feat: remove project route mode
1 parent ece5c7c commit 69defe1

6 files changed

Lines changed: 143 additions & 24 deletions

File tree

docs-linhay/dev/20260524-account-routing-engine.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,13 @@ Routing Engine
165165

166166
- `codex - 账号列表`
167167
- Codex 请求顺序。
168-
- Codex route mode:`sequential / balanced / project`
169-
- Codex 项目名绑定账号组或账号。
168+
- Codex route mode:`sequential / balanced`
169+
- Codex 项目名绑定账号组或账号(兼容保留,不作为主模式入口)
170170
- Codex 路由说明、dry-run/explain、路由探测。
171171
- `claude - 账号列表`
172172
- Claude Code 请求顺序。
173-
- Claude route mode:`sequential / balanced / project`
174-
- Claude 项目名绑定账号组或账号。
173+
- Claude route mode:`sequential / balanced`
174+
- Claude 项目名绑定账号组或账号(兼容保留,不作为主模式入口)
175175
- Claude 路由说明、dry-run/explain、路由探测。
176176

177177
sidecar `AccountRoutingEngine` 是执行层,读取渠道级配置和账号池快照后做决策;它不把渠道级顺序或 route mode 反写成总账号池属性。
@@ -188,8 +188,8 @@ sidecar `AccountRoutingEngine` 是执行层,读取渠道级配置和账号池
188188
前端按三层模型重切页面 ownership:
189189

190190
- `AccountsFeature` 属于 `Account Inventory`。它可以管理账号、账号组、启停、弃用、基础排序和状态展示,但不能再承载 route mode、渠道 fallback、项目绑定或路由探测。
191-
- `CodexAccountListFeature` 属于 Codex `Channel Routing`。它可以整页重做,主职责改为 Codex 渠道账号顺序、`sequential / balanced / project`、渠道组状态、项目绑定、dry-run/explain 和 probe。
192-
- `ClaudeCodeAccountListFeature` 属于 Claude `Channel Routing`。它可以整页重做,主职责改为 Claude 渠道账号顺序、`sequential / balanced / project`、渠道组状态、项目绑定、dry-run/explain 和 probe。
191+
- `CodexAccountListFeature` 属于 Codex `Channel Routing`。它可以整页重做,主职责改为 Codex 渠道账号顺序、`sequential / balanced`、渠道组状态、项目绑定、dry-run/explain 和 probe。
192+
- `ClaudeCodeAccountListFeature` 属于 Claude `Channel Routing`。它可以整页重做,主职责改为 Claude 渠道账号顺序、`sequential / balanced`、渠道组状态、项目绑定、dry-run/explain 和 probe。
193193

194194
建议新增共享前端领域 `frontend/src/features/channel-routing/`,沉淀纯模型、校验、preview 数据和共享工作台组件;Codex / Claude 页面只装配渠道差异。共享不等于共用配置,保存接口、配置 key、preview 数据和 explain trace 必须按渠道隔离。
195195

@@ -372,6 +372,7 @@ GetTokens 自定义能力应放在 GetTokens-owned 包,例如:
372372
- WebSocket request-boundary 特例已收口为单一连接生命周期 helper:guarded pinned auth 释放 pin、关闭旧 execution session、强制 transcript replay。
373373
- WebSocket pinned auth 的 429/401/402/403 前置错误补齐透明 failover:若尚未写出 downstream payload,handler 抑制错误事件、释放 pin、关闭 execution session,并用完整 transcript 立即重派同一 request;若已开始输出,仍保持不做 mid-response 迁移。
374374
- `legacy-routing-cleanup-v01.md` 已更新当前 shim 状态:公共 `RoutePolicy` 兼容 API 是后续上游合并与旧 request policy 的主要兼容边界。
375+
- Codex 前端已把 `session-affinity` / `websocket-pin` / `route-order-header` 收进 `Legacy compatibility mask`,前端只保留总数与说明,不展开三条明细;explain 仍记录兼容遮罩摘要,不再回写到新的通道配置,避免上游合并时扩散改动面。
375376

376377
仍未完成的项:
377378

docs-linhay/memory/2026-05-26.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
# 2026-05-26
22

33
## Codex Channel Routing 兼容遮罩收口
4-
- 决策:Codex 账号列表里的 `session-affinity``websocket-pin``route-order-header` 不再回写新 `ChannelRoutingConfig`前端只展示为 `Legacy compatibility mask`,并在 explain 中记录 `legacy:compatibility-mask=blocked`,以便后续和上游合并时保持最小改动面。
5-
- 验证:`npm --prefix frontend run typecheck``npm --prefix frontend run test:unit -- src/features/channel-routing/tests/channelRouting.test.mjs``npm --prefix frontend run test:unit -- src/features/codex/codexAccountList.test.mjs` 通过;浏览器 dev 预览在 `http://127.0.0.1:5173/#frame=codex&workspace=account-list` 可见兼容遮罩区块与三条信号说明
4+
- 决策:Codex 账号列表里的 `session-affinity``websocket-pin``route-order-header` 不再回写新 `ChannelRoutingConfig`前端只展示 `Legacy compatibility mask` 的总数与说明,不展开三条明细,并在 explain 中记录 `legacy:compatibility-mask=blocked`,以便后续和上游合并时保持最小改动面。
5+
- 验证:`npm --prefix frontend run typecheck``npm --prefix frontend run test:unit -- src/features/channel-routing/tests/channelRouting.test.mjs``npm --prefix frontend run test:unit -- src/features/codex/codexAccountList.test.mjs` 通过;浏览器 dev 预览在 `http://127.0.0.1:5173/#frame=codex&workspace=account-list` 只显示兼容遮罩总数与说明
66
- 边界:这次只收口 Codex Channel Routing 的前端语意和 preview/explain 文案,不改上游核心路由实现,也不扩大到 AGENTS 级别规则。
77

8+
## Codex / Claude 路由模式收成两项
9+
- 决策:当前前端 `Channel Routing` 只保留 `sequential / balanced` 两种可见路由模式;旧 `project` 输入会被归一化为 `sequential`,项目名绑定继续作为兼容数据保留,但不再作为主模式入口。
10+
- 验证:`npm --prefix frontend run typecheck``npm --prefix frontend run test:unit -- src/features/channel-routing/tests/channelRouting.test.mjs` 通过。
11+
- 边界:这次仍然只收前端可见模式,不改 Go 后端 `project` 兼容实现,方便后续和上游合并时保持最小 diff。
12+
13+
## Codex 账号列表外层壳去卡中卡
14+
- 决策:`#frame=codex&workspace=account-list` 的请求顺序 section 外壳从重卡阴影改成 `bg-surface` 承载,标题与消息条继续用 `bg-main`,内层账号行保留原有卡片体系,整体从“卡中卡”转成更轻的工作区段落。
15+
- 验证:`npm --prefix frontend run test:unit -- src/features/codex/codexAccountList.test.mjs``npm --prefix frontend run typecheck``npm --prefix frontend run build` 通过;浏览器在 `http://127.0.0.1:5173/#frame=codex&workspace=account-list` 桌面视图确认 outer section 去掉大阴影,账号卡层级仍保持清晰。
16+
- 边界:这次只收 Codex 账号列表请求顺序区的视觉层级,不改账号排序、路由探测或 Wails 绑定语义。
17+
818
## Codex live sessions 时间线首 token 补齐
919
- 决策:`#frame=codex&workspace=live-sessions` 的请求时间线行补出 `firstTokenLabel`,和 `总 / TTFT` 并排显示;次级的 `stream` 指标继续保留但会在更窄断点隐藏,避免紧凑行过满。
1020
- 补充:线上版也需要稳定显示 `总 / TTFT / 首` 三个指标,因此前三个指标不再受 `sm` 断点隐藏影响;只有第 4 个 `stream` 仍作为宽屏次级指标。

docs-linhay/spaces/20260524-account-routing-engine/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ sidecar 现有账号轮动能力已经包含 `round-robin`、`fill-first`、prio
1818

1919
1. 建立统一 `AccountRoutingEngine`,承载账号路由决策,而不是把自定义端点路由做成普通 HTTP middleware。
2020
2. 把 manual-disabled、rate-limit、自定义端点路由、请求级 allow/deny/order/fallback、session affinity 归一为 policy pipeline。
21-
3. 简化用户可理解的路由语义:先由账号状态、排序值、归属组和组级状态/排序确定可路由账号池,再按顺序、均衡、项目三种模式选择账号
21+
3. 简化用户可理解的路由语义:先由账号状态、排序值、归属组和组级状态/排序确定可路由账号池,再按顺序、均衡两种模式选择账号;项目名绑定保留为兼容数据,但不再作为当前前端可见路由模式
2222
4. 支持 dry-run / explain:用户或开发者可在不请求上游的情况下看到候选账号、过滤原因、排序和最终选择。
2323
5. 支持 shadow mode:新策略先并行计算并记录差异,不立即接管真实请求。
2424
6. 将持久化账号状态、路由规则、运行态快照、请求级临时状态分层,保证热路径快速决策。
@@ -49,8 +49,8 @@ sidecar 现有账号轮动能力已经包含 `round-robin`、`fill-first`、prio
4949
- 保持 root `main.App` 绑定边界,新增 Wails 方法必须经过 root facade 和 generated binding。
5050
- Frontend:
5151
- 总账号池只提供账号和账号组的增删改查、启停、弃用、排序和基础状态展示;不提供账号轮动编排。
52-
- `codex - 账号列表` 负责 Codex 渠道的账号请求顺序、路由模式、项目绑定、路由说明和路由探测。
53-
- `claude - 账号列表` 负责 Claude Code 渠道的账号请求顺序、路由模式、项目绑定、路由说明和路由探测。
52+
- `codex - 账号列表` 负责 Codex 渠道的账号请求顺序、路由模式、项目绑定、路由说明和路由探测;路由模式当前仅暴露 `sequential / balanced`
53+
- `claude - 账号列表` 负责 Claude Code 渠道的账号请求顺序、路由模式、项目绑定、路由说明和路由探测;路由模式当前仅暴露 `sequential / balanced`
5454
- Codex / Claude 可以引用同一个账号组,但各自维护渠道组启停和渠道排序。
5555
- dry-run/explain 调试面板优先跟随对应渠道账号列表展示,显示“为什么选这个账号”和“为什么过滤那些账号”。
5656
- 支持 shadow mode 差异展示和策略启用状态。
@@ -131,6 +131,7 @@ sidecar 现有账号轮动能力已经包含 `round-robin`、`fill-first`、prio
131131
## 当前状态
132132
- 状态:implementation-ready
133133
- 最近更新:2026-05-25
134+
- 2026-05-26 补充:Codex 账号列表的旧 `session-affinity` / `websocket-pin` / `route-order-header` 现在只作为 `Legacy compatibility mask` 的总数与说明呈现,不写入新的 `ChannelRoutingConfig`,也不展开三条明细,以便后续继续和上游代码保持最小合并面。
134135

135136
## 实施入口
136137

frontend/src/features/channel-routing/components/ChannelRoutingWorkbench.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Activity, GitBranch, History, Play, RefreshCw, Save, ShieldCheck, Shuffle, Split, Zap } from 'lucide-react';
1+
import { Activity, History, Play, RefreshCw, Save, ShieldCheck, Shuffle, Split, Zap } from 'lucide-react';
22
import type { main } from '../../../../wailsjs/go/models';
33
import {
44
buildChannelRouteAuditEventSummary,
5+
buildLegacyRoutingMaskPanel,
56
type ChannelID,
67
type ChannelRouteAuditEvent,
78
type ChannelRouteMode,
@@ -33,7 +34,6 @@ const routeModes: Array<{
3334
}> = [
3435
{ mode: 'sequential', icon: Split, label: '顺序' },
3536
{ mode: 'balanced', icon: Shuffle, label: '均衡' },
36-
{ mode: 'project', icon: GitBranch, label: '项目' },
3737
];
3838

3939
export default function ChannelRoutingWorkbench({
@@ -57,6 +57,7 @@ export default function ChannelRoutingWorkbench({
5757
const filteredCount = explain?.filtered?.length ?? 0;
5858
const selectedID = explain?.selectedAccountID || 'none';
5959
const shadow = explain?.shadow;
60+
const legacyMask = buildLegacyRoutingMaskPanel();
6061
const eventSummaries = routeEvents.slice(0, 5).map((event) => buildChannelRouteAuditEventSummary(event));
6162

6263
return (
@@ -155,6 +156,20 @@ export default function ChannelRoutingWorkbench({
155156
<Metric label="账号组" value={config.accountGroups?.length ?? 0} />
156157
</div>
157158

159+
<div className="border-2 border-[var(--border-color)] bg-[var(--bg-subtle)] p-3">
160+
<div className="flex items-center justify-between gap-2">
161+
<div className="text-[length:var(--font-size-ui-xs)] font-black uppercase text-[var(--text-muted)]">
162+
{legacyMask.title}
163+
</div>
164+
<div className="font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase tracking-wide text-[var(--text-muted)]">
165+
{legacyMask.summary}
166+
</div>
167+
</div>
168+
<div className="mt-2 font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase leading-5 text-[var(--text-muted)]">
169+
{legacyMask.note}
170+
</div>
171+
</div>
172+
158173
{message ? <p className="text-[length:var(--font-size-ui-sm)] text-[var(--text-secondary)]">{message}</p> : null}
159174
</div>
160175

frontend/src/features/channel-routing/model/channelRouting.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
export const CHANNEL_ROUTE_MODES = ['sequential', 'balanced', 'project'] as const;
1+
export const CHANNEL_ROUTE_MODES = ['sequential', 'balanced'] as const;
22
export const PROJECT_MODE_FALLBACK_ROUTE_MODES = ['sequential', 'balanced'] as const;
33
export const CHANNEL_FALLBACK_MODES = ['fail-closed', 'fallback-default', 'fallback-global'] as const;
44
export const UPSTREAM_COMPAT_ROUTE_MODES = ['dedicated', 'prefer', 'ordered', 'weighted', 'canary'] as const;
5+
export const LEGACY_CHANNEL_ROUTING_BYPASS_IDS = ['session-affinity', 'websocket-pin', 'route-order-header'] as const;
56

67
export type ChannelID = 'codex' | 'claude';
78
export type ChannelRouteMode = (typeof CHANNEL_ROUTE_MODES)[number];
@@ -69,12 +70,50 @@ export interface ChannelRoutingConfigDraft {
6970
shadowRouteMode?: unknown;
7071
}
7172

73+
export type LegacyChannelRoutingBypassID = (typeof LEGACY_CHANNEL_ROUTING_BYPASS_IDS)[number];
74+
export type LegacyChannelRoutingBypassDisposition = 'blocked' | 'ignored';
75+
76+
export interface LegacyChannelRoutingBypass {
77+
id: LegacyChannelRoutingBypassID;
78+
label: string;
79+
disposition: LegacyChannelRoutingBypassDisposition;
80+
detail: string;
81+
}
82+
83+
export interface LegacyRoutingMaskPanel {
84+
title: string;
85+
summary: string;
86+
note: string;
87+
hasDetails: false;
88+
}
89+
7290
export interface NormalizedChannelRoutingConfig {
7391
config: ChannelRoutingConfig;
7492
ignoredUpstreamModes: UpstreamCompatRouteMode[];
7593
invalidModes: string[];
7694
}
7795

96+
export const LEGACY_CHANNEL_ROUTING_BYPASSES: LegacyChannelRoutingBypass[] = [
97+
{
98+
id: 'session-affinity',
99+
label: 'Session affinity',
100+
disposition: 'blocked',
101+
detail: '跳过,不进入新配置;仅保留为 runtime 粘性信号',
102+
},
103+
{
104+
id: 'websocket-pin',
105+
label: 'WebSocket pin',
106+
disposition: 'blocked',
107+
detail: '屏蔽,不进入新配置;仅保留为连接级运行态信号',
108+
},
109+
{
110+
id: 'route-order-header',
111+
label: 'Route order header',
112+
disposition: 'ignored',
113+
detail: '跳过,不进入新配置;探测路径不再注入顺序头',
114+
},
115+
];
116+
78117
export interface ChannelRouteAuditEvent {
79118
id: string;
80119
recordedAt: string;
@@ -190,6 +229,19 @@ export function updateChannelRoutingConfig(
190229
};
191230
}
192231

232+
export function buildLegacyRoutingBypassSummary() {
233+
return `${LEGACY_CHANNEL_ROUTING_BYPASSES.length} 个 legacy 输入已从新配置中屏蔽`;
234+
}
235+
236+
export function buildLegacyRoutingMaskPanel(): LegacyRoutingMaskPanel {
237+
return {
238+
title: 'Legacy compatibility mask',
239+
summary: buildLegacyRoutingBypassSummary(),
240+
note: '这些信号只保留为兼容层,不写入新配置,也不影响上游合并后的主路由模型。',
241+
hasDetails: false,
242+
};
243+
}
244+
193245
export function buildChannelRouteAuditEventSummary(event: ChannelRouteAuditEvent): ChannelRouteAuditEventSummary {
194246
const routeMode = String(event.routeMode || 'unknown').trim() || 'unknown';
195247
const selected = String(event.selectedAccountID || 'none').trim() || 'none';
@@ -290,8 +342,6 @@ function normalizeProjectFallbackRouteMode(
290342
const classified = classifyChannelRouteMode(input);
291343
if (classified.kind === 'upstream-compat') {
292344
ignoredUpstreamModes.push(classified.mode);
293-
} else if (classified.kind === 'gettokens' && classified.mode === 'project') {
294-
invalidModes.push(classified.mode);
295345
} else if (classified.kind === 'invalid' && classified.mode) {
296346
invalidModes.push(classified.mode);
297347
}

0 commit comments

Comments
 (0)