feat(chat): show the steward execution binding in the manager header - #4419
Conversation
8b35803 to
25f712b
Compare
6e1f80f to
309beb4
Compare
25f712b to
ea96161
Compare
309beb4 to
2da0bfb
Compare
ea96161 to
90f4f2f
Compare
2da0bfb to
d69970d
Compare
|
Status update: this PR is held, not abandoned, and it is not mergeable as-is. Two things changed around it:
What the refreshed chip does, on the corrected binding:
Validation on the refreshed branch (dashboard 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. |
|
状态:暂缓合并(两项前置条件未满足) 这条 PR 目前保持 open、不推新提交,原因有两条,都需要先解决。
因此本轮不给出合并结论。重切并取得预览确认后,我会按同一执行契约提交一次完整评审(含 |
huangruiteng
left a comment
There was a problem hiding this comment.
动机
steward 通道已经会从 operator 凭据解析出 executor endpoint 与 model,也把这份绑定放进了 Chat capabilities payload,但打包前端从头到尾没显示它:operator 既看不到这次 manager 会话跑在哪个 endpoint、用哪个 model、来源是什么,也看不到"已解析的托管 host 还没有 Chat 通道"这一限制。把这两个事实搬到 manager 头部是对的——它把原本只能靠猜的信息变成可回读的界面事实。
改动思路
chat.ts把原先内联在chatCapabilitiesSchema.manager.channel_binding的对象形状提升为具名managerChannelBindingSchema并导出类型,供组件复用。dashboard-page.tsx读取capabilities.manager?.channel_binding,经personal-workspace-page.tsx透传给channel-header.tsx。- 头部在"未选中 Goal 且有 binding"时渲染一枚紧凑 chip(endpoint、已解析 model、来源标签),并在
executor_transport_reason非空时追加一句"托管执行器尚无 Chat 通道"的说明。 - 新增浏览器场景
examples/personal-workspace-browser/execution-chip.mjs,并更新打包产物与资源保留清单。
具体改动
apps/presentation/dashboard/src/data/chat.ts(:99具名 schema)、channel-header.tsx(:89来源标签映射、:113-121chip 与说明句)、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(新一代资源 + 保留上一代以便已打开页面继续加载)。
关键内容讲解
- 数据只来自后端投影:chip 渲染的是
executor_endpoint/model/model_source/executor_transport_reason四个字段,前端不重算解析;credential_env_var只用于声明变量名,界面不显示凭据值。 - 无投影时行为与 base 完全一致:
capabilities.manager?.channel_binding ?? null,为 null 就不渲染;场景第二段专门断言没有 binding 时.personal-execution-chip数量为 0,这正是 feature-off parity 的证据。 - 打包面自洽:我解析了
index.html的引用并遍历asset-retention.json的条目,逐个检查文件存在——引用的assets/index-Dv0s3LKG.js、assets/index-ClrAI17B.css与清单里的 26 条资源都在;打包 JS/CSS 中也确实各含一处 chip 标记。 - 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.
d69970d to
b60e433
Compare
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>
b60e433 to
5b71f0b
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
动机
管家通道是选择执行器而不是发现执行器: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),最大的文件是生成物而非源码。
关键代码讲解
managerChannelBindingSchema(chat.ts:99):把原先内联的能力对象提升为具名 schema 并导出ManagerChannelBinding,同时给manager增加可选channel_binding(chat.ts:127)。关键是available只接受boolean | null、unavailable_reason只接受string | null,让"没有主张"和"已证明不可用"保持可区分;而种类与来源仍是开放字符串——新增枚举值只会渲染成"未认领",不会让整个 capabilities 解析失败、白屏。managerChannelBinding状态(dashboard-page.tsx:1403):只在能力拉取成功时写入(capabilities.manager?.channel_binding ?? null),readOnly分支清空。不在前端重新推导规则,也不把null(无投影)和available === false(已解析但不可用)合并。- chip 渲染(
channel-header.tsx:119):只有当"未选中 Goal 且存在 binding"时才渲染,available === false时给 chip 加is-unavailable,并在同一行给出原因。因此老服务、Goal 视图都不会改变头部。 managerExecutionKindLabel(channel-header.tsx:92):individual→个人 CLI 登录、managed→operator 凭据,其余一律"注册端点"。这是刻意不留"厂商默认"这类兜底,避免把不认识的执行器种类说成 operator 能据以行动的配置。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
left a comment
There was a problem hiding this comment.
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),最大的文件是生成物而非源码。
关键代码讲解
managerChannelBindingSchema(chat.ts:99):把原先内联的能力对象提升为具名 schema 并导出ManagerChannelBinding,同时给manager增加可选channel_binding(chat.ts:127)。关键是available只接受boolean | null、unavailable_reason只接受string | null,让"没有主张"和"已证明不可用"保持可区分;而种类与来源仍是开放字符串——新增枚举值只会渲染成"未认领",不会让整个 capabilities 解析失败、白屏。managerChannelBinding状态(dashboard-page.tsx:1403):只在能力拉取成功时写入(capabilities.manager?.channel_binding ?? null),readOnly分支清空。不在前端重新推导规则,也不把null(无投影)和available === false(已解析但不可用)合并。- chip 渲染(
channel-header.tsx:119):只有当"未选中 Goal 且存在 binding"时才渲染,available === false时给 chip 加is-unavailable,并在同一行给出原因。因此老服务、Goal 视图都不会改变头部。 managerExecutionKindLabel(channel-header.tsx:92):individual→个人 CLI 登录、managed→operator 凭据,其余一律"注册端点"。这是刻意不留"厂商默认"这类兜底,避免把不认识的执行器种类说成 operator 能据以行动的配置。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.
合并决定(自合并,owner 已授权)变更面:Dashboard 生产源码( 执行的检查
失败与跳过:无失败; 人工保留项:无阻塞项。上一版自己的两个 P3(390px 下原因被压成 0px、未知 为什么覆盖率足够:本次改动完全落在展示层(一枚只读投影的 chip + 可选不可用说明),没有触碰选择、凭据解析、模型解析、Chat 准入或配额;相应风险面就是"头部渲染是否正确",由 由此按 owner 授权执行 admin 自合并(squash)。 |
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 noneof 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.tslifts the payload shape into a named,reused
managerChannelBindingSchema/ManagerChannelBindingand declares theoptional
manager.channel_bindingfield, so the projection is no longer silentlydropped by the capabilities parse.
channel-header.tsxrenders the chip from the projected fields only. The kind keepsthe
individual/managedvocabulary of the governed Turn surface, and a kind thisbuild 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.tsxthread the projection through oneprop; the value is never derived locally.
is-unavailable) and thenarrow 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
execution-chip(registered inpersonal-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.scenarios=navigation-sorting,chat-recovery,typed-actions,execution-chip.loopx/web/chat/index.htmlreferencesassets/index-BWxHtIJu.jsandassets/index-DMq_dBB9.css; every retained asset exists; the retention manifest keepsthe new generation plus the one the previous
index.htmlreferenced.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 managerheader binding plus its evidence and the refreshed packaged bundle. The steward channel
default stays
codex— a configured provider credential never re-points it.