创建:2026-05-19(v1) 重写:2026-05-19(v2 — Codex review pass,修 4 P1 + 1 P2) 增补:2026-05-20(v6 — 用户真实 UI smoke 暴露源错位,加 Phase 7) 增补:2026-05-20(v7 — 用户决议 Phase 7 范围扩三 Runtime + 强制抽象 contract,防止新 Agent 接入时重写 producer 逻辑) 父计划:
phase-6-context-visualization.md触发:用户 Codex review (2026-05-19) — Phase 6 Tier 2 (a4fa2d4) 实施了context_breakdown持久化链路,但真实 smoke 证明普通消息和 humanizer-zh Skill 消息的context_breakdown完全相同:Skills 行统计的是固定 compiler capability prompt(一直存在的 hardcoded harness 输出),不是实际 Skill 注入 / 调用。这是 "界面有数字,但数字不是用户以为的那个东西" 的 hallucination 风险,违反feedback_no_hallucination元规则。 v6 二次触发:用户 2026-05-20 真实 UI smoke 暴露 Phase 2-4 设计只覆盖 "badge picker 预选" 一条路径,用户自然语言让 Claude 自主 invoke Skill/MCP 这条主流路径 producer 永远看不到 → 仍违反"数字反映用户实际工作量"的语义契约。 先读:docs/guardrails/Runtime.md、docs/handover/harness-capability-contract.md、docs/guardrails/MCP.md、docs/exec-plans/completed/phase-5e-runtime-harness-architecture.md
升级前(Phase 6 Tier 2 落地后的现状 a4fa2d4):
- 普通消息 vs humanizer-zh Skill 消息的 popover 完全一样
- "Skills 1.5K" 永远是同一数字 — 因为它读的是
budget.perCategory.capabilityFragments(compiler 把 capability descriptor 包装成 system prompt 段),跟用户实际调没调 Skill 完全无关 - 同理:tools = 0(placeholder),mcp =
mcpServerNames.length * 200(粗略估值跟实际 schema tokens 差很远) - 结果:用户切 Skill 会话期待看到差异 → 看到没差 → 不信任 popover 数字
- 这违反
feedback_no_hallucination:UI 暗示"这是真实分类数字",实际是 "compiler 内部 hardcoded + 粗估"
v2 review pass 进一步确认:所谓"保留 system_prompt / rules / memory 真实"也站不住脚:
compiled.workspaceFragments当前是空数组 → workspaceRuleTokens 永远是 0(不是真实 workspace rules)- ClaudeCode adapter 当前没传 assistantMemory → memoryTokens 是 compiler 内部估算,不反映实际 memory 注入
compiled.basePrompt当前为空,artifactContracts是 compiler artifact 片段——这两个加起来不包含 Claude Code preset / SDK 实际 system prompt。UI 显示 "系统提示 313" 让用户以为是完整 system prompt,实际只是片段- → Phase 0 不能宣称这三个是真实,跟 Skills/MCP 是同质错误
| 用户场景 | 现状(Tier 2 + Phase 0 v2) | Phase 5 接通后(badge path) | Phase 7 接通后(auto-invoke path) |
|---|---|---|---|
| 发"你好"普通消息 | popover 显示对话历史 + 缓存两行;其余全部隐藏 | 同左 + rules 真实(CLAUDE.md filesize) | 同左(无 tool_use → skills/mcp/tools 都不显示) |
popover 选 /humanizer-zh badge 发送 |
popover 仍只显示对话历史 + 缓存(不暴露假 Skills 1.5K) | Skills 行出现 反映本轮 SKILL.md filesize | 同左(badge 路径仍 work;Phase 7 不破坏) |
| 自然语言提 "用 humanizer-zh 优化"(不点 badge) | popover 不出 Skills 行 | 仍不出(Phase 2-4 限制;用户最反馈痛点) | Skills 行出现(producer 扫 assistant message tool_use blocks 提取 Skill { skill: 'humanizer-zh' }) |
Widget 生成(Claude 自主调 mcp__codepilot-widget__...) |
MCP 行 unsupported 隐藏 | 同左(Phase 2-4 spec 仍 unsupported) | MCP 行出现 含 codepilot-widget / codepilot-memory 等 server 名 + invocation 次数 |
| Claude 调 Bash 5 次 / Read 10 次 | Tools 行 unsupported 隐藏 | 同左 | Tools 行出现 含 Bash×5 / Read×10 breakdown |
| 切到 Native runtime | 同上 | rules 真实;其他 unsupported | Phase 7.5 评估(Native agent-loop 没 SDK tool_use 抽象;可能保留 unsupported) |
| 切到 Codex Account session | 同上 | rules 真实;其他 unsupported;providerBackend='codex_account' | Phase 7.5 评估(Codex 用 usage_updated event 已能拿真实 turn-level 数;不走 message tool_use scan 路径) |
| 切到 Codex + CodePilot bridge | 同上 | memory bridge 接通(Phase 5e 已支持);popover 按 bridge 实际反映 | 同左 |
| 某类别 Runtime 不支持 | hide(不暴露 0) | 同左 | 同左(unsupported first-class 保留) |
- 不做账单级精度:跟 server 实际 tokenization 仍有 char/4 估算偏差,跟 cost API 同源即可
- 不强制所有 Runtime 必须支持所有类别:显式声明 unsupported 是合规,UI 据此 hide
- 不重构 send path 整体结构:只引入新 contract 抽象 + 三 Runtime adapter 各自实现 produce()
- 不动 conversation / cache_or_previous / pending 三类(这些来自 baseline + composer,跟 Runtime 无关)
- 不立即在 Phase 0 修真实接通:Phase 0 仅止损(隐藏假数据),真实接通是 Phase 2-4 范围
- 不允许 Phase 0 保留任何"半真半假"字段——只保留 conversation / cache(baseline 数据来源真实),其他全部 hide 直到 Phase 2+ 真接通
- Phase 0 止损:发任意消息后 popover 只显示对话历史 + 缓存 / 上轮两行;不再看到 Skills / Tools / MCP / 系统提示 / 规则 / Memory 假数据
- Phase 1 Contract:
src/lib/harness/context-accounting.ts导出RuntimeContextAccountingSnapshot+ContextAccountingEntry+producedBy: RuntimeId类型;至少 1 个 unit test pin shape;guardrail Runtime.md 加 reference - Phase 2 ClaudeCode:发 humanizer-zh Skill 后 Skills 行出现且大于普通消息基线;该字段 source 明确写
sdk-turn/loaded-skill(不是 available list) - Phase 3 Native:Native session 切过去后 popover 类别数字非 placeholder
- Phase 4 Codex:
- Codex Account session:memory 行 unsupported 隐藏;其他按 codex_account 范围
- Codex + CodePilot bridge:memory 行真实接通(Phase 5e bridge 已支持)
- Codex native app-server 自有能力:按 app-server 实际能力声明 unsupported / 接通
- run_completed 时 result.usage.context_breakdown 必须落库(Codex 不能只靠 live
context_usageevent 实时预览,最终账本必须由 result event 持久化)
- Phase 5 收口:三 Runtime Smoke Ledger 三条;Phase 6 文档 Phase 4 验收段引用本计划
- Contract first:先定义"snapshot 是什么 / 字段含义 / unsupported 怎么表达 / Skills available vs loaded 怎么区分"——三个 Runtime 一起遵守
- 真实数据来源可追踪:每个字段必须能 trace 到具体源(
source: 'sdk-init/available-skills' | 'sdk-turn/loaded-skill' | 'mcp-server-schemas' | 'workspace-rules-fs/CLAUDE.md' | ...);source 区分 "可用 vs 实际本轮注入" - unsupported 是 first-class:Runtime 可声明 "I don't know how to count this kind",UI 据此隐藏行,不伪装
- result event 是唯一持久化入口:live
context_usageevent 只做实时预览;Codex 现在 live event + run_completed 不带 usage 的设计要改成 adapter 缓存最后一次 + run_completed → result 写入 producedBy必须用 RuntimeId:'claude_code' | 'codepilot_runtime' | 'codex_runtime'(项目当前 RuntimeId 约定)。禁止再引入'native'之类别名(之前已踩过 RuntimeId vs 别名混乱坑)。子分类用providerBackend?: 'codex_account' | 'codepilot_proxy' | ...字段表达,不混入 producedBy- Skills 必须区分 available vs loaded/invoked:
sdk-init/available-skills:模型每轮都看到的 skill descriptor 列表(包括没调用的)sdk-turn/loaded-skill:本轮实际注入的 skill 正文/规则- UI 显示的 Skills 只反映 loaded/invoked(用户期望"我调用 Skill 后数字变化")。available list 即使要展示也必须是另一个 kind 或 sub-detail,不能跟 invoked 混
- 同类原则适用于其他 kind:tools (available descriptor vs invoked call)、mcp (available server vs invoked tool call)、memory (snapshot 内容 vs 本轮 search 命中) — UI 必须只反映本轮实际进入上下文的 tokens
用户能看到什么:popover 立刻只显示 对话历史 + 缓存 / 上轮 两行。不再显示 Skills / Tools / MCP / 系统提示 / 规则 / Memory(这 6 个全部隐藏,直到 Phase 2-4 真接通)。
不做什么:不动 Contract spec、不动其他 Runtime、不删 walkContextUsage / hook wire(管道保留),不修 UI 渲染逻辑(hideZero=true 已经做了 hide)
为什么这 6 个全要隐藏(不是只隐藏 3 个):
- Skills:
budget.perCategory.capabilityFragments是 compiler hardcoded 输出,跟用户调没调 Skill 无关 → 不真实 - Tools:永远 0 placeholder → 不真实
- MCP:
mcpServerNames.length * 200粗估 → 不真实 - 系统提示:
compiled.basePrompt当前为空 +artifactContracts仅是片段,不包含 Claude Code preset / SDK 实际 system prompt → 数字会让用户以为是完整 system prompt,实际不是 - 规则:
compiled.workspaceFragments当前是空数组,永远是 0 → 显示 0 等同 hideZero hide,但保留字段会让用户期待"工作区规则"实际并未在统计中 - Memory:ClaudeCode adapter 没传 assistantMemory,所以读
compiled.memoryFragments是 compiler 内部估算,不反映实际 memory 注入
实施清单:
src/types/index.ts:ContextBreakdownSnapshot全字段改可选(兼容 Phase 1 contract)src/lib/claude-client.ts删除整个 snapshot computation 块(line ~1138-1162)+ result event 不再附加context_breakdown(保留 baseline + cache + pending wire 不动)- 不修 UI:现有
hideZero=true默认行为已经 hide 0/undefined 行 - 更新 tech-debt #21:本来描述 placeholder 待 Phase 1c,现在改为"由 Phase 2-4 Contract 实施接管"
关键测试:手动启动 dev server,发任意消息,popover 只显示对话历史 + 缓存两行。无新 unit test 必要(Phase 0 是删字段,hideZero 已经测过)。
新建 src/lib/harness/context-accounting.ts:
import type { RuntimeId } from '@/lib/runtime/effective'; // or wherever it lives
export type ContextAccountingKind =
| 'system_prompt' | 'tools' | 'rules' | 'skills' | 'mcp'
| 'memory' | 'files_attachments';
/** Per-kind accounting entry — only real, current-turn data. */
export interface ContextAccountingEntry {
tokens: number;
/**
* Trace source — MUST distinguish "available" vs "loaded/invoked".
* Examples:
* 'sdk-init/available-skills' — every-turn list (NOT displayed)
* 'sdk-turn/loaded-skill' — this turn's actual injection
* 'mcp-server-schemas' — all schemas (every turn)
* 'mcp-turn/invoked-tool' — this turn's MCP tool call
* 'workspace-rules-fs/CLAUDE.md' — file-system source
*/
source: string;
/** Optional sub-detail (e.g. each loaded Skill name + size). */
detail?: string;
}
/** Runtime-produced snapshot. Each kind is either real entry OR unsupported. */
export interface RuntimeContextAccountingSnapshot {
entries: Partial<Record<ContextAccountingKind, ContextAccountingEntry>>;
/** Kinds this Runtime explicitly can't count. UI hides these rows. */
unsupported: readonly ContextAccountingKind[];
/** RuntimeId — project convention 'claude_code'|'codepilot_runtime'|'codex_runtime'. */
producedBy: RuntimeId;
/** Optional sub-classification (Codex Account vs CodePilot proxy etc). */
providerBackend?: 'codex_account' | 'codepilot_proxy' | 'native_app_server' | string;
}每个 Runtime adapter 必须实现:
function produceContextAccountingSnapshot(
input: AdapterInput
): RuntimeContextAccountingSnapshot验收:
- Contract type 落地 + 单元测试 pin shape(≥ 3 测试:entries shape / unsupported semantics / producedBy 必填 RuntimeId)
- walkContextUsage 返回值 + hook 喂改成消费新 snapshot(取代旧
ContextBreakdownSnapshot) - guardrail Runtime.md 加 reference 指向 Contract
v6 现状(2026-05-20):Phase 2 实施按 badge picker 路径完成(用户主动选 Skill 时计入)。Phase 2.0 POC 实际确认:SDK 不暴露 turn-level loaded-skill 元数据;可用的等效信号是 SDK assistant message 内的
tool_useblocks。覆盖 auto-invoke 主流路径转 Phase 7。下面的来源 spec 是 v2 review 时的预期,记录原始决策上下文;实际实现细节看 Phase 7 + commit5c356e8之前的链。
前置:先确认 SDK 字段事实
Phase 2.0 一步 POC:跑一条 chat 把 SDK init message + run-level messages dump 到日志,实际看到 SDK 提供什么字段。下面的来源 spec 是待 POC 确认的预期;如果字段名 / 语义不符就调整。
| Kind | 真实来源(待 POC 确认) | source breadcrumb | 备注 |
|---|---|---|---|
system_prompt |
SDK 实际发出的 system prompt 完整文本(含 Claude Code preset + adapted.systemPromptAppend)char/4 |
sdk-actual-system-prompt |
必须是 SDK 实际看到的,不能用 compiled.basePrompt + artifactContracts 片段 |
tools |
SDK Options.tools 的 tool definition JSON(非 MCP,且本轮真实注入 vs available list 待 POC 区分) | sdk-options-tools/<available|invoked> |
区分"可用 tool descriptor"和"本轮调用的 tool";UI 显示后者 |
rules |
CLAUDE.md + workspace .cursor/rules/(文件系统直读,非 compiled.workspaceFragments 空数组) |
workspace-rules-fs/CLAUDE.md |
compiled.workspaceFragments 当前空,必须直接 fs 读 |
skills |
本轮实际注入 Skill 正文 char/4(SDK turn-level loaded-skill metadata,待 POC 确认 SDK 是否暴露) | sdk-turn/loaded-skill |
不计入 available skill descriptors(那是每轮固定的) |
mcp |
SDK Options.mcpServers tool list 各 tool schema JSON char/4;区分 available schemas vs 本轮 invoked tool calls | mcp-server-schemas/<available|invoked> |
同 tools 区分 |
memory |
assistant memory snapshot tokens(adapter 必须传 assistantMemory;compiled.memoryFragments 当前 ≈ 0) |
assistant-memory-snapshot |
adapter 改 signature 加 assistantMemory 参数 |
风险 / POC 步骤:
- SDK initMsg.skills / turn-level loaded skill 字段是否存在 — Phase 2.0 必须 POC 确认
- 如果 SDK 只暴露 available list,不暴露 invoked → 必须从其他地方(如 tool_use 事件流)反推
- 如果完全反推不到 → 该字段标 unsupported,绝不允许伪装 available-list 当 invoked
Native runtime 通过 AI SDK 自建:
- tools: AI SDK
tools参数的 schema JSON - system_prompt: 自建 system prompt template
- mcp: 通过 harness bundle 拿到的 MCP 描述
- skills: harness bundle 的 user/external extensions kind=skill
- memory: 同 ClaudeCode
同样要区分 available vs 本轮 invoked,POC 先确认每个字段实际可拿到的形态。
Codex Runtime 必须按 provider backend 拆三种场景,不能一概用 "Codex unsupported memory" 盖过去。
producedBy: 'codex_runtime',providerBackend: 'codex_account'- memory:unsupported(Codex Account 是 perception_only,不接 CodePilot memory bridge)
- skills / mcp / tools:按 app-server 实际能力声明 — Phase 5e 已确认大部分 perception-only / unsupported
- rules / system_prompt:从 Codex app-server init metadata 拿
producedBy: 'codex_runtime',providerBackend: 'codepilot_proxy'- memory:接通(Phase 5e 已通过 CodePilot bridge 支持 memory_recent / memory_search)
- skills / mcp / tools:按 CodePilot bridge 实际暴露的能力计,跟 ClaudeCode runtime 同源(capability matrix)
- rules:同 ClaudeCode(workspace-rules-fs)
producedBy: 'codex_runtime',providerBackend: 'native_app_server'- 按 app-server 自身能力(不走 CodePilot bridge)声明
- 大部分字段可能 unsupported(app-server 不暴露细分)
Codex 当前 context_usage 是 live event,run_completed 的 result 不带 usage。这跟设计原则 #4(result 是唯一持久化入口)冲突。
修法:
- Codex adapter 必须缓存最后一次
usage_updatedevent + bridge/proxy accounting snapshot - 在
run_completed→ 转换成 result event 时写入result.usage.context_breakdown(用缓存的最后一次) - 否则 DB 没最终账本,
useContextUsage看到 token_usage = null,breakdown 整个失效
实施位置(推定,需 verify):src/lib/codex/runtime.ts 或 src/lib/codex/event-mapper.ts 在 run_completed 处理处加 snapshot 缓存读取 + 嵌入 usage。
三 Runtime 各跑一条真实 smoke:
- ClaudeCode:普通"你好" baseline + humanizer-zh Skill badge picker 调用对比,验证 Skills 行仅在 badge 选 Skill 后出现且数字反映 SKILL.md 大小
- Native:Native session 配 MCP server,验证 MCP 行非 placeholder
- Codex 三场景:
- codex_account session — memory 不显示行(unsupported)
- codex + codepilot_proxy session — memory 接通显示真实
- codex native app-server — 按实际能力
v6 现状:Phase 5 cover badge picker 路径已 partial 完成(v5 producer canonicalize + v5 hotfix client bundle 修复后;待 user 真实凭据 smoke 填 Ledger)。auto-invoke 主流路径(用户自然语言 / Claude 自主调 MCP / Tool)转 Phase 7.6。Phase 5 closeout 等 Phase 7 完成后一并归档。
Smoke Ledger 落 Phase 6 文档 + 本文档;Phase 6 closeout 后归档。
| Phase | 内容 | Commit | 状态 |
|---|---|---|---|
| Phase 0 | 止损 — 删 假数据 snapshot + ContextBreakdownSnapshot 字段全可选 | 4fcc09e |
✅ |
| Phase 1 | RuntimeContextAccountingSnapshot contract type + walkContextUsage + hook wire + 10 unit tests | a997e33 |
✅ |
| Phase 2 | ClaudeCode adapter produceClaudeCodeAccountingSnapshot + 6 unit tests + send-path wire(badge path only) |
7c2937e → v3 92a777a → v4 c35918b → v5 27b5629 → v5 hotfix 5c356e8 |
✅ 部分(badge picker 选 skill → entries.skills 已 verify;auto-invoke path 仍漏,转 Phase 7) |
| Phase 3 | Native (agent-loop.ts) produceNativeAccountingSnapshot + send-path wire |
ebe0071 |
✅ |
| Phase 4 | Codex (runtime.ts) produceCodexAccountingSnapshot + usage cache + run_completed → supplementary result event with usage+context_accounting |
ebe0071 |
✅ |
| Phase 5 | Smoke + closeout(badge path 已 verify;auto-invoke path 反例归 Phase 7.6) | 待 | ✅ 由 Phase 7.7 Smoke Ledger 真实 evidence 段全面取代 |
| Phase 7 | 三 Runtime 共用 ToolInvocation 抽象 + Producer 时机迁移 — 9 个子阶段全部完成 | 80eb959 + 后续 4 commit |
✅ |
Phase 7 后此表的 ClaudeCode 列会变 —
skills/mcp/tools三项从 unsupported 改为 real (auto-invoke scan)。Phase 7 实施完成时同步更新本表。
| Kind | ClaudeCode 当前 | ClaudeCode (Phase 7 目标) | Native 当前 | Native (Phase 7 目标) | Codex codex_account 当前 | Codex (Phase 7 目标,pending 7.3 调研) | Codex codepilot_proxy 当前 | 真实 source |
|---|---|---|---|---|---|---|---|---|
system_prompt |
unsupported | unsupported | unsupported | unsupported | unsupported | unsupported | unsupported | 全 SDK preset opaque from 我们侧 |
tools |
unsupported | ✅ real (auto-invoke) | unsupported | ✅ real (auto-invoke) | unsupported | ✅ real 或 unsupported(7.3 决议) | unsupported | 共用 collectAutoInvokeSnapshot — 扫 tool_use 内置 tool 名 + per-call args/result chars/4 |
rules |
✅ real (CLAUDE.md filesize) | ✅ real(保留) | ✅ real | ✅ real(保留) | ✅ real | ✅ real | ✅ real | workspace/CLAUDE.md fs.statSync |
skills |
✅ real (badge picker path) | ✅ real (badge + auto-invoke) | unsupported | ✅ real (auto-invoke) | unsupported | ✅ real 或 unsupported(7.3) | unsupported | 共用 collector — tool_use.name === 'Skill' + badge merge dedup → discoverSkills lookup → SKILL.md filesize |
mcp |
unsupported | ✅ real (auto-invoke) | unsupported | ✅ real (auto-invoke) | unsupported | ✅ real 或 unsupported(7.3) | unsupported | 共用 collector — mcp__<server>__<tool> 前缀 split → per-call args + tool_result chars/4 |
memory |
unsupported | unsupported | unsupported | unsupported | unsupported | unsupported | unsupported (待 Phase 6.x bridge wire) | adapter 没传 assistantMemory |
files_attachments |
unsupported | unsupported | unsupported | unsupported | unsupported | unsupported | unsupported | composer pending 走 ContextBreakdownInputs.pending(已 Phase 6 Tier 2 wire) |
说明:
- "unsupported" = Runtime 明确声明不计 → UI hide 行,不显示 0 假数据
- "real" = 该 Runtime 从真实 data source 读 token 估算 → UI 显示真实数字
system_prompt / tools / mcp / memory全 unsupported 是 Phase 0-4 当前架构限制- Phase 7(v6 增补)会解锁 ClaudeCode 列的
tools/mcp/skills(auto-invoke):扫 assistant messagetool_useblocks 提取 invocation-level token;system_prompt / memory / files_attachments仍 unsupported(不在 Phase 7 范围) - 远期 Phase 6.x / 8 接通 SDK 更深字段 / harness bundle / MCP loader schema 后扩展剩余 unsupported 项
- ClaudeCode 是唯一接通 skills 的 Runtime(slash-command 检测限定到 Claude Code 入口);Native + Codex producer 没有 raw userPrompt 可访问,等 Phase 6.x adapter input 扩展
| Kind | source value | 含义 |
|---|---|---|
| skills (ClaudeCode) | workspace/.claude/skills/<name>/SKILL.md (workspace) OR absolute path (global / agents) |
MessageInput badge label → discoverSkills() lookup 真实 filePath |
| rules (all 3 Runtimes) | workspace/CLAUDE.md |
workspace 根的 CLAUDE.md 文件 |
未来扩展 (Phase 6.x):
sdk-actual-system-prompt— SDK 实际看到 system prompt(待 SDK 暴露)mcp-server-schemas/available— MCP server tool schemasassistant-memory-snapshot— adapter assistantMemory wire 后
| 测试文件 | 测试数 | 覆盖 |
|---|---|---|
src/__tests__/unit/context-accounting.test.ts |
10 | Contract shape pin (producedBy 仅 3 RuntimeIds; source breadcrumb required; unsupported first-class; available vs invoked 语义;snapshotToCompilerInputs 各分支) |
src/__tests__/unit/claude-code-context-accounting.test.ts |
6 | ClaudeCode producer (普通消息 vs skill 消息 DIFFERENT snapshot;slash-command scan;source breadcrumb 格式) |
现有 context-breakdown.test.ts |
19 | Phase 6 Tier 2 contract 仍 valid (compiler inputs 形态不变) |
现有 context-breakdown-list-i18n.test.ts |
6 | i18n 元规则 |
现有 run-cockpit-unknown-capacity.test.ts |
4 | Popover fallback 仍 OK |
每行 Evidence 列必须含:
- DB 行验证: latest assistant message 的
token_usage.context_accounting完整 JSON dump(含 producedBy / providerBackend / unsupported list / entries map with sources) - Popover 验证: 截图 + DOM 摘要(哪些行可见 / 隐藏 / 数字 + source breadcrumb 一致)
- Console 口径: "console clean except tech-debt #20"
| Date | Runtime | Provider | Model | 凭据形态 | 场景 | Result | Evidence |
|---|---|---|---|---|---|---|---|
| 示例 | claude_code | Claude Code | Opus | 本机登录 | 普通"你好" vs /humanizer-zh xxx |
✅ | token_usage.context_accounting: 普通: { entries: { rules: {tokens: 234, source: 'workspace/CLAUDE.md'} }, unsupported: [tools/mcp/memory/system_prompt/files_attachments/skills], producedBy: 'claude_code' } vs Skill: { entries: { rules: {...same}, skills: {tokens: 580, source: 'workspace/.claude/skills/humanizer-zh/SKILL.md', detail: 'humanizer-zh'} }, ... } |
| 待 | claude_code | — | — | — | 普通消息 baseline | 📋 | |
| 待 | claude_code | — | — | — | /humanizer-zh skill 调用 | 📋 | Skills 行必须出现且数字 = SKILL.md char/4 |
| 待 | codepilot_runtime | — | — | — | Native + MCP 工具 | 📋 | rules 真实;其他 unsupported hide |
| 待 | codex_runtime | codex_account | — | OAuth | 普通消息 | 📋 | rules 真实;其他 unsupported;providerBackend='codex_account' |
| 待 | codex_runtime | (codepilot proxy) | — | API key | 普通消息 | 📋 | rules 真实;其他 unsupported;providerBackend='codepilot_proxy';result event 含 usage(Phase 4 P2 修复验证) |
升级前(Phase 2-4 落地后)只在用户主动用 badge picker 选 skill 时显示 Skills 行。但用户最自然的工作方式是自然语言提到 skill 名 / 让 Claude 自主决定调哪些 MCP / Tool,这条主流路径 popover 完全看不到。
Phase 7 后(v7:三 Runtime 全覆盖):
- popover Skills 行:Claude 实际 invoke 的所有 Skill(无论用户用 badge 选还是自然语言触发都会出;ClaudeCode + Native;Codex 调研后决定)
- popover MCP 行:本轮调用的 MCP server / tool 清单 + 每个 invocation 估算 token(之前 3 Runtime 永远 hide)
- popover Tools 行:本轮调用的内置工具次数(Bash×N / Read×N / Edit×N,之前 3 Runtime 永远 hide)
- 切到 Native session 或 Codex session 看到同样的行 — 跨 Runtime 一致语义
- 不做账单级精度:仍 char/4 估算,跟 server tokenization 有偏差
- 不试图统计 MCP schema 注入 的系统提示部分 token(那属于 system_prompt,Phase 2-4 已声明 unsupported;Phase 7 只统计 invocation-level token,即 args + tool_result 内容)
- 不为 "available 但未 invoke 的 skill/MCP/tool" 显示行 — 只统计实际发生的 tool_use
- 不动 badge picker 路径(保留作为 UX 保证,picker 主动选时也会被 tool_use 扫描覆盖到 — 数据来源一致)
- 不预先 trigger producer — 只在 result event 内跑一次
- 铁证 #1(ClaudeCode Skills 自然语言):复制本计划触发证据的同一 prompt "你好帮我创建一个当前目录内容的可视化解释组件,然后随便写点啥调用 humanizer-zh 优化",修复前 DB row entries.skills 空,修复后 entries.skills 含 humanizer-zh + SKILL.md 真实 filesize
- 铁证 #2(ClaudeCode MCP):Widget 生成场景 entries.mcp 非空 + source 含
codepilot-widget、codepilot-memoryserver 名 + invocation count - 铁证 #3(ClaudeCode Tools):任何调用 Bash 的对话 entries.tools 非空 + detail 含 "Bash × N"
- 铁证 #4(Native 三类):Native session 跑等效 prompt → entries.skills / entries.mcp / entries.tools 跟 ClaudeCode 语义一致(行出、数字接近)
- 铁证 #5(Codex 三类,前提是调研可行):Codex session 跑等效 prompt → 同上;不可行则 entries 显式 unsupported(不允许显示 0 / 假数据)
- 反例 baseline:3 Runtime 都跑"你好" 不触发任何 tool_use → entries.skills/mcp/tools 都 omit(防 hallucination 加 0)
- 回归 baseline:badge picker 选 humanizer-zh 仍 work(与 auto-invoke 数据等价)
- harness contract:
src/__tests__/unit/harness-capability-contract.test.tspass(CLAUDE.mdfeedback_no_live_smoke_driven_patching:harness contract 变更必须先过 contract test 再 burn 真实凭据 smoke) - Smoke Ledger 加 Phase 7.7 反例行 — ClaudeCode 3 行(Skills/MCP/Tools) + Native 3 行 + Codex 按调研结果 0~3 行
A 类(用户可见 UI)+ C 类(基础设施):A 直接修复用户最反馈的痛点;C 沉淀 ToolInvocation 抽象 contract,未来接新 Agent 不需要重写 producer 逻辑(用户决议 v7:「不然后面接新的 Agent 又会出问题」)。
以下内容是给开发者看的;用户验收只看上一段「怎么验收」铁证。
三 Runtime 在 SSE 流出阶段已经 emit 相同 shape 的 tool_use event:
| Runtime | 累积点 | 已 emit shape |
|---|---|---|
| ClaudeCode | src/lib/claude-client.ts:1585-1591 if (block.type === 'tool_use') |
{ id, name, input } |
| Native | src/lib/agent-loop.ts:483-489 case 'tool-call' |
{ id: event.toolCallId, name: event.toolName, input } |
| Codex | src/lib/codex/runtime.ts:82-134 type: 'tool_use' event |
{ tool_use_id, name, input } |
含义:抽象不需要新发明 — 把已 emit 的 SSE tool_use event shape 提升为正式 contract,三 Runtime accumulator 都套同一个接口。这也是为啥 v7 用户说"必须抽象"是对的:当前虽然 shape 一致,但累积逻辑散在三处,未来加第 4 个 Agent 又会复制一份。
// 共用 Runtime-agnostic shape,所有 Runtime accumulator 都产这个
export interface ToolInvocationRecord {
toolUseId: string;
toolName: string; // 'Skill' | 'mcp__server__tool' | 'Bash' | ...
input: unknown;
resultContent?: string; // 配对 tool_result,未配上时 undefined
}
// 共用 accumulator — 在每 Runtime 的 streaming loop 中复用
export class ToolInvocationAccumulator {
recordToolUse(toolUseId: string, toolName: string, input: unknown): void;
recordToolResult(toolUseId: string, content: string): void;
drain(): readonly ToolInvocationRecord[]; // result event 时调用
}
// 共用 collector — Runtime-agnostic 分类 + 估算 + 产 snapshot
export function collectAutoInvokeSnapshot(input: {
workspacePath: string;
records: readonly ToolInvocationRecord[];
producedBy: ContextAccountingRuntimeId; // 'claude_code' | 'codepilot_runtime' | 'codex_runtime'
providerBackend?: string; // Codex 子分类透传
selectedSkills?: readonly string[]; // badge picker 兼容;dedup
workspaceRulesSource?: () => Promise<ContextAccountingEntry | undefined>; // rules 入口可注入
}): RuntimeContextAccountingSnapshot;Token 估算公式(共用,3 Runtime 不重复):
- Skill:
discoverSkills().find(s.name === input.skill).filesize / 4 - MCP per call:
(JSON.stringify(input).length + (resultContent?.length ?? 0)) / 4 - 内置 tool per call: 同 MCP 公式
| Runtime | accumulator wire 位置 | result-event produce 位置 | 备注 |
|---|---|---|---|
| ClaudeCode | claude-client.ts:1585 block.type==='tool_use' / :1674 tool_result |
:2162 result handler 内 |
删 :1100-1166 的旧 streamClaude-start produce |
| Native | agent-loop.ts:483 case 'tool-call' / :505-510 tool_result |
result event handler(在 finalUsage 处) | 替换现有 produceNativeAccountingSnapshot |
| Codex | codex/runtime.ts:82-134 tool_use event / 配对 tool_result |
run_completed → 嵌入 result.usage.context_accounting(同 Phase 4 P2 通道) | 是否复用 SDK 原生 API 待 7.3 调研 |
| 子阶段 | 内容 | 验收 |
|---|---|---|
| 7.0 抽象 contract | auto-invoke-accounting.ts 新模块:ToolInvocationRecord type / ToolInvocationAccumulator class / collectAutoInvokeSnapshot 入口;专属 unit test pin shape |
模块存在 + 8 个 contract test pass(accumulate / drain / dedup / Skill 分类 / MCP 前缀 split / 内置 tool / 空 records / badge + tool_use Skill merge) |
| 7.1 ClaudeCode POC + fixture | 从 DB row 487c190a 抽 tool_use blocks 存 src/__tests__/fixtures/widget-message-tool-uses.json;verify 4 个 tool_use 都有对应 tool_result(如果没有,记录到 7.0 contract risk) |
fixture 文件存在;shape 测试 pass;tool_result 配对率 100%(不通过则 7.0 contract 加 "missing tool_result" 分支) |
| 7.2 ClaudeCode 接通 | claude-client.ts wire ToolInvocationAccumulator;删旧 produce(line 1100-1166);result event 调 collectAutoInvokeSnapshot;保留 selectedSkills badge merge | typecheck pass;现有 unit test (claude-code-context-accounting.test.ts 13 测试) 改造为复用 collectAutoInvokeSnapshot 不破坏既有 assert |
| 7.3 Codex SDK 调研 | 读 codex-sdk types / runtime SSE 流文档;判定 Codex 是否暴露 native tool-call list API;如果有 → 用;没有 → 走 SSE tool_use event accumulator(兜底);写决策入 decision log |
调研文档(短)+ 决议落到 decision log |
| 7.4 Native 接通 | agent-loop.ts wire ToolInvocationAccumulator;替换 produceNativeAccountingSnapshot;result event 调 collectAutoInvokeSnapshot;producedBy: 'codepilot_runtime' |
新建 native-auto-invoke.test.ts 4 test:Skill / MCP / 内置 tool / 空;现有 native 单测不回归 |
| 7.5 Codex 接通 | 按 7.3 调研决议接通:accumulator wire 到 runtime.ts 或新位置;保持 Phase 4 P2 run_completed → supplementary result event 通道;providerBackend 透传 | codex 单测 + harness contract test pass |
| 7.6 PHASE_2_UNSUPPORTED 收编 | 三 Runtime producer 的 unsupported 列表去掉 tools mcp skills(auto-invoke 已 cover);保留 system_prompt memory files_attachments |
每 Runtime 每 kind 总账表更新;context-accounting.test.ts 同步 |
| 7.7 Smoke Ledger 反例 | ClaudeCode 3 行(Skills 自然语言 / MCP Widget / Bash) + Native 3 行(等效场景)+ Codex 按 7.3 0~3 行;每行 DB row dump + popover 截图 + console clean | Ledger 行齐;用户/Codex 验证 pass |
| 7.8 harness contract | harness-capability-contract.test.ts 三 Runtime auto-invoke 维度都 pass |
测试列表跑全绿 |
新增:
src/lib/harness/auto-invoke-accounting.ts— 抽象 contractsrc/__tests__/unit/auto-invoke-accounting.test.ts— 8 contract testsrc/__tests__/fixtures/widget-message-tool-uses.json— golden fixturesrc/__tests__/unit/native-auto-invoke.test.ts— Native 4 testdocs/research/codex-sdk-tool-call-surface.md(7.3 调研短文)
改动:
src/lib/harness/claude-code-context-accounting.ts— 复用 collectAutoInvokeSnapshot;移除重复逻辑src/lib/harness/native-context-accounting.ts— 同上src/lib/harness/codex-context-accounting.ts— 同上src/lib/claude-client.ts— 时机迁移 + accumulator wiresrc/lib/agent-loop.ts— accumulator wiresrc/lib/codex/runtime.ts— accumulator wire(按 7.3 决议)src/__tests__/unit/claude-code-context-accounting.test.ts— 改造 13 既有 test 复用 collectorsrc/types/index.ts— 视 contract 类型导出(可能新增 ToolInvocationRecord 导出)
不改:
src/components/chat/MessageInput.tsx(badge picker 路径保留)、ChatView.tsx、stream-session-manager.ts、/api/chat(selectedSkills 通道仍 alive 作 badge UX 保证)
- MCP schema 注入仍 unsupported:用户期待"MCP-heavy 工作流影响 system_prompt 大"无法覆盖;这是 Phase 8 范围(需要从 MCP loader 拿 server tool schemas)
- tool_result 配对失败的兜底:7.1 POC 必须 verify Widget message 全部 tool_use 都有对应 tool_result;若配对率 < 100%,accumulator 必须能优雅降级(drain 时返回 record.resultContent = undefined,token 估算只算 input 部分;不许漏行)
- estimate 精度:char/4 + JSON 序列化估算与 server tokenization 偏差 5-15%;可接受(跟 Phase 2-4 一致基线)
- Skill 同名歧义:tech-debt #22 已记录;collectAutoInvokeSnapshot 复用 discoverSkills 仍受影响
- Codex 走兜底路径的代价:如果 7.3 调研结论是 "SDK 不暴露 native API",Codex 走 SSE
tool_useevent accumulator — 跟 ClaudeCode/Native 路径同源,无额外代价;但若 Codex SSE 没全量带 tool 内容(如 input 截断),accounting 精度会比另两个 Runtime 低,需 7.5 决议
新 Agent 接入 CodePilot 时,必须遵守 ToolInvocation contract,不允许重写 producer 逻辑:
- 在 Agent 自己的 streaming loop 中实例化
ToolInvocationAccumulator - 每次看到 tool_use 调用
accumulator.recordToolUse(id, name, input) - 每次看到 tool_result 调用
accumulator.recordToolResult(id, content) - result event 时调
collectAutoInvokeSnapshot({ records: accumulator.drain(), producedBy, workspacePath, ... }) - 把 snapshot 嵌入
usage.context_accounting
禁止:
- 在 Agent runtime 内重写 token 估算公式(必须复用 collectAutoInvokeSnapshot)
- 在 Agent runtime 内重写 Skill / MCP / 内置 tool 分类(必须复用 collector 内分类)
- 跳过 accumulator 直接构造 RuntimeContextAccountingSnapshot(除非 producedBy 明确不支持 auto-invoke 维度,此时全部 unsupported;不允许部分自构)
只允许:
- 自定义 producedBy 值(按 RuntimeId enum)
- 自定义 providerBackend 子分类透传
- 自定义 workspaceRulesSource 入口(rules entry 是 Runtime-specific 文件系统/配置查询)
- MCP server tool schemas 系统提示注入估算 → 留 Phase 8
- Memory / files_attachments 接通 → Phase 6.x 范围
- 第 4+ 个 Agent 真正接入 → 新 Agent 各自的 phase,但必须遵守上方扩展规则
-
2026-05-19(v1):User Codex review 指出 Phase 6 Tier 2 (
a4fa2d4) "数字不是用户以为的那个东西"。triggering 本计划。拆 6 Phase(0 止损 + 1 Contract + 2 ClaudeCode + 3 Native + 4 Codex + 5 closeout),Phase 0 紧急独立 commit。 -
2026-05-19(v1):unsupported 是 first-class 状态而非"显示 0"——避免 hallucination 风险。
-
2026-05-19(v1):tech-debt #21(tools/mcp placeholder)被本计划吸收。
-
2026-05-19(v2 Codex review pass):接受 4 P1 + 1 P2 findings 全部修订:
- P1 Phase 0 范围扩大:v1 错误宣称 system_prompt / rules / memory "保留真实",事实是
workspaceFragments空数组、adapter 没传 assistantMemory、basePrompt为空——这三个跟 skills/mcp 一样不真实。Phase 0 改成只保留 conversation + cache 两行,其他全部隐藏直到 Phase 2-4 真接通 - P1 Phase 2 Skills 语义:必须区分
sdk-init/available-skills(每轮固定,不显示)vssdk-turn/loaded-skill(本轮注入,显示);source breadcrumb 强制区分;同理 tools / mcp 区分 available 和 invoked - P1 Phase 4 Codex 拆三场景:不能一概 "Codex unsupported memory"。codex_account 才 unsupported;codex + codepilot_proxy 经 Phase 5e bridge 支持 memory;native app-server 按 app-server 实际能力
- P1 producedBy 命名:
'claude_code' | 'codepilot_runtime' | 'codex_runtime'走 RuntimeId 类型,禁止 'native' 别名;子分类用providerBackend?: ... - P2 Codex result event 持久化:Codex adapter 必须缓存最后一次 usage_updated + run_completed → result.usage.context_breakdown 落库,否则 DB 没最终账本
- P1 Phase 0 范围扩大:v1 错误宣称 system_prompt / rules / memory "保留真实",事实是
-
2026-05-20(Phase 0-4 实施完成):5 个 commit 串成 Phase 0→1→2→3→4 chain:
4fcc09ePhase 0 止损(删假数据 + 字段 optional)a997e33Phase 1 Contract type + 16 unit tests (10 accounting + 6 走 hook 已有)7c2937ePhase 2 ClaudeCode adapter (skills real source via slash-command + rules via CLAUDE.md) — deprecated by 2026-05-20 v3 fix belowebe0071Phase 3 Native + Phase 4 Codex (rules real source;Codex usage cache + supplementary result event for P2 fix)- 实施现状:每 Runtime 每 kind 状态表 + source breadcrumb 标准 + 测试覆盖见上方"实施状态"段;Smoke Ledger 待 Codex/user 真实凭据 smoke 填
-
2026-05-20(v3 Codex review pass):接受 1 P1 + 1 P2 finding:
- P1 Skills 仅覆盖手打
/skill窄路径,不覆盖真实 Agent Skill badge 选择路径:用户用 UI 选 Agent Skill 时 dispatchBadge 实际发的是Use the humanizer-zh skill. User context: ...,不是/humanizer-zh。Phase 2 commit 7c2937e 的 SLASH_COMMAND_RE 完全 miss 这条真实路径。修法:删 prompt regex,从 MessageInput badge 元数据作为结构化字段 (selectedSkills) 通过 send-path 传到 producer;producer 用discoverSkills()(已覆盖 project / global / installed / agents skill 目录)按 name lookup 拿 SKILL.md 真实 filePath - P2 "SDK POC 已完成"口径不成立:SDK 实际只暴露 available skills/tools/mcp_servers 列表元数据,没暴露 turn-level loaded-skill。Phase 2 实施其实是 fs 启发式 + 现在改为 badge 结构化 metadata。文档口径修正为 "starts from MessageInput badge metadata; SDK doesn't expose turn-level loaded-skill; no prompt-text guessing"
- P1 Skills 仅覆盖手打
-
2026-05-20(v3 P1 修复 commit 92a777a):
- producer 签名加
selectedSkills?: readonly string[]参数;用 discoverSkills() lookup filePath;删 SLASH_COMMAND_RE 完全(手打/skill也 hide,避免猜测) - 5 文件 plumbing:MessageInput badges → onSend → ChatView/page sendMessage → stream-session-manager body → /api/chat → streamClaude options → producer
- 新回归测试:dispatchBadge real prompt + structured selectedSkills 必须产 skills entry;手打
/skill无 selectedSkills 必须 hide
- producer 签名加
-
2026-05-20(v4 P1 修复 commit c35918b):ChatView 已有会话发送链路 plumb 完整:QueuedMessage interface 加字段 / doStartStream 6th param / 三处 caller (直发 + enqueue + dequeue) 都传 selectedSkills。同时修 v3 漏的 typecheck error (selectedSkills 在 doStartStream scope 内未声明)。
-
2026-05-20(v4 P2 deferred → tech-debt #22):selectedSkills 仍是
readonly string[]。同名 skill 在 project + global 重复时按 discoverSkills() 优先级选第一个,可能跟 picker 里点的不一致。短期接受(user OK)— 单 skill 场景不触发;同名重复时数字仍真实,只 source breadcrumb 可能指错 SKILL.md,不算 hallucination。升级方向(tech-debt #22):string[]→{ name, sourcePath }[],picker 存 SkillDefinition.filePath,全链 plumb 到 producer,producer 用 sourcePath 直接 statSync 不再二次 lookup。 -
2026-05-20(v5 P1 修复 commit
27b5629):badge picker 存command: '/humanizer-zh'(slash 前缀),producer 原 strict 比对 SkillDefinition.name 失败。加canonicalizeSkillName(value)=value.trim().replace(/^\/+/, '')+ 大小写不敏感 lookup;新加 5 个 regression test(slash 前缀 / 大小写 / 防御性空格 / slash-only / source breadcrumb 格式)。 -
2026-05-20(v5 hotfix commit
5c356e8):v5 引入 client bundle 致命错误。MessageInput.tsx 加的await import('@/lib/harness/claude-code-context-accounting')把 producer module → discoverSkills →node:fs拖进客户端 bundle,触发Module not found: 'fs'→ 整个 /chat 页面 HTTP 500。修法:MessageInput 改 inline(v) => v.trim().replace(/^\/+/, '')不 import 任何 server-only module;producer 自留 canonicalize export 给 server caller 用,client + server 各一份(enforced boundary)。教训:Next.js client bundle traces 所有 reachable imports (static + dynamic);dynamic import 不 escape bundle inclusion;想当然用await import()跨 client-server 边界会出事。 -
2026-05-20(v6 触发:用户真实 UI smoke 暴露设计源错位):用户 prompt "你好帮我创建一个当前目录内容的可视化解释组件,然后随便写点啥调用 humanizer-zh 优化" → DB row
487c190a72ce51e030e706ca7ab3cea8token_usage.context_accounting 只有 rules,缺 skills + mcp + tools。Assistant message content 实际 4 个 tool_use:Bash × 2 / mcp__codepilot-widget__codepilot_load_widget_guidelines / mcp__codepilot-memory__codepilot_memory_recent / Skill { skill: 'humanizer-zh' }。根本原因:Phase 2-4 producer 在 streamClaude 起点跑,源是options.selectedSkills(badge picker 预选);用户没用 badge,自然语言提到 skill 后 Claude 自主 invoke Skill tool — 这条 path producer 永远看不到。同理 MCP / 内置 Tool 永远 unsupported。Phase 2-4 仍属 partial:只 cover badge picker 路径,不 cover Claude 自主 invoke 主流路径。决议:加 Phase 7 (producer 时机迁到 SDK result event;扫 assistant message tool_use blocks);Phase 7 不否定 Phase 2-4(badge 路径 dedup 合并),只补漏 auto-invoke 路径。 -
2026-05-20(v7 用户决议:Phase 7 范围扩三 Runtime + 强制抽象):用户明确要求 "ClaudeCode + Native 都搞;Codex 看 SDK 接口;也得抽象,不然后面接新的 Agent 又会出问题"。侦察结论:三 Runtime 早已 emit 相同 shape 的 SSE
tool_useevent(ClaudeCodeclaude-client.ts:1585/ Nativeagent-loop.ts:483-489/ Codexcodex/runtime.ts:82-134)— 抽象 shape 不需要新发明,把已 emit 的 event shape 提升为正式 contract 即可。v7 调整:- 加 Phase 7.0 — 新模块
auto-invoke-accounting.ts沉淀ToolInvocationRecord/ToolInvocationAccumulator/collectAutoInvokeSnapshot共用 contract;8 个 contract test pin shape - 子阶段拆为 7.0-7.8(vs v6 的 7.1-7.7),加 Codex SDK 调研 (7.3) + Native 完整接通 (7.4) + Codex 接通 (7.5)
- Phase 7 价值类型从 A 类升级为 A + C 类(C = 基础设施:跨 Agent 复用 contract)
- 每 Runtime 每 kind 表加 Phase 7 目标列;Native 三项从 unsupported → ✅ real (auto-invoke);Codex 待 7.3 决议
- **沉淀「跨 Agent 扩展规则」**入计划:新 Agent 必须套 ToolInvocationAccumulator + collectAutoInvokeSnapshot;禁止重写 token 估算 / 分类逻辑 / 直接构造 snapshot。这条规则单独成段写给未来接 Agent 的开发者看
- Smoke Ledger 反例扩到 3 Runtime × 多场景
- 加 Phase 7.0 — 新模块
-
2026-05-20(Phase 7 closeout 完成):三 Runtime 真实 UI smoke 全部跑完(见上方 Smoke Ledger 真实 evidence 段),4 commit 链 + 一系列 bugfix 全部落地,归档至
completed/。- 后续 bugfix commit 链(按提交顺序):
9ed3af9mini-bar 不被小类目挤满(加minCellsPerKind参数)d273388Native + Codex provider-proxyinput_tokens=0三层兜底(walkContextUsage / buildContextUsageBreakdown / useContextUsage)e791b22mini-bar 三 fix(Codex 小占用消失 / Native 全满 / Native 浮层遮文案 padding 错位)4cd60f2Native runtime 显示 context window(agent-loop catalog fallback)57d0f3dmodel-context revert hallucinated entries — 只保留 web 核实 + DB 实际使用的 glm-5-turbo / gpt-5.4 / gpt-5.5;剩下 modelId 留 absent 让 mini-bar 200K fallback 兜底 graceful degrade2473905tech-debt #24 (footer cost 双计 P2 deferred — Codex review 接受)
- 跨 Agent 扩展规则已沉淀到 plan「跨 Agent 扩展规则」段 + feedback memory
feedback_new_agent_must_reuse_contracts.md,未来接新 Agent 必须套已有 contract(plan review 时如出现 "自己写一份" → P1 reject) - 保留 tech-debt #22(selectedSkills 同名歧义
string[]→{name, sourcePath}[])+ tech-debt #24(footer cost 双计),不在本轮扩 scope - 本计划归档至
completed/context-accounting-runtime-contract.md
- 后续 bugfix commit 链(按提交顺序):
-
2026-05-20(Phase 7 实施完成 commit
80eb959):8 个子阶段一口气落地(7.7 真实凭据 smoke 等用户跑除外):- 7.0 auto-invoke-accounting.ts 新模块 (332 行):ToolInvocationRecord + ToolInvocationAccumulator + classifyToolUse + collectAutoInvokeSnapshot + canonicalizeSkillName + resolveWorkspaceClaudeMdRules 共用 contract
- 7.1 Widget message golden fixture (src/tests/fixtures/widget-message-tool-uses.json) — DB row 487c190a 抽 5 个 tool_use 100% tool_result 配对率
- 7.2 ClaudeCode wire (claude-client.ts 两条路径) — 主路径 line 1576/1672/1844 + 兼顾 retry-after-compression 的 alt 路径 line 2102/2156/2198;删 streamClaude-start produce
- 7.3 Codex SDK 调研短文 (docs/research/codex-sdk-tool-call-surface.md) — 决议:Codex
RuntimeRunEvent已是 canonical shape,wire 到onAnyNotification,无需外部 SDK - 7.4 Native wire (agent-loop.ts) — case 'tool-call' 481 + case 'tool-result' 495 + result emit 588;删 produceNativeAccountingSnapshot
- 7.5 Codex wire (codex/runtime.ts onAnyNotification) — tool_started / tool_completed / command_started 三类映射 ToolInvocationAccumulator;run_completed → collectAutoInvokeSnapshot 嵌入 supplementary result event(沿用 Phase 4 P2 通道)
- 7.6 PHASE_X_UNSUPPORTED 收编:3 Runtime 各自 unsupported 列表收编到
[system_prompt, memory, files_attachments];删 legacy modules(claude-code-context-accounting.ts -156 行 + native-context-accounting.ts -61 行 + claude-code-context-accounting.test.ts -286 行;codex-context-accounting.ts trim 到只剩 resolveCodexProviderBackend) - 7.7 真实凭据 Smoke ⏳ 等用户跑(dev server HTTP 200 alive,可直接试)
- 7.8 harness contract + 全套测试 ✅:27/27 新 contract+fixture 测试 pass;2948/2948 完整单元测试套件 pass(净增 4);typecheck 清;现有 chat 页面渲染 OK
- 用户决议 "新 Agent 必须套抽象" 沉淀到 feedback memory
feedback_new_agent_must_reuse_contracts.md
三 Runtime 真实 UI smoke 全部跑完。所有 DB row 都是 Phase 7 落地后产生(commit 80eb959 及之后),通过 Browser Use / CDP 触发真实 send → DB token_usage.context_accounting 取证。
Session: 2bbe8d0ff545097345fe4cf27666051f (Widget session, glm-5-turbo, workspace /Users/op7418/Documents/test-workspace2)
DB row: f2b2503d2a64bed66f3014997525dd2c (2026-05-20 17:55:37 local)
Prompt: "用 humanizer-zh 优化一下:AI 真的不仅仅是一个强大的工具,它更彰显了人类对未来的深刻洞察。"
Claude 行为: 0 tool_use blocks(直接内联思考 + 文本回答)
token_usage.context_accounting:
{
"entries": {
"rules": { "tokens": 93, "source": "workspace/CLAUDE.md", "detail": "CLAUDE.md" }
},
"unsupported": ["system_prompt", "memory", "files_attachments"],
"producedBy": "claude_code"
}断言通过: entries.skills/mcp/tools 全部 omit(no fabrication — 没 tool_use 就不显示行),unsupported 收编到 3 项,producedBy 'claude_code'。
DB row: 033fd7853ca2dd43a24f9f80293a42fb (2026-05-20 17:57:30 local, 同 session)
Prompt: "运行 ls 看看当前目录有哪些文件,然后用 humanizer-zh 优化这句..."
Claude 行为: 1 Bash tool_use (id call_8d8909fa72dc4bd) + 1 配对 tool_result (91 chars)
token_usage.context_accounting:
{
"entries": {
"rules": { "tokens": 93, "source": "workspace/CLAUDE.md", "detail": "CLAUDE.md" },
"tools": { "tokens": 48, "source": "tool_use/tool/Bash", "detail": "Bash × 1" }
},
"unsupported": ["system_prompt", "memory", "files_attachments"],
"producedBy": "claude_code"
}断言通过: entries.tools 字段从无到有,token=48 来自 Bash invocation args+result chars/4,source breadcrumb 指向 tool_use/tool/Bash,detail 含 "Bash × 1"。UI popover 验证: 工具 48 行真实显示(前 Phase 2-4 该行永远 unsupported hide)。
Session: 00a64fe15fde7c7ddfb2259c4ea4098e (Native session, glm-5-turbo via codepilot_runtime)
DB row: 0d80f9976828eb344db8bd30f25d5368 (2026-05-20 20:40:36 local)
Claude 行为: 5 个 tool_use blocks (Bash × 3 + Read × 2),全部 100% tool_result 配对
token_usage.context_accounting:
{
"entries": {
"rules": { "tokens": 93, "source": "workspace/CLAUDE.md", "detail": "CLAUDE.md" },
"tools": {
"tokens": 813,
"source": "tool_use/tool/Bash + tool_use/tool/Read",
"detail": "Bash × 3, Read × 2"
}
},
"unsupported": ["system_prompt", "memory", "files_attachments"],
"producedBy": "codepilot_runtime"
}断言通过: producedBy 'codepilot_runtime',entries.tools 含 Bash + Read 两类聚合 + 各自计数。Native 跟 ClaudeCode 走同一 collectAutoInvokeSnapshot collector,shape 完全一致。
Session: 1eeeba96677397886ae75bb210db27ad (Codex session via codepilot_proxy backend)
DB row: 50f3f89aeea00cae5a73de2b9b8fa971 (2026-05-20 20:44:17 local)
Claude 行为: 1 Bash tool_use(通过 Codex command_started → tool_started/tool_completed event 流入 accumulator)
token_usage.context_accounting:
{
"entries": {
"rules": { "tokens": 93, "source": "workspace/CLAUDE.md", "detail": "CLAUDE.md" },
"tools": { "tokens": 2062, "source": "tool_use/tool/Bash", "detail": "Bash × 1" }
},
"unsupported": ["system_prompt", "memory", "files_attachments"],
"producedBy": "codex_runtime",
"providerBackend": "codepilot_proxy"
}断言通过: producedBy 'codex_runtime' + providerBackend 'codepilot_proxy'(v2 P1 命名规则)。entries.tools 来自 Codex RuntimeRunEvent command_started 路径(按 7.3 调研决议接入),同 ClaudeCode/Native 同源 collector。context_window: 258400 来自 Codex ThreadTokenUsage.modelContextWindow 上游 report(Codex SDK 唯一拿到 upstream window 的 Runtime)。
Phase 7 之前(DB row 487c190a72ce51e030e706ca7ab3cea8, 2026-05-20 12:03,session 2bbe8d0 历史消息)— 用户原始报告的 Widget message,实际 invoke 5 个 tool_use(Bash×2 + 2 MCP + Skill humanizer-zh),但 Phase 2-4 producer 在 streamClaude 起点跑、源是 badge selectedSkills,所以 context_accounting.entries 只有 {rules: 93} — 全部 Skills/MCP/Tools invocation 漏统计。
Phase 7 之后 同样输入(fixture 抽取 src/__tests__/fixtures/widget-message-tool-uses.json)跑 collectAutoInvokeSnapshot → 单元测试 auto-invoke-accounting.test.ts golden fixture round-trip 断言 entries.skills (humanizer-zh) + entries.mcp (codepilot-widget × 1, codepilot-memory × 1) + entries.tools (Bash × 2) 全部生效。
- 三 Runtime popover 都通过 CDP 截图验证(
docs/exec-plans/active/_smoke-evidence/fix-{native,codex}-popover*.png+phase7-claudecode-bash-1call-popover.png) - mini-bar 三 Runtime 都显示真实占用百分比(Codex 1.1% / Native 1.7% / ClaudeCode 接近 0.04% 触发场景仍有 1 dot 因 boost 规则)
- console clean except tech-debt #20(codex_account models 404 — 跟本计划无关历史 noise)
| 维度 | ClaudeCode | Native | Codex (codepilot_proxy) |
|---|---|---|---|
| producedBy | claude_code |
codepilot_runtime |
codex_runtime |
| providerBackend 透传 | — | — | codepilot_proxy ✓ |
| entries.rules (CLAUDE.md filesize) | ✅ | ✅ | ✅ |
| entries.tools (auto-invoke scan) | ✅ | ✅ | ✅ |
| entries.skills (Skill tool + badge merge) | ✅ | ✅ (机制相同,无 smoke session) | ✅ (同) |
| entries.mcp (mcp__ 前缀 split) | ✅ (fixture 验证) | ✅ (同 collector) | ✅ (同 collector) |
| unsupported 收编后 | [system_prompt, memory, files_attachments] |
同 | 同 |
| context_window 来源 | SDK upstream (200K) | catalog fallback (200K) | Codex SDK upstream (258K) |
三 Runtime user-facing 语义一致;source 不同但 contract 同 — Phase 7 抽象目标达成。