Skip to content

feat(chat): show the steward execution binding in the manager header - #4419

Merged
huangruiteng merged 2 commits into
mainfrom
codex/steward-execution-chip-20260915
Sep 15, 2026
Merged

huangruiteng merged 2 commits into
mainfrom
codex/steward-execution-chip-20260915

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

The steward channel already selects its executor endpoint and model, and the Chat
capabilities payload already projects that selection (manager.channel_binding:
endpoint, endpoint source, executor kind, resolved model and source, credential
variable name, available, unavailable_reason). The packaged frontends rendered none
of it, so an operator could not read which executor answers for the steward, which
credential pays for it, which model follows it, or that a selected managed host cannot
serve the channel at all. The contract doc already promised "a frontend can show which
executor and model the steward channel resolved", with no surface doing it.

What changed

One compact chip in the manager channel header — executor · executor kind · model —
plus the transport gate inline when the projection proves the selected endpoint cannot
serve this channel.

  • apps/presentation/dashboard/src/data/chat.ts lifts the payload shape into a named,
    reused managerChannelBindingSchema / ManagerChannelBinding and declares the
    optional manager.channel_binding field, so the projection is no longer silently
    dropped by the capabilities parse.
  • channel-header.tsx renders the chip from the projected fields only. The kind keeps
    the individual / managed vocabulary of the governed Turn surface, and a kind this
    build does not recognize renders as an unclaimed registered endpoint instead of being
    labelled as one the operator can act on.
  • dashboard-page.tsx / personal-workspace-page.tsx thread the projection through one
    prop; the value is never derived locally.
  • An unavailable selection is carried by the chip itself (is-unavailable) and the
    narrow header keeps this row, so a wrapped-out reason cannot leave the state looking
    healthy.

This is display-only: no change to selection, credential resolution, model resolution,
Chat admission, or the managed-host transport gate. An older service that projects no
binding keeps the previous header.

Evidence

  • New browser scenario execution-chip (registered in personal-workspace-browser-smoke.mjs)
    covers the shipped binding, the managed-host binding, an unrecognized kind, the
    no-binding case, and the 390px layout where the reason must stay readable.
  • npm run build:chat (tsc + vite), npm run smoke:goal-order, the workspace theme test,
    and loopx canary premerge --from-git-diff (19 selected, 0 failures) pass locally.
  • Browser smoke passes in both modes: scenarios=navigation-sorting,chat-recovery,typed-actions,execution-chip.
  • Packaged surface: loopx/web/chat/index.html references assets/index-BWxHtIJu.js and
    assets/index-DMq_dBB9.css; every retained asset exists; the retention manifest keeps
    the new generation plus the one the previous index.html referenced.
  • First-screen evidence is committed with the contract update:
    docs/assets/personal-workspace/steward-execution-chip-desktop.png,
    ...-unavailable-desktop.png, ...-unavailable-mobile.png.

Scope

Re-cut from a stacked base onto latest main; this PR now carries only the manager
header binding plus its evidence and the refreshed packaged bundle. The steward channel
default stays codex — a configured provider credential never re-points it.

@huangruiteng
huangruiteng force-pushed the codex/steward-channel-binding-20260915 branch from 8b35803 to 25f712b Compare September 15, 2026 04:34
@huangruiteng
huangruiteng force-pushed the codex/steward-execution-chip-20260915 branch from 6e1f80f to 309beb4 Compare September 15, 2026 04:34
@huangruiteng
huangruiteng force-pushed the codex/steward-channel-binding-20260915 branch from 25f712b to ea96161 Compare September 15, 2026 05:13
@huangruiteng
huangruiteng force-pushed the codex/steward-execution-chip-20260915 branch from 309beb4 to 2da0bfb Compare September 15, 2026 05:13
@huangruiteng
huangruiteng force-pushed the codex/steward-channel-binding-20260915 branch from ea96161 to 90f4f2f Compare September 15, 2026 06:47
@huangruiteng
huangruiteng force-pushed the codex/steward-execution-chip-20260915 branch from 2da0bfb to d69970d Compare September 15, 2026 06:48
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Status update: this PR is held, not abandoned, and it is not mergeable as-is.

Two things changed around it:

  1. Its base branch codex/steward-channel-binding-20260915 belongs to feat(chat): bind the steward channel defaults to the operator credential #4417, which is closed: that rule was replaced by feat(chat): select the steward channel executor explicitly #4446 (feat(chat): select the steward channel executor explicitly). The binding shape this PR rendered no longer exists, so the chip has to be refreshed onto the corrected payload before it can merge.
  2. The chip is a first-viewport change on the product home surface, so it is behind the first-screen preview gate. A refreshed version is implemented and validated locally and is waiting on owner preview approval before it is committed or pushed.

What the refreshed chip does, on the corrected binding:

  • the chip reads executor_endpoint · executor_kind · model, so codex · individual CLI login · gpt-6-astra is what ships, and an executor and model that disagree are visible in the header instead of arriving as one silent configuration;
  • the inline note appears only when the binding resolves an endpoint that cannot serve the channel (available: false), and it names the selected host and the missing Chat transport;
  • the schema in apps/presentation/dashboard/src/data/chat.ts matches the corrected channel_binding fields (executor_kind, available, unavailable_reason in place of executor_transport_reason).

Validation on the refreshed branch (dashboard smoke:personal-workspace, which runs the goal-order contract, the workspace-theme contract, and the browser scenarios navigation-sorting, chat-recovery, typed-actions, and the new execution-chip): all four scenarios PASS, and the packaged loopx/web/chat assets regenerate with no diff against a clean build:chat, so the packaged-asset step stays green.

This PR will be replaced by a main-based branch once the preview is approved. Closing it now would only lose the discussion, so it stays open as the record of the approved design.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

状态:暂缓合并(两项前置条件未满足)

这条 PR 目前保持 open、不推新提交,原因有两条,都需要先解决。

  1. 分支形态需要重切。 当前 head d69970dc1c3811e142cc2693c9d74675f77d09c0 的 base 是 codex/steward-channel-binding-20260915(已废弃的堆叠分支),因此它对 main 的 diff 包含整条旧栈(47 文件 +1885/-188)。这个 PR 自身只有两个 chip 提交(0a7f3dc54 与 d69970dc1)。托管侧已经重切为 main-based 的 feat(turn): select the managed Turn host explicitly #4443 与 feat(chat): select the steward channel executor explicitly #4446,管家执行器读回也已在 feat(chat): select the steward channel executor explicitly #4446 中交付;本 PR 应当只用「管家头部展示执行绑定」这一件事重切到最新 main,并把打包资产一并刷新(build 作业会校验打包资产与源码一致)。
  2. 首屏预览待确认。 该改动落在 Chat 管家界面的顶部(manager header)执行绑定 chip,属于公共产品界面的首屏呈现,按仓库规则需要先给出预览并取得确认,之后才能定稿。刷新版(executor · kind · model,例如 codex · 个人 CLI 登录 · gpt-6-astra,仅在不可用时附带下一步提示)已在本地构建并验证通过 smoke:personal-workspace 四个场景,且打包产物可由源码稳定重建;预览就绪,等确认后再推分支。

因此本轮不给出合并结论。重切并取得预览确认后,我会按同一执行契约提交一次完整评审(含 loopx pr-review --check-result 回执)。

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

steward 通道已经会从 operator 凭据解析出 executor endpoint 与 model,也把这份绑定放进了 Chat capabilities payload,但打包前端从头到尾没显示它:operator 既看不到这次 manager 会话跑在哪个 endpoint、用哪个 model、来源是什么,也看不到"已解析的托管 host 还没有 Chat 通道"这一限制。把这两个事实搬到 manager 头部是对的——它把原本只能靠猜的信息变成可回读的界面事实。

改动思路

  1. chat.ts 把原先内联在 chatCapabilitiesSchema.manager.channel_binding 的对象形状提升为具名 managerChannelBindingSchema 并导出类型,供组件复用。
  2. dashboard-page.tsx 读取 capabilities.manager?.channel_binding,经 personal-workspace-page.tsx 透传给 channel-header.tsx。
  3. 头部在"未选中 Goal 且有 binding"时渲染一枚紧凑 chip(endpoint、已解析 model、来源标签),并在 executor_transport_reason 非空时追加一句"托管执行器尚无 Chat 通道"的说明。
  4. 新增浏览器场景 examples/personal-workspace-browser/execution-chip.mjs,并更新打包产物与资源保留清单。

具体改动

  • apps/presentation/dashboard/src/data/chat.ts(:99 具名 schema)、channel-header.tsx(:89 来源标签映射、:113-121 chip 与说明句)、i18n.tsx(en/zh 各 4 条)、personal-workspace-page.tsx / dashboard-page.tsx(prop 透传,:1634 数据入口)、personal-workspace.css(7 行样式 + brutal 主题半径覆盖)。
  • examples/personal-workspace-browser/execution-chip.mjs(新增 84 行)、personal-workspace-browser-smoke.mjs(注册场景)、fixture.mjs(managerChannelBinding 注入钩子)。
  • 打包面:loopx/web/chat/index.html、assets/index-Dv0s3LKG.js、assets/index-ClrAI17B.css、asset-retention.json(新一代资源 + 保留上一代以便已打开页面继续加载)。

关键内容讲解

  1. 数据只来自后端投影:chip 渲染的是 executor_endpoint / model / model_source / executor_transport_reason 四个字段,前端不重算解析;credential_env_var 只用于声明变量名,界面不显示凭据值。
  2. 无投影时行为与 base 完全一致:capabilities.manager?.channel_binding ?? null,为 null 就不渲染;场景第二段专门断言没有 binding 时 .personal-execution-chip 数量为 0,这正是 feature-off parity 的证据。
  3. 打包面自洽:我解析了 index.html 的引用并遍历 asset-retention.json 的条目,逐个检查文件存在——引用的 assets/index-Dv0s3LKG.js、assets/index-ClrAI17B.css 与清单里的 26 条资源都在;打包 JS/CSS 中也确实各含一处 chip 标记。
  4. CI 在受审 head 上全绿:test-shard (1-4)、dashboard-acceptance、kernel-static-checks、node-minimum/forward-compatibility、merge-gate、Sign-off 全部 pass。

对主干的风险

P3(未知 model_source 会被标成"厂商默认")。channel-header.tsx:89-94 的三元链只认 env_override 与 operator_credential_default,其余一律落到 header.managerModelSourceVendorDefault;而 chat.ts:99 里 model_source 是 z.string(),后端将来多一个取值就会被静默标成"厂商默认",operator 可能据此误判计费与排障方向。最小修法:把 model_source 收敛为枚举,或在末尾加显式"未识别来源"文案,并在场景里补一条未知取值断言。

P3(说明句的前提与显示条件不一致)。:121 只要 executor_transport_reason 非空就渲染说明句,而 i18n.tsx:410 的文案同时断言"本会话的模型解析自 operator 凭据";当 model_source 是 env_override 或厂商默认时,这半句话陈述了错误事实。最小修法:让说明句只陈述 transport 事实("托管执行器尚无 Chat 通道,本会话由 {executor} 承载"),模型来源交给 chip 自己的来源标签。

P3(首屏门的证据没有随 PR 留痕)。chip 落在 manager 首页首屏的头部行内,仓库的 docs/development/design.md:241/274 要求首屏/高保真类改动提供截图;PR 描述声明"提交前给 owner 看过 1512x982 首屏预览并获批准",但 PR 里没有附上那张截图或其链接,场景生成的 execution-chip-manager-header.png 也不在仓库里。最小修法:把这张截图附到 PR 描述,作为可核验的首屏记录。

其余残余风险:这是一个 stacked PR,base 是 codex/steward-channel-binding-20260915(90f4f2ff5)而不是 main——我按真实 base 核对了 diff(14 文件 +286/-148,与 packet 的 scale 一致),所以没有把 stack 前序 PR 的历史混进这次结论;但它必须等基分支落地才能进主线,合并时应再按最新 main 复核一次 merge base。未验证面:我没有本地运行浏览器场景(需要 Playwright 运行时),几何与截图只有 CI 语义;dark/移动端下 chip 的表现也没有额外断言。

我的整体评价

APPROVE。它把"这次 manager 会话跑在哪、模型来自哪里、托管通道还缺什么"从隐式变成可读,并且严格限定在显示层:端点与模型的解析仍归后端,chip 只是渲染已投影的字段,无投影时退回完全相同的旧头部(场景有反例断言),打包产物与资源保留清单也自洽。三条发现都是可局部修的 P3——未知 model_source 的兜底标签、说明句里"模型来自凭据"这半句话的适用条件、以及首屏截图没有随 PR 留痕——它们不影响本次是否该合并,也不涉及权限、状态或后端行为。合并时记得这是 stack 中一环,按最新 main 复核 base。

English verdict: APPROVE at d69970d. The change renders the already-projected manager channel binding in the manager header (endpoint, resolved model, model-source label, plus an inline sentence when the resolved managed host has no Chat transport), and it stays strictly in the display layer: the binding schema is lifted from an inline capability shape into a named, reused schema, the data flows from capabilities.manager.channel_binding through one prop chain, and a control plane with no projection keeps the previous header (the new browser scenario asserts zero chips in that case). I verified the packaged surface is self-consistent: every asset referenced by loopx/web/chat/index.html exists, all 26 paths in asset-retention.json exist, and the new chip markup appears in both packaged JS and CSS; CI at this head is fully green (test-shard 1-4, dashboard-acceptance, kernel-static-checks, node compatibility, merge-gate, Sign-off). Non-blocking findings: an unrecognized model_source falls through to the "vendor default" label while the schema still types it as z.string() (P3), the transport sentence asserts the model resolves from the operator credential even when model_source is an env override or vendor default (P3), and the first-viewport preview the PR says the owner approved is not attached, though docs/development/design.md asks for screenshots on first-screen changes (P3). Note this is a stacked PR on codex/steward-channel-binding-20260915; I diffed against that real base (14 files, +286/-148) and it needs the base branch to land first.

@huangruiteng
huangruiteng force-pushed the codex/steward-execution-chip-20260915 branch from d69970d to b60e433 Compare September 15, 2026 14:09
@huangruiteng
huangruiteng changed the base branch from codex/steward-channel-binding-20260915 to main September 15, 2026 14:09
The manager channel selects its executor endpoint and model, and the Chat
capabilities payload already projects that selection as `manager.channel_binding`
with the executor kind, the resolved model, and `available`/`unavailable_reason`.
The packaged frontends rendered none of it, so an operator could not read which
executor answers for the steward, which credential pays for it, which model
follows it, or that a selected managed host cannot serve the channel at all.

Render one compact chip in the manager channel header (`executor · executor kind
· model`) and the transport gate beside it when the projection proves the
selected endpoint cannot serve this channel. The chip is display-only: it lifts
the inline capability shape into a named, reused schema, keeps the kind in the
`individual`/`managed` vocabulary of the governed Turn surface, and names a kind
this build does not recognize as an unclaimed registered endpoint rather than
guessing. An unavailable selection is carried by the chip itself and the narrow
header keeps that row, so a wrapped-out reason cannot leave the state looking
healthy.

The chip renders only when the control plane projects a binding, so an older
service keeps the previous header. A new browser scenario (`execution-chip`)
proves the chip text for the shipped and managed-host bindings, the inline
transport gate, the unavailable marker, the compact header-row geometry, the
390px layout where the reason must stay readable, and that the chip stays absent
without a binding.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The packaged chat bundle is what operators actually load, so rebuild it from the
manager header change. `index.html` now references `assets/index-BWxHtIJu.js` and
`assets/index-DMq_dBB9.css`; the retention manifest keeps that generation plus the
one the previous `index.html` referenced, and every referenced and retained asset
exists.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng
huangruiteng force-pushed the codex/steward-execution-chip-20260915 branch from b60e433 to 5b71f0b Compare September 15, 2026 14:15

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

管家通道是选择执行器而不是发现执行器:codex 是出厂端点,LOOPX_MANAGER_ENDPOINT 可以显式改写,而凭据(DEEPSEEK_API_KEY/DEEPSEEK_BASE_URL)从来不是选择信号。Chat capabilities 已经把这次解析投影成 manager.channel_binding(端点与端点来源、执行器种类、模型与模型来源、凭据变量名、available/unavailable_reason),但打包前端一个字段都没渲染:chatCapabilitiesSchema.manager 在 base 886fb8e9f 只声明了 scope/model/reasoning_effort/runtime,zod 直接把 channel_binding 丢掉。

代价不是"少一行字":出厂 codex 与显式选中的 dsh(当前没有交互式 Chat 传输)在头部看起来完全一样,operator 会把管家失败归因到模型或凭据,而不是"所选执行器无法承载本通道"。协议文档 docs/reference/protocols/manager-evidence-and-continuity-v0.md 早就写下"前端可以展示管家通道解析出的执行器与模型,且不需要重新推导规则",只是没有任何界面在做。更小的替代方案(只写文档、只显示凭据变量名)都不解决"operator 正在看通道时读到错误结论"这件事。

改动思路

入口是 Chat 服务的 GET /api/chat/capabilities(loopx/chat_server.py 的 do_GET,把 manager_channel_binding() 作为 channel_binding 交给 manager_runtime_capability_projection),权威状态完全在控制面:选择、种类、模型、可用性都由后端解析,前端只做展示。

链路是单向的:dashboard-page.tsx 的能力拉取把投影存进 managerChannelBinding 状态(:1403,?空值即"没有投影"),经 personal-workspace-page.tsx 透传一个 prop 给 ChannelHeader,由头部渲染一枚 chip;没有写路径、没有持久状态、没有副作用。数据形状只在一处声明(chat.ts 的具名 managerChannelBindingSchema),避免内联对象和组件各写一份。

复用面刻意保持最小:沿用已有的头部副标题槽位(manager.runtime 那一行旁边)、已有的 --pw-* token、已有的 i18n 命名空间、已有的浏览器 fixture 能力路由。没有新增面板、store 或 API,也没有把选择事实并入 manager.runtime——选择与机器策略是两件事,合在一起会把"谁在跑"和"机器允许什么"混淆。

具体改动

改动面 18 文件 +429/-137:生产 UI 与 schema 5 个文件、浏览器场景与 fixture 3 个、协议文档与首屏证据 4 个、打包产物 5 个(删掉的 137 行主要是被替换的旧打包资产)。生产侧热点只有 channel-header.tsx(+28/-1)与 data/chat.ts(+16/-0),最大的文件是生成物而非源码。

关键代码讲解

  1. managerChannelBindingSchema(chat.ts:99):把原先内联的能力对象提升为具名 schema 并导出 ManagerChannelBinding,同时给 manager 增加可选 channel_binding(chat.ts:127)。关键是 available 只接受 boolean | null、unavailable_reason 只接受 string | null,让"没有主张"和"已证明不可用"保持可区分;而种类与来源仍是开放字符串——新增枚举值只会渲染成"未认领",不会让整个 capabilities 解析失败、白屏。
  2. managerChannelBinding 状态(dashboard-page.tsx:1403):只在能力拉取成功时写入(capabilities.manager?.channel_binding ?? null),readOnly 分支清空。不在前端重新推导规则,也不把 null(无投影)和 available === false(已解析但不可用)合并。
  3. chip 渲染(channel-header.tsx:119):只有当"未选中 Goal 且存在 binding"时才渲染,available === false 时给 chip 加 is-unavailable,并在同一行给出原因。因此老服务、Goal 视图都不会改变头部。
  4. managerExecutionKindLabel(channel-header.tsx:92):individual→个人 CLI 登录、managed→operator 凭据,其余一律"注册端点"。这是刻意不留"厂商默认"这类兜底,避免把不认识的执行器种类说成 operator 能据以行动的配置。
  5. executionChipScenario(examples/personal-workspace-browser/execution-chip.mjs:79):覆盖出厂绑定、托管宿主绑定、未知种类、无投影、以及 390px 三种状态,断言 chip 文本、不可用标记、窄屏原因可读性、无投影时 chip 数为 0。

对主干的风险

没有阻塞性发现。 这一轮我实际修掉的是上一版自己的两个 P3 与一个证据缺口:

  • 上一版窄屏(≤720px)头部会隐藏整行副标题,导致"所选 dsh 无法承载 Chat"的原因被压成 0px 宽(修复前实测 note width = 0),状态看起来像健康配置。现在 personal-manager-execution 允许换行,且窄屏显式保留这一行(personal-workspace.css 媒体查询里有注释说明为什么这一行是例外),修复后 390px 下原因为 129x17,页面无横向溢出。
  • 上一版把不认识的 model_source 静默说成"厂商默认";现在 chip 不再渲染 model_source,种类标签有显式的"注册端点"兜底,未知种类分支被场景断言。
  • 首屏证据此前只在对话里存在;现在随协议文档落库(桌面 1512px、不可用桌面 1512px、不可用移动 390px)。

残余风险:场景驱动的是真实打包 bundle,但 capabilities 路由是 stub,所以"后端 manager_channel_binding() 与前端 schema 字段一致"是靠逐字段阅读确认,不是跑真实服务;深色主题与非 Chromium 引擎没有单独重渲。两者都不改变本次结论。

最坏回归场景是"选了托管宿主但头部照旧看着健康",触发条件是 available:false + 窄屏;现在由 chip 自身的 is-unavailable 标记、换行规则和场景断言三重兜住,回滚只需还原分支,无数据迁移。

我的整体评价

APPROVE。它把管家"这次会话跑在哪个执行器、由哪套凭据付费、用哪个模型、能不能承载 Chat"从隐式变成可回读的界面事实,并且严格限定在展示层:选择与解析仍归控制面,前端只渲染投影字段,无投影时回退到与 base 相同的头部(场景有反例断言),打包产物与资源保留清单自洽。改动规模(生产侧约 90 行 + 一个耐久场景)与问题规模匹配:这不是"再加一层框架",而是把已有投影接到 operator 真正阅读的位置。

English verdict: APPROVE at 5b71f0b. The manager header now renders the already-projected manager.channel_binding as one compact chip (endpoint, executor kind, resolved model) and marks the chip unavailable with an inline reason when the selected executor cannot serve the channel; the change stays display-only, the kind vocabulary follows the governed Turn surface with an explicit unclaimed fallback for unknown kinds, and a control plane that projects no binding keeps the previous header (asserted as zero chips). No blocking finding: the two P3 issues from the previous revision (a 0px-wide reason at 390px, and an unrecognized model source being labelled as the vendor default) are fixed and covered, and first-screen evidence is now committed with the contract update. Validation: npm run build:chat, npm run smoke:goal-order, workspace theme test, loopx canary premerge --from-git-diff (19 selected, 0 failures), and the browser smoke in packaged and development modes (navigation-sorting,chat-recovery,typed-actions,execution-chip). Residual risk: the scenario stubs the capabilities route, so backend/frontend field agreement is established by reading, and dark theme plus non-Chromium engines were not re-rendered.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

动机

管家通道是选择执行器而不是发现执行器:codex 是出厂端点,LOOPX_MANAGER_ENDPOINT 可以显式改写,而凭据(DEEPSEEK_API_KEY/DEEPSEEK_BASE_URL)从来不是选择信号。Chat capabilities 已经把这次解析投影成 manager.channel_binding(端点与端点来源、执行器种类、模型与模型来源、凭据变量名、available/unavailable_reason),但打包前端一个字段都没渲染:chatCapabilitiesSchema.manager 在 base 886fb8e9f 只声明了 scope/model/reasoning_effort/runtime,zod 直接把 channel_binding 丢掉。

代价不是"少一行字":出厂 codex 与显式选中的 dsh(当前没有交互式 Chat 传输)在头部看起来完全一样,operator 会把管家失败归因到模型或凭据,而不是"所选执行器无法承载本通道"。协议文档 docs/reference/protocols/manager-evidence-and-continuity-v0.md 早就写下"前端可以展示管家通道解析出的执行器与模型,且不需要重新推导规则",只是没有任何界面在做。更小的替代方案(只写文档、只显示凭据变量名)都不解决"operator 正在看通道时读到错误结论"这件事。

改动思路

入口是 Chat 服务的 GET /api/chat/capabilities(loopx/chat_server.py 的 do_GET,把 manager_channel_binding() 作为 channel_binding 交给 manager_runtime_capability_projection),权威状态完全在控制面:选择、种类、模型、可用性都由后端解析,前端只做展示。

链路是单向的:dashboard-page.tsx 的能力拉取把投影存进 managerChannelBinding 状态(:1403,?空值即"没有投影"),经 personal-workspace-page.tsx 透传一个 prop 给 ChannelHeader,由头部渲染一枚 chip;没有写路径、没有持久状态、没有副作用。数据形状只在一处声明(chat.ts 的具名 managerChannelBindingSchema),避免内联对象和组件各写一份。

复用面刻意保持最小:沿用已有的头部副标题槽位(manager.runtime 那一行旁边)、已有的 --pw-* token、已有的 i18n 命名空间、已有的浏览器 fixture 能力路由。没有新增面板、store 或 API,也没有把选择事实并入 manager.runtime——选择与机器策略是两件事,合在一起会把"谁在跑"和"机器允许什么"混淆。

具体改动

改动面 18 文件 +429/-137:生产 UI 与 schema 5 个文件、浏览器场景与 fixture 3 个、协议文档与首屏证据 4 个、打包产物 5 个(删掉的 137 行主要是被替换的旧打包资产)。生产侧热点只有 channel-header.tsx(+28/-1)与 data/chat.ts(+16/-0),最大的文件是生成物而非源码。

关键代码讲解

  1. managerChannelBindingSchema(chat.ts:99):把原先内联的能力对象提升为具名 schema 并导出 ManagerChannelBinding,同时给 manager 增加可选 channel_binding(chat.ts:127)。关键是 available 只接受 boolean | null、unavailable_reason 只接受 string | null,让"没有主张"和"已证明不可用"保持可区分;而种类与来源仍是开放字符串——新增枚举值只会渲染成"未认领",不会让整个 capabilities 解析失败、白屏。
  2. managerChannelBinding 状态(dashboard-page.tsx:1403):只在能力拉取成功时写入(capabilities.manager?.channel_binding ?? null),readOnly 分支清空。不在前端重新推导规则,也不把 null(无投影)和 available === false(已解析但不可用)合并。
  3. chip 渲染(channel-header.tsx:119):只有当"未选中 Goal 且存在 binding"时才渲染,available === false 时给 chip 加 is-unavailable,并在同一行给出原因。因此老服务、Goal 视图都不会改变头部。
  4. managerExecutionKindLabel(channel-header.tsx:92):individual→个人 CLI 登录、managed→operator 凭据,其余一律"注册端点"。这是刻意不留"厂商默认"这类兜底,避免把不认识的执行器种类说成 operator 能据以行动的配置。
  5. executionChipScenario(examples/personal-workspace-browser/execution-chip.mjs:79):覆盖出厂绑定、托管宿主绑定、未知种类、无投影、以及 390px 三种状态,断言 chip 文本、不可用标记、窄屏原因可读性、无投影时 chip 数为 0。

对主干的风险

没有阻塞性发现。 这一轮我实际修掉的是上一版自己的两个 P3 与一个证据缺口:

  • 上一版窄屏(≤720px)头部会隐藏整行副标题,导致"所选 dsh 无法承载 Chat"的原因被压成 0px 宽(修复前实测 note width = 0),状态看起来像健康配置。现在 personal-manager-execution 允许换行,且窄屏显式保留这一行(personal-workspace.css 媒体查询里有注释说明为什么这一行是例外),修复后 390px 下原因为 129x17,页面无横向溢出。
  • 上一版把不认识的 model_source 静默说成"厂商默认";现在 chip 不再渲染 model_source,种类标签有显式的"注册端点"兜底,未知种类分支被场景断言。
  • 首屏证据此前只在对话里存在;现在随协议文档落库(桌面 1512px、不可用桌面 1512px、不可用移动 390px)。

残余风险:场景驱动的是真实打包 bundle,但 capabilities 路由是 stub,所以"后端 manager_channel_binding() 与前端 schema 字段一致"是靠逐字段阅读确认,不是跑真实服务;深色主题与非 Chromium 引擎没有单独重渲。两者都不改变本次结论。

最坏回归场景是"选了托管宿主但头部照旧看着健康",触发条件是 available:false + 窄屏;现在由 chip 自身的 is-unavailable 标记、换行规则和场景断言三重兜住,回滚只需还原分支,无数据迁移。

我的整体评价

APPROVE。它把管家"这次会话跑在哪个执行器、由哪套凭据付费、用哪个模型、能不能承载 Chat"从隐式变成可回读的界面事实,并且严格限定在展示层:选择与解析仍归控制面,前端只渲染投影字段,无投影时回退到与 base 相同的头部(场景有反例断言),打包产物与资源保留清单自洽。改动规模(生产侧约 90 行 + 一个耐久场景)与问题规模匹配:这不是"再加一层框架",而是把已有投影接到 operator 真正阅读的位置。

English verdict: APPROVE at 5b71f0b. The manager header now renders the already-projected manager.channel_binding as one compact chip (endpoint, executor kind, resolved model) and marks the chip unavailable with an inline reason when the selected executor cannot serve the channel; the change stays display-only, the kind vocabulary follows the governed Turn surface with an explicit unclaimed fallback for unknown kinds, and a control plane that projects no binding keeps the previous header (asserted as zero chips). No blocking finding: the two P3 issues from the previous revision (a 0px-wide reason at 390px, and an unrecognized model source being labelled as the vendor default) are fixed and covered, and first-screen evidence is now committed with the contract update. Validation: npm run build:chat, npm run smoke:goal-order, workspace theme test, loopx canary premerge --from-git-diff (19 selected, 0 failures), and the browser smoke in packaged and development modes (navigation-sorting,chat-recovery,typed-actions,execution-chip). Residual risk: the scenario stubs the capabilities route, so backend/frontend field agreement is established by reading, and dark theme plus non-Chromium engines were not re-rendered.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

合并决定(自合并,owner 已授权)

变更面:Dashboard 生产源码(data/chat.ts、channel-header.tsx、i18n.tsx、personal-workspace-page.tsx、personal-workspace.css、views/dashboard-page.tsx)、浏览器场景与 fixture、协议文档与首屏截图、打包产物(loopx/web/chat)。head 5b71f0be9d8d281a7fe594848e619fd9b1162ea3,base 已重切为 main。

执行的检查

  • npm run build:chat(tsc --noEmit + vite):通过
  • npm run smoke:goal-order、node src/features/personal-workspace/workspace-theme.test.mjs:通过
  • examples/personal-workspace-browser-smoke.mjs 打包模式与开发模式:四个场景全通过(navigation-sorting,chat-recovery,typed-actions,execution-chip)
  • loopx canary premerge --from-git-diff:19 selected / 0 failures(catalog 10、risk profile 8、public boundary 1)
  • 打包一致性:index.html 引用的资源与 asset-retention.json 全部条目均存在
  • GitHub:31 项 check 全绿(含 dashboard-acceptance、kernel-static-checks、4 个 test-shard、merge-gate、Sign-off)

失败与跳过:无失败;presentation、upload-release、publish-pypi、deploy 按仓库设计跳过。

人工保留项:无阻塞项。上一版自己的两个 P3(390px 下原因被压成 0px、未知 model_source 被说成厂商默认)已在本 head 修复并被场景断言覆盖;首屏证据已随协议文档落库。

为什么覆盖率足够:本次改动完全落在展示层(一枚只读投影的 chip + 可选不可用说明),没有触碰选择、凭据解析、模型解析、Chat 准入或配额;相应风险面就是"头部渲染是否正确",由 execution-chip 场景的五个分支(出厂绑定 / 托管宿主 / 未知种类 / 无投影 / 390px)在真实打包 bundle 上覆盖,另有反例断言保证无投影时头部与 base 一致。loopx pr-review --check-merge-readiness 4419@5b71f0be9 返回 ready=true(review conclusion valid,checks 31 success,0 pending)。

由此按 owner 授权执行 admin 自合并(squash)。

@huangruiteng
huangruiteng merged commit 9719dc0 into main Sep 15, 2026
31 checks passed
@huangruiteng
huangruiteng deleted the codex/steward-execution-chip-20260915 branch September 15, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant