Files
mcn-short-video/.workbuddy/memory/2026-09-29.md
T

517 lines
38 KiB
Markdown
Raw Normal View History

# 2026-09-29 工作日志
## 11:30 卡片回炉 + 素材库补全 · 批次 58/59/60(暂存模式)
### 本轮闭环(三批连续,全部 apply 成功)
| 批次 | 目标标签 | 来源视频 | 卡片 | 主标签分布 | apply 结果 |
|:--:|:--|:--|:--:|:--|:--|
| 58 | 自嘲反差 | 待补 | 15 | 自嘲反差为主 | 接受 15 / 退回 0 |
| 59 | 品质对比 | 37116 / 35227 / 19058 / 17484 / 28244 | 15 | 品质对比 5 / 反差 3 / 反常识 2 / 自嘲反差 2 / 细节专业 1 / 价值观冲击 2 | 接受 15 / 退回 0 |
| 60 | 氛围沉浸 | 22939 / 20432 / 18972 / 23321 / 23841 | 15 | 氛围沉浸 8 / 感官沉浸 2 / 反差 5 | 接受 15 / 退回 0 |
### 关键口径新增(本轮确立)
- **品质对比口径**:同品类两物「高下立判」的可视对比(劣质 vs 优质 / 廉价 vs 高配 / 旧 vs 新 / 常规 vs 超规格),落差须由**实物证据**呈现,**非修辞性夸赞**;非同类对比(代价对比、能力对比)→ 归 `价值观冲击` 或 `细节专业`。
- **氛围沉浸口径**(本轮补采时确立):**环境/氛围本身构成可感知的强包裹感**,观众被「拽进」某个时空或情境(夜色压迫 / 烟火滚烫 / 荒野孤绝 / 温度与声音的物理包裹)。判据两条:① 氛围**先于事件成立**;② **不依赖台词解释**。纯情绪激动(不靠环境的)→ 归 `感官沉浸` 或 `反差`。
- 已用子形态:火光破暗 / 伏击张力 / 隔窗压迫 / 圈层奇观 / 黑暗剥夺 / 门外压迫 / 深夜孤寂 / 车厢闷热。
### 技术要点复现(本轮再次验证)
- **apply-pending 的 list→dict 坑**:模型返回若为 list,`cmd_apply_pending` 的 `res.get(str(idx))` 会报 `AttributeError`。**解法**:先 `out={str(i):c for i,c in enumerate(d,1)}` 写 `out/polish_result/pending_00NN.json` 再 apply。本轮三批均先用此转换(批 60 模型直接返回 dict,同样按 str 键规整后写入)。
- **`build-pending <n>` 载荷路径**:载荷落在 `out/pending_tasks/batch_00NN.json`(含 `items[].{id,card_name,card,polish,lock,tag_name,std,sim,neg}`),**不产 `out/_bNN_payload.txt`**(早期批次才有该文件,勿据此判断失败)。
- **`_tmp_bNN_spec.py` 模式已稳定**(连续多批一次通过):从 md 正则抽 15 卡 → 内置 `Q` 字典手写问法 → `CARD_RE = r"```text\n(### [^\n]+\n.*?)\n```"`、`TAG_RE = r"事件标签[^:]*:\`([^\`(]+)(主)\`"` → 落盘 spec;断言 `len(cards)==15`、`len(order)==15`。
- **tempprompt 路径**:`C:\Users\maidou\AppData\Local\Temp\_prompt60.txt` / `_raw60.txt`。
- **告警判定**:批 60 的 15 条 fiction 告警**全部为第⑤类误报**(std 骨架「…」内的检索意图摘要短语被照搬进引号)→ 只告警不拦截,无需退回。
### 当前进度与缺口
- 暂存已积累 **17 批**(43–60,缺 53),卡片数约 255 张,均待 `13_import_pending.py` 统一入库。
- `5_tag_coverage.py` / `7_dispatch_next.py` **读 `state.json` 不反映暂存批** → 显示的仍是回炉基线数字(氛围沉浸 25、品质对比 19)。
- 缺口排序(跳难度极限):品质对比 81 / 预见式服务 81 / 氛围沉浸 75 / 感官沉浸 74 / 细节专业 68 / 自嘲反差 56 / 视觉冲击 33 / 价值观冲击 7。
- ⛔ **无候选**:`食材极致`(现有 8、候选 0)、`预见式服务`(现有 19、候选 2)→ 关键词捞不到,需重建候选池。
- 已取批 **61(品质对比)**:24819 / 28003 / 31772 / 28296 / 31813,待拉详情并产卡。
---
## 15:35 CLI 接入技术侦查结论(WorkBuddy CLI --serve + runs 空转)
### 目标
用户指令:放弃 Dify,改用 WorkBuddy CLI 调 `doubao-seed-2-1-pro` 执行 AI创作;对话框要流式输出。
### 已确认可用(踩坑记录)
- 入口:`C:/Users/maidou/AppData/Local/Programs/WorkBuddy/resources/app.asar.unpacked/cli/bin/codebuddy`
**无扩展名 node 脚本** → Python 必须 `[NODE_EXE, CLI, ...]`,直接 Popen 报 `WinError 193`。
- 启动:`--serve --port <P> --auth none --session-id <id> --permission-mode bypassPermissions --model <m>`
(`--auth none` 后仍需 Bearer;日志打印的 Password 就是 Bearer 值)。
- 官方文档:https://www.codebuddy.cn/docs/cli/http-api (中文)/ http://www.workbuddy.ai/docs/cli/http-api
- ⭐ **所有 `/api/v1/*` 请求必须带 `X-CodeBuddy-Request: 1`**(豁免:`/auth/login`、`/health`? 实测 health 401、`/api/openapi.json`、`/api/docs`)
- ⭐ **Bearer 值 = 启动打印的 Password**(不是 login 返回的 token,虽然实测两者相同)
- 文档给的 `POST /runs` body 字段表:`id`✅ `type`✅(`message`|`action`) `payload.text` `source.{platform,sender.id,conversation.id,conversation.type}` `timeoutMs`
- 错误码表:403=`Missing required header`(缺 X-CodeBuddy-Request),401=`AUTH_REQUIRED`
- 实测通过:`/api/v1/health` `/api/v1/info` `/api/v1/sessions` `/api/v1/stats/session` `/api/v1/sessions/live`
- 实测 `POST /api/v1/runs` → **202 `{"data":{"runId":...,"status":"accepted"}}`**(按官方 body 逐字复刻也成功)
### ⛔ 核心故障:run 被受理但 Agent 从不执行
证据链(三种模型 × 两种 body 变体 × 带/不带 X-CodeBuddy-Request,全部一致):
1. `POST /runs` → 202 accepted
2. `GET /runs/{id}` → **恒 `active:true`**(30 分钟超时也不变)
3. `GET /runs/{id}/stream` → **连响应头都不返回**(不是 404/超时,是挂住)
4. `GET /sessions/{id}/replay` → **只有 `user_message_chunk`,无任何 assistant 事件**;`snapshot.busy=false`
5. `GET /stats/session` → **`apiDuration:0`、`tokenUsageByModel:{}`** → 一个 token 都没消耗,模型调用从未发出
6. `GET /workers`、`GET /metrics` → **超时**(HTTP 层部分阻塞)
7. `codebuddy -p "..." --model glm-5.1` → **4 分钟超时,输出 0 字节**
结论:**Agent 运行时(agentManager / 模型客户端)在独立起的 `--serve` 进程里从未激活**。
代码线索(bundle `codebuddy-headless.js`):`AgentManager.init()` 里
`this.prewarmStateService?.isStandby() ? onActivated(startAgentBuild) : startAgentBuild()`
→ 怀疑独立 serve 的 prewarm 状态未激活导致 agent 永不 build。
### ✅ 排除的假设
- ❌ 不是企业模型问题:`glm-5.1`(官方模型)空转表现完全相同。
- ❌ 不是 `X-CodeBuddy-Request` 缺失:补上后行为不变(仍 202 + 空转)。
- ❌ 不是 body 格式:official schema 写法(`{id,type,source,payload}`)也有 400→最终 accepted。
- ❌ 不是端口/进程冲突:每次换新端口,服务确实起来且能响应。
- ❌ 不是 `--session-id` 缺失或 `--model` 缺失:都加过,无效。
### 有关 API 全景(从 bundle 提取,99 条)
`/api/v1/acp/connect` + `GET/POST /api/v1/acp`(有状态 JSON-RPC over SSE,是唯一自带完整对话能力的入口)
`/api/v1/jobs`(POST 派发独立 agent 实例,另一条执行路径,未测)
`/api/v1/daemon/{status,start,stop,restart}`、`/api/v1/workers`
`/api/v1/scheduled-tasks`、`/api/v1/plugins`、`/api/v1/settings`、`/api/v1/fs/*`、`/api/v1/pty`
### 本机环境事实(新增)
- **12134 端口 = WorkBuddy daemon(PID 33720)**,多路 ESTABLISHED,**正常常驻,禁止杀**。
- CLI 日志:`C:/Users/maidou/.codebuddy/logs/memwatch/cli-memwatch-<pid>.log`(活跃);`~/.codebuddy/logs/{date}/` 只有 8 月的(已废弃)。
- CLI 的 `PathUtils.getHomeDir()` → `C:/Users/maidou/.codebuddy`;Config 却读 `D:/.workbuddy/settings.json`。
- `--debug` 的日志目录 `~/.codebuddy/debug/` **不存在**(未生成)。
---
## 15:55 根因确认(重大):CLI 内部 IPC 端口 12134 被 WorkBuddy prewarm 池占用
### ⭐ 决定性证据
`codebuddy -p "你好" --debug` 首次抓到了被吞掉的真实异常:
```
Unhandled rejection Error: listen EADDRINUSE: address already in use 127.0.0.1:12134
at Server.setupListenHandle [as _listen2] (node:net:1940:16)
```
→ 12134 是 **CLI 自己的内部 IPC 端口**(每次 CLI 启动都要 listen)。
`netstat` 显示被 **PID 33720 独占 LISTENING**;Python 实测 bind 报 `WinError 10013`(权限不允许)。
### 占用者身份(PowerShell Get-CimInstance 实测)
```
PID=33720 PPID=21628 WorkBuddy.exe ...codebuddy --prewarm --prewarm-id wb-pool-1790566628102-f40096
PID=32328 PPID=21628 WorkBuddy.exe ...codebuddy --prewarm --prewarm-id wb-pool-1790579518962-fabb5b
```
→ **父进程 21628 = 用户正在运行的 WorkBuddy 桌面主程序**,这两个是它 spawn 的预热池。
→ `cbc-prewarm list` 返回 `No prewarm processes found`(不走 cbc-prewarm 注册表,是主程序直管,故无法用官方工具优雅停掉)。
### 完整根因链
```
WorkBuddy 主程序(21628) spawn prewarm 池(33720/32328) 占用 127.0.0.1:12134
↓ 手动起 codebuddy --serve / -p
EADDRINUSE 127.0.0.1:12134 → 内部 IPC 服务起不来
↓
HTTP 层照常工作(health/sessions/info/auth 全部正常响应,故极具迷惑性)
↓
Agent 运行时初始化失败 → span "cli" status:error / "Error in agent run"(耗时 1ms)
↓
POST /runs → 202 accepted 但恒 active:true、tokenUsage 全 0、无任何 assistant 输出
POST /jobs → state:working/tempo:active/alive:true/pid 但 detail 永停 "starting…"、transcript 空
ACP session/prompt → 有 requestId/traceId 但 stopReason: refusal
codebuddy -p → EADDRINUSE 后卡死,超时零输出
```
### ⭐ 第三条诊断利器:GET /api/v1/traces(新发现)
- `GET /api/v1/traces?limit=5` → trace 列表,含 status/errorCount/firstErrorMessage/firstErrorWhere/totalTokens
- `GET /api/v1/traces/{traceId}` → 完整 spans + bottleneck + errorSummary
- 实测关键 span:`mcp_tools` × 5 全 **ok**(39/32/14/7/7 ms),随后 `cli`(type:agent) **status:error, duration:1ms, error:"Error in agent run"**,`totalTokens:0`
- `terminalTitleGenerator`(auxiliary) 同时报同错 → 证明是**共性依赖故障**而非单 agent 问题
### ACP 通道完整协议(已实测跑通,代码来自 bundle DetachedSseBridge)
1. `POST /api/v1/acp/connect`(带 X-CodeBuddy-Request + Bearer)→ `{connectionId, sessionToken}`
2. 之后所有请求必须带 header **`acp-connection-id: <cid>`**
3. ⭐ **`Accept: application/json, text/event-stream` 必须同时含两者**,否则 406
`"Not Acceptable: Client must accept both application/json and text/event-stream"`
4. `POST /api/v1/acp` — JSON-RPC 2.0:
- `initialize` params `{protocolVersion:1, clientInfo:{name,version}, clientCapabilities:{fs,terminal}}`
- `session/new` params **`{cwd: "<path>", mcpServers: []}`** ← ⚠️ 字段名是 **cwd**,不是 workingDirectory(传错报 `Invalid input: expected string, received undefined`)
- `session/prompt` params `{sessionId, prompt:[{type:"text", text:"..."}]}`
- 其他方法:`authenticate`(methodId: iOA/external/internal/selfhosted)、`session/load`、`session/resume`、`session/cancel`、`session/set_model`、`session/set_mode`、`session/set_config_option`
5. 响应是 **SSE 流**(`:ok` 心跳 + `event: message` + `data: {jsonrpc...}`)
6. sessionUpdate 枚举:`agent_message_chunk`/`agent_thought_chunk`/`tool_call`/`tool_call_update`/`session_end`/`session_info_update`/`model_update`/`mode_update`/`current_mode_update`/`config_option_update`/`available_commands_update`/`plan`/`interruption_request`
7. 实测:initialize ✅、session/new ✅(拿到 sessionId + config_option_update 流)、session/prompt ✅(返回 `stopReason:refusal` + requestId + traceId,说明**协议链路完全正确**,只是底层 agent 因 12134 故障而拒答)
### POST /api/v1/jobs(B 通道,实测)
- 请求体:`{prompt, cwd, model, effort, permissionMode, agent, name, bash, sourceSessionId, bgIsolation}`
- 响应:`{data:{id, sessionId, state, tempo, detail, intent, cwd, kind, alive, settled, pid, webUrl}}`
- 实测:创建成功(`state:working`/`tempo:active`/`alive:true`/`pid:40664`),**但 detail 永停 `starting…`**、`transcript.updates:[]` 空 → **同样受 12134 故障影响**
- 查询:`GET /api/v1/jobs`、`GET /api/v1/jobs/{id}`、`GET /api/v1/jobs/{id}/transcript`、`GET /api/v1/jobs/{id}/stream`(SSE)
### 结论与待决策
- ✅ **CLI 接入方案本身完全可行**:`--serve` HTTP 层 99 条路由全部正常;ACP 协议链路(connect → initialize → session/new → prompt → SSE 流)已完整跑通。
- ⛔ **唯一阻塞 = 12134 被 WorkBuddy prewarm 池占用**。这是本机「WorkBuddy 桌面程序常开」与「手动调 CLI」共存的端口冲突。
- 待用户决策的解法(涉及杀掉用户正在运行的 WorkBuddy 子进程,必须先确认):
A. 临时 `Stop-Process -Id 33720,32328`(主程序会重建池;新开 WorkBuddy 会话慢几秒;不丢数据)→ 重跑 CLI 验证
B. 查 CLI 是否有可配置的内部 IPC 端口(bundle 里 12134 非硬编码,疑似运行时派生,暂未找到覆盖入口)
C. 改用 WorkBuddy 自带的 daemon 通道(12134 那个 server 本身)而非新起 CLI 进程
---
## 17:00-18:20 CLI 接入方案 · 根因修正与去留复盘
### ⛔ 推翻前序误判:「端口冲突 12134」是错的
- 12134 是 **WorkBuddy 宿主进程自己的 gateway 端口**(PID 33720 = prewarm 进程,`kind:"interactive"`,`isCurrent:true` = 我当前会话所在进程),**与 CLI 启动无关**。
- 该端口提供完整 CodeBuddy HTTP Server:`GET /` 返回「CodeBuddy Remote Control」页;带 `Authorization: Bearer <CODEBUDDY_GATEWAY_PASSWORD>` 后 `/api/v1/health`、`/api/v1/info`、`/api/v1/jobs`、`/api/v1/traces` 全部 200,`auth/status` = `{authEnabled:true, authenticated:true}`。
- 12134 不是硬编码:搜遍 `product*.json` / bundle 无此字面量。主 server 端口 = `parseInt(process.env.SERVER__PORT) || config.get("cell.server", {port:3000}).port`;Gateway 复用它(`endpointProvider.get()`)。
### ✅ 真实根因:独立启动的 CLI **没有登录态**
- 决定性证据(ACP `session/new` 返回):
`{"code":-32000,"message":"Authentication required","data":{"details":"Authentication required. Please use /login command to sign in to your account","category":"auth"}}`
`codebuddy.ai/outcome: "FAILED_MODEL_REQUEST"`
- 换 `--serve` 也一样:此时 `session/new` 直接报 `Authentication required`。
- **认证凭据不落盘**:全盘搜无 `.credentials.json`;`D:/.workbuddy/keyblob` 是加密 blob(`static-v1` + wrapped ciphertext)。凭据只由**宿主进程注入环境变量**传递:
`CODEBUDDY_GATEWAY_PASSWORD`、`CODEBUDDY_AUTH_TOKEN` / `CODEBUDDY_API_KEY`(`CustomTokenAuthenticationStorageImpl`:三者皆无 → `AuthenticationStoragePriority.Disabled`)。
- 产品配置 `ACC_PRODUCT_CONFIG_PATH` 指向 `%TEMP%\workbuddy-product-spill-*\acc-product-config-v3.json`,`authentication.type = "cli-external-link"`(需要走登录流程,非固定 key)。
### ⛔ 另一条误判:`--prewarm` 端口占用不是 named pipe
`resolvePrewarmIpcPath()` 在 win32 = `\\.\pipe\codebuddy-prewarm-<id>`(命名管道),**不是 TCP**。Prewarm IPC 只管「激活」,不占 TCP 端口。
### 🔑 bundle 分流(重要,之前踩过)
`bin/codebuddy` launcher 按环境变量选 bundle:
- `CODEBUDDY_FORCE_LITE_WB_BUNDLE=1` → `codebuddy-lite-wb.mjs`(**WorkBuddy 特供精简包**,宿主 spawn sidecar/prewarm 时注入;优先级最高)
- `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` → `codebuddy-headless.js`(官方 headless)
- 含 `--print` → 自动 headless
- 均缺失 → `dist/codebuddy`(full TUI bundle,**该产物在本安装里不存在** → `Cannot find module '../dist/codebuddy'`)
→ 自己测 CLI 时必须显式设 `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1`,否则会误用 wb 特供包或报模块找不到。
### ⛔⛔ 决定去留的硬约束:宿主 gateway 不可被外部发现
| 事实 | 实测 |
|:--|:--|
| 端口**每轮轮换** | 12134 → 13116 → 10261(同一台机、同一天内) |
| 密码**每轮轮换** | `gcoMpIuQ…` → `xJzG3hgI…` |
| 无落盘注册表 | `D:/.workbuddy/` 下无 worker/registry/daemon 文件;`cbc-prewarm list` = `No prewarm processes found` |
| 唯一发现通道 | 环境变量 `CODEBUDDY_SERVICE_PROXY_URL=http://127.0.0.1:<port>/internal/hooks/services/invoke` |
| 该变量的可见范围 | **仅 WorkBuddy 派生的进程树内**(我当前 Bash 里可见;用户双击 start.bat 起的进程**看不到**) |
| ACP 会话语义 | 宿主 gateway `session/new` 返回**当前活跃会话 ID**(非新会话)→ prompt 自会话阻塞 |
### 三条路实测结论
| 方案 | 结论 |
|:--|:--|
| A 独立起 CLI(`--serve` / `-p` / ACP) | ❌ 缺登录态,`FAILED_MODEL_REQUEST` |
| B 复用宿主 gateway | ⚠️ 协议层全通(connect/initialize/session/new 均 ✅,认证 ✅),但**端口+密码随机轮换且不可从外部发现** → 工作台(用户手动启动)拿不到 |
| C 官方非交互 key | ✅ 存在 `CODEBUDDY_API_KEY` 环境变量通道(bundle 内 `eT="CODEBUDDY_API_KEY"`),与桌面程序完全解耦 —— **唯一无耦合的官方路径,待用户提供 key** |
### 环境事实(本轮)
- 沙箱会**回收后台服务**:`run_in_background` 起的 serve 在任务结束即死;测试须前台 + `timeout`,或单次脚本内自包含。
- 宿主重启会使 App PID 变化(20760 → 22976),gateway 随之换端口。
- 清理:`_start_serve*.py`/`_probe*.py`/`_test*.py`/`_acp_*.py`/`_clean_*.py`/`_keep_auth.py` 等 30+ 临时脚本 + `_*.log/_*.txt` 待删。
---
## 18:20-18:45 A 方案落地:工作台接入本地 CodeBuddy CLI(stdio 直连)
### 决议
用户拍板走 **A 方案**(官方 `CODEBUDDY_API_KEY` 非交互认证),不再挖宿主 gateway(B/D 路已证不可靠)。
### ⭐ 官方认证矩阵(来源 codebuddy.ai/docs/cli/iam,是权威)
| 场景 | 环境变量 | 获取地址 |
|:--|:--|:--|
| 个人开发者 | `CODEBUDDY_API_KEY` | 中国版 https://copilot.tencent.com/profile/ / 国际版 https://www.codebuddy.ai/profile/keys |
| 已有 OAuth token | `CODEBUDDY_AUTH_TOKEN` | 直接填 |
| 企业 OAuth | `apiKeyHelper`(settings.json) | 建应用拿 Client ID/Secret |
- **优先级**:`CODEBUDDY_AUTH_TOKEN` > `apiKeyHelper` > `CODEBUDDY_API_KEY`
- **⚠️ 必配** `CODEBUDDY_INTERNET_ENVIRONMENT`:中国版 `internal`/iOA `ioa`/国际版不设(**最常漏,漏了就连错端点**)
- 优先级链实现(`getAuthenticationAttributesSync`):env `CODEBUDDY_AUTH_TOKEN` → `ACC_PRODUCT_CONFIG_V3/V2` JSON → **`settings.json` 的 `env` 字段** → `ACC_PRODUCT_CONFIG_PATH` 文件
- **可落盘**:`settings.json` 的 `env.CODEBUDDY_AUTH_TOKEN` 是官方支持的持久化位置
- ⛔ 官方 HTTP API 文档(docs/cli/http-api)**没有** API Key 机制 —— 那是网关密码认证,别搞混
### ⭐⭐ 本次最大技术收获:CLI 端口冲突的真正机理
**`SERVER__PORT` 环境变量继承导致 EADDRINUSE。**
- `codebuddy`(**连 `-p` 打印模式也**)内部会起 HTTP server:`listen(parseInt(process.env.SERVER__PORT) || config.get("cell.server",{port:3000}).port)`
- 在 WorkBuddy 进程树内启动 → 继承宿主的 `SERVER__PORT` → 去 listen 宿主已占端口 → `EADDRINUSE` → **静默卡死**
- **迷惑性极强**:HTTP 层(health/info/sessions/auth)全正常,只有 agent 执行环节挂 → 极易误判成「prewarm 占端口」
- **官方自己也这么处理**:CLI 派生子进程时 `delete el.SERVER__PORT, delete el.SERVER__HOST`
- 验证:删掉这两个变量后,从「卡死 110s」→「秒退 RC=0」
### ⭐ bundle 分流坑(`bin/codebuddy` launcher)
| 环境变量 | 命中产物 |
|:--|:--|
| `CODEBUDDY_FORCE_LITE_WB_BUNDLE=1`(宿主注入,**优先级最高**) | `codebuddy-lite-wb.mjs`(WorkBuddy 特供精简包) |
| `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` | `codebuddy-headless.js`(官方 headless,**该用的**) |
| 含 `--print` | 自动 headless |
| 都没有 | `dist/codebuddy` → **本安装不存在**,报 `Cannot find module '../dist/codebuddy'` |
### ⭐ 宿主 gateway 不可用(结论钉死,别再挖)
| 事实 | 实测 |
|:--|:--|
| 端口**每轮轮换** | 12134 → 13116 → 10261 |
| 密码**每轮轮换** | `gcoMpIuQ…` → `xJzG3hgI…` |
| 无落盘注册表 | `D:/.workbuddy/` 无 worker/registry/daemon 文件 |
| 唯一发现通道 | 环境变量 `CODEBUDDY_SERVICE_PROXY_URL`,**仅 WorkBuddy 派生进程树内可见** |
| 凭据保护是**故意设计** | 宿主用 4 个环境变量交付凭据,CLI 读后**立即 delete**;凭据不落明文盘(`keyblob` 是加密 blob) |
### 交付物
| 文件 | 说明 |
|:--|:--|
| `mcn-work-shop/cli-backend.js` | **新增**。`runCli()`:spawn CLI + stream-json 解析 + 文本增量回调;`buildChildEnv()` 含三条防护 |
| `mcn-work-shop/server.js` | 新增 `/api/ai/status`(自检,`?probe=1` 真跑);`/api/ai/clarify` 加 CLI 分支;`aiBackend()` 支持 `MCN_AI_BACKEND` 覆盖 |
| `mcn-work-shop/load-config.js` | `ai` 配置块 + **深合并**(浅合并会把 cli 子字段整体顶掉) |
| `mcn-work-shop/config.json` | 新增 `ai` 段(`backend` 暂留 `dify` 保证不回归) |
| `mcn-work-shop/docs/AI链路-本地CLI接入.md` | **权威文档**:配置步骤/字段说明/三条防护/输出协议/排障速查 |
### stream-json 协议(`--output-format stream-json --include-partial-messages`)
- `type:"system" subtype:"init"` → `session_id`、`apiKeySource`
- `type:"stream_event"` → `event.content_block_delta` → `delta.type==="text_delta"` → `delta.text`(增量)
- `type:"assistant"` → 无增量流时的整段兜底
- `type:"result"` → 终态;**`is_error:true` 时真实原因在 `errors[]`**,正文作废
- ⚠️ 未认证时 CLI 把报错**当 assistant 文本发出来** → 需 `isCliBoilerplate()` 拦截,否则英文报错会显示成"AI 的回答"
### 测试证据(端到端,均已通过)
- `/api/ai/status` → `backend:cli`、`cli.found:true`、`resolved.kind:"bundled"`、`credential:"(未配置)"`、`ready:false`
- `/api/ai/status?probe=1` → 真跑 CLI,11s 返回可操作中文错误(含 `sessionId`)
- `/api/ai/clarify`(stream) → SSE 正常发 `{error}` + `[DONE]`,**不卡死**
- ⇒ **除凭据外全链路已验证**;填 key 即可出内容
### 剩余动作(待用户)
1. 去 https://copilot.tencent.com/profile/ 拿 API Key
2. `config.json`:`ai.backend` → `"cli"`,`ai.cli.apiKey` → 你的 key
3. 自检 `curl "http://localhost:8900/api/ai/status?probe=1"`
4. **(待定)** 前端 `data-pages.js` 的 `sendTurn` 是否需适配(SSE 协议未变,理论上零改动,待实测)
### 清理
本轮 91 个临时文件(`_*.py`/`_*.log`/`_*.txt`/`_debug/` 等,均在根目录与 mcn-work-shop 下)已全部分批删除。
---
## 18:20-18:45 A 方案落地:工作台接入本地 CodeBuddy CLI(stdio 直连)
### 决议
用户拍板走 **A 方案**(官方 `CODEBUDDY_API_KEY` 非交互认证),不再挖宿主 gateway(B/D 路已证不可靠)。
### ⭐ 官方认证矩阵(来源 codebuddy.ai/docs/cli/iam,是权威)
| 场景 | 环境变量 | 获取地址 |
|:--|:--|:--|
| 个人开发者 | `CODEBUDDY_API_KEY` | 中国版 https://copilot.tencent.com/profile/ / 国际版 https://www.codebuddy.ai/profile/keys |
| 已有 OAuth token | `CODEBUDDY_AUTH_TOKEN` | 直接填 |
| 企业 OAuth | `apiKeyHelper`(settings.json) | 建应用拿 Client ID/Secret |
- **优先级**:`CODEBUDDY_AUTH_TOKEN` > `apiKeyHelper` > `CODEBUDDY_API_KEY`
- **⚠️ 必配** `CODEBUDDY_INTERNET_ENVIRONMENT`:中国版 `internal`/iOA `ioa`/国际版不设(**最常漏,漏了就连错端点**)
- 优先级链实现(`getAuthenticationAttributesSync`):env `CODEBUDDY_AUTH_TOKEN` → `ACC_PRODUCT_CONFIG_V3/V2` JSON → **`settings.json` 的 `env` 字段** → `ACC_PRODUCT_CONFIG_PATH` 文件
- **可落盘**:`settings.json` 的 `env.CODEBUDDY_AUTH_TOKEN` 是官方支持的持久化位置
- ⛔ 官方 HTTP API 文档(docs/cli/http-api)**没有** API Key 机制 —— 那是网关密码认证,别搞混
### ⭐⭐ 本次最大技术收获:CLI 端口冲突的真正机理
**`SERVER__PORT` 环境变量继承导致 EADDRINUSE。**
- `codebuddy`(**连 `-p` 打印模式也**)内部会起 HTTP server:`listen(parseInt(process.env.SERVER__PORT) || config.get("cell.server",{port:3000}).port)`
- 在 WorkBuddy 进程树内启动 → 继承宿主的 `SERVER__PORT` → 去 listen 宿主已占端口 → `EADDRINUSE` → **静默卡死**
- **迷惑性极强**:HTTP 层(health/info/sessions/auth)全正常,只有 agent 执行环节挂 → 极易误判成「prewarm 占端口」
- **官方自己也这么处理**:CLI 派生子进程时 `delete el.SERVER__PORT, delete el.SERVER__HOST`
- 验证:删掉这两个变量后,从「卡死 110s」→「秒退 RC=0」
### ⭐ bundle 分流坑(`bin/codebuddy` launcher)
| 环境变量 | 命中产物 |
|:--|:--|
| `CODEBUDDY_FORCE_LITE_WB_BUNDLE=1`(宿主注入,**优先级最高**) | `codebuddy-lite-wb.mjs`(WorkBuddy 特供精简包) |
| `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` | `codebuddy-headless.js`(官方 headless,**该用的**) |
| 含 `--print` | 自动 headless |
| 都没有 | `dist/codebuddy` → **本安装不存在**,报 `Cannot find module '../dist/codebuddy'` |
### ⭐ 宿主 gateway 不可用(结论钉死,别再挖)
| 事实 | 实测 |
|:--|:--|
| 端口**每轮轮换** | 12134 → 13116 → 10261 |
| 密码**每轮轮换** | `gcoMpIuQ…` → `xJzG3hgI…` |
| 无落盘注册表 | `D:/.workbuddy/` 无 worker/registry/daemon 文件 |
| 唯一发现通道 | 环境变量 `CODEBUDDY_SERVICE_PROXY_URL`,**仅 WorkBuddy 派生进程树内可见** |
| 凭据保护是**故意设计** | 宿主用 4 个环境变量交付凭据,CLI 读后**立即 delete**;凭据不落明文盘(`keyblob` 是加密 blob) |
### 交付物
| 文件 | 说明 |
|:--|:--|
| `mcn-work-shop/cli-backend.js` | **新增**。`runCli()`:spawn CLI + stream-json 解析 + 文本增量回调;`buildChildEnv()` 含三条防护 |
| `mcn-work-shop/server.js` | 新增 `/api/ai/status`(自检,`?probe=1` 真跑);`/api/ai/clarify` 加 CLI 分支;`aiBackend()` 支持 `MCN_AI_BACKEND` 覆盖 |
| `mcn-work-shop/load-config.js` | `ai` 配置块 + **深合并**(浅合并会把 cli 子字段整体顶掉) |
| `mcn-work-shop/config.json` | 新增 `ai` 段(`backend` 暂留 `dify` 保证不回归) |
| `mcn-work-shop/docs/AI链路-本地CLI接入.md` | **权威文档**:配置步骤/字段说明/三条防护/输出协议/排障速查 |
### stream-json 协议(`--output-format stream-json --include-partial-messages`)
- `type:"system" subtype:"init"` → `session_id`、`apiKeySource`
- `type:"stream_event"` → `event.content_block_delta` → `delta.type==="text_delta"` → `delta.text`(增量)
- `type:"assistant"` → 无增量流时的整段兜底
- `type:"result"` → 终态;**`is_error:true` 时真实原因在 `errors[]`**,正文作废
- ⚠️ 未认证时 CLI 把报错**当 assistant 文本发出来** → 需 `isCliBoilerplate()` 拦截,否则英文报错会显示成"AI 的回答"
### 测试证据(端到端,均已通过)
- `/api/ai/status` → `backend:cli`、`cli.found:true`、`resolved.kind:"bundled"`、`credential:"(未配置)"`、`ready:false`
- `/api/ai/status?probe=1` → 真跑 CLI,11s 返回可操作中文错误(含 `sessionId`)
- `/api/ai/clarify`(stream) → SSE 正常发 `{error}` + `[DONE]`,**不卡死**
- ⇒ **除凭据外全链路已验证**;填 key 即可出内容
### 剩余动作(待用户)
1. 去 https://copilot.tencent.com/profile/ 拿 API Key
2. `config.json`:`ai.backend` → `"cli"`,`ai.cli.apiKey` → 你的 key
3. 自检 `curl "http://localhost:8900/api/ai/status?probe=1"`
4. **(待定)** 前端 `data-pages.js` 的 `sendTurn` 是否需适配(SSE 协议未变,理论上零改动,待实测)
### 清理
本轮 91 个临时文件(`_*.py`/`_*.log`/`_*.txt`/`_debug/` 等,均在根目录与 mcn-work-shop 下)已全部分批删除。
---
## 18:45-19:30 A 方案落地完成 + 联网搜索接入(实测全通过)
### ✅ 结果:工作台 AI 链路已切到本地 CLI,可用
用户提供 API Key 后,端到端全部跑通:
`/api/ai/status` → ready:true / `?probe=1` → ok:true / `/api/ai/clarify`(SSE) → 正常出内容且格式合规(`==OPTS==/==REQ==` 完整)。
### 🔐 凭据存放(重要,防泄密)
- **`mcn-work-shop/config.json` 是 git 跟踪文件** → ⛔ 绝不能把 key 写进去。
- 凭据落在**仓库之外**:`%USERPROFILE%\.workbuddy\mcn-work-shop\ai-cli.json`(`{"apiKey":"..."}`)。
- 解析优先级:`config.apiKey/authToken` > **凭据文件** > 环境变量 `CODEBUDDY_API_KEY/AUTH_TOKEN`。
- `server.js` 新增 `cliBaseOpts()` 统一构造调用参数;新增环境变量覆盖开关 **`MCN_AI_MODEL`**(不改配置做模型 A/B)。
### ⭐⭐ doubao 不可用(钉死结论)
- `custom:doubao-seed-2-1-pro-260628` → **500**;`custom:doubao-seed-2-0-lite-260428` → **400 `Custom model [...] service info not found`**。
- CLI `--help` 的模型列表是**二进制静态清单,≠ 已开通**。桌面产品配置 48 个模型里没 doubao;`workbuddy.db` 历史只用过 `deepseek-v4.1-flash`/`hy4-preview`。
- **但 `custom:` 系并非全不可用** —— 同租户下有一大批已开通的自定义槽位。
### ⭐⭐ `custom:custom-model-*` 槽位 → 真实模型名映射(实测逐个跑出)
| 槽位 | 真名 |
|:--|:--|
| `a1` / `a2` / `a3` / `a4` | Claude-Opus-4.7 / Sonnet-4.6 / Opus-4.6 / **Opus-4.8** |
| `b1-standard` / `b1-priority` | GPT-5.4-Standard / GPT-5.4-Priority |
| `b3-priority` | GPT-5.2-Priority |
| `b4-standard` / `b4-priority` | GPT-5.1-Standard / GPT-5.1-Priority |
| `b5-standard` / `b5-priority` | **GPT-5.5-Standard / GPT-5.5-Priority** |
| `c2-flash` | Gemini-3.5-Flash-1 |
> **取证方法**(可复用):CLI 日志 `%TEMP%\mcn-cli-home\logs\<日期>\*.log` 里会有
> `"requestModelId":"custom:...","requestModelName":"Claude-Opus-4.8"` 配对 → 正则一次提取全部映射。
> 直接问模型「你是谁」不可靠(a1~a4 会统一答「我是AI智能编程助手」)。
### ⭐⭐ 三个性能杠杆(实测数据,都已在代码里落地)
| 杠杆 | 做法 | 效果 |
|:--|:--|:--|
| **工具面收敛** | `--tools "WebSearch,WebFetch"` | prompt **21,776 → 4,859 tokens(-78%)**,**47.6s → 15.0s** |
| **推理档位** | `--effort low`(新增,`minimal\|low\|medium\|high\|xhigh\|max`) | 首字 **61.7s → 19.7s** |
| **换模型** | `deepseek-v4-pro` 46.2s → `custom:custom-model-a4` 18.5s | 快 2.5 倍 |
- ⚠️ **`[INIT tools]` 事件列的是工具注册表全量,不是启用集** —— 判断 `--tools` 是否生效要看 `usage.input_tokens`,别被事件列表误导(本轮差点误判成"白名单没生效")。
- 附带安全收益:工具面收敛后模型**物理上碰不到文件系统**(Bash/Read/Write/Edit 全不在白名单)。
### ⭐ 联网搜索:CLI 原生自带,开箱可用
- `--tools` 里保留 `WebSearch,WebFetch` 即可;实测真的发起 `WebSearch {"query":...,"freshness":"d1"}`。
- **但不能常开也不能常关** → 用 `CLI_SEARCH_HINT` 作 `--append-system-prompt` 注入判据:
该搜(今天/近期的外部事实、热点、平台规则、事实核查)/不该搜(创作讨论、需求澄清、脚本撰写)/
**搜索预算硬上限 2 次**(1 WebSearch + 必要 1 WebFetch),搜到即答禁止反复搜。
- 教训:第一版提示词只写"最多搜 2 轮",模型实际连搜 4 次把耗时推到 3 分钟 → 改成显式预算 + `maxTurns: 6` 硬顶。
### ⭐ 两个文本污染(必须处理)
1. **报错当回答**:未认证时 CLI 把报错走文本通道发出 → `isCliBoilerplate()` 拦截。
2. **工具调用前的过程话术**(本轮新发现):模型搜前先吐一句
`I'll search for ... before responding.` → 被当正文显示。
**修法**:`runCli` 记录每次 `tool_use` 的位置(`toolMarks`),**只保留最后一次工具调用之后的文本**;
因为这段已流式推给前端,`server.js` 成功分支比对 `r.text !== acc` 时补发 `{replace}` 整体覆盖。
(前端 `data-pages.js` L653-672 对未知事件类型是 `continue` 安全跳过 → 加事件类型不破坏兼容。)
### ⭐ 本轮新识别并清掉的宿主干扰
- **宿主 MCP 注入**:`CODEBUDDY_MCP_CONFIG` 会在子 CLI 里自动挂上 `weixinpay` + `sheetagent` 两个 MCP,
纯浪费启动时间还可能被误调 → `buildChildEnv` 删该变量 + 传 `--strict-mcp-config --mcp-config '{"mcpServers":{}}'`。
实测 `[INIT mcp]` 从 2 个变成 `[]`。
- 另删 `CODEBUDDY_GATEWAY_PASSWORD`/`CODEBUDDY_GATEWAY_AUTH`/`WORKBUDDY_PAC_RPC_*`。
### 当前配置
`config.json → ai`:`backend:"cli"`,`model:"custom:custom-model-a4"`(Claude-Opus-4.8),
`effort:"low"`,`search:true`,`tools:""`,`strictMcp:true`,`maxTurns:6`。
### 遗留 / 待办
- **需求打磨单轮耗时仍 60-120s**(首字已降到 ~20s)。总耗时主要在模型推理+输出,属模型特性;若要更快可再换 `b5-standard`(GPT-5.5) 或加 `effort:minimal`。
- **前端 `sendTurn` 未实测**(本轮只测了后端 SSE;协议未变理论零改动)。
- 真实账号 + 参考选题走**完整 AI 创作链路**(S1-S11)仍未验证。
- `flash`/`minimal` 档位对产出质量的影响未评估。
---
## 19:32-19:40 模型切换:GPT-5.5(用户拍板)
用户选定 **GPT-5.5**,配置已改为 `custom:custom-model-b5-standard`。
### 实测对比(端到端 `/api/ai/clarify`,同一测试用例)
| 模型 | 普通创作问答 | 问热点(带搜索) | 格式合规 |
|:--|:--|:--|:--|
| **GPT-5.5-Standard**(当前) | **42.2s** | 137.2s | ✅ |
| Claude-Opus-4.8 | 105.5s | 139.6s | ✅ |
| DeepSeek-V4-Pro | 119.5s | 151.2s | ✅ |
- **普通问答快 2.5 倍**(42.2s vs 105.5s)—— 这是最常用的路径,收益最大。
- **触发搜索后各模型都是 130-150s** → 瓶颈转移到搜索本身,不是模型。
∴ `CLI_SEARCH_HINT` 把搜索预算压到 ≤2 次是对的,这是搜索场景唯一有效的提速手段。
- 首字延迟:普通问答 ~20s;搜索场景 ~67s(因为叙述被切掉了,搜索期间用户只看到"正在思考"占位)。
### b5 两个变体的区别
`custom:custom-model-b5-standard`(标准档,已选)/`custom:custom-model-b5-priority`(优先档,
资源紧张时优先路由,通常积分倍率更高)。b1/b3/b4 同族也是这套 -standard/-priority 后缀。
### 本轮验证方法(可复用)
测试脚本自检三件事,比只看耗时更有价值:
1. `==OPTS==` / `==REQ==` 双标记是否齐全(格式合规,前端要靠它解析候选与需求清单)
2. 英文过程话术残留(正则扫整行纯英文 ≥25 字符的段落)
3. 首字延迟 + 总耗时分开记(首字反映推理档位,总耗时反映输出+搜索)
### 当前最终配置
`config.json → ai`:`backend:"cli"`,`model:"custom:custom-model-b5-standard"`,
`effort:"low"`,`search:true`,`tools:""`,`strictMcp:true`,`maxTurns:6`;
凭据在 `%USERPROFILE%\.workbuddy\mcn-work-shop\ai-cli.json`。
---
## 19:40-19:50 切换 deepseek-v4.1-flash + 全流程跑通验证
用户要求「改为用 deepseek v4.1 flash 把流程跑通」。配置已改 `custom:custom-model-b5-standard` → **`deepseek-v4.1-flash`**。
### ✅ 全流程四环节实测全绿
| 环节 | 结果 |
|:--|:--|
| `GET /api/ai/status` | backend=cli · model=deepseek-v4.1-flash · ready=**true** |
| `?probe=1` | ok=**true** · 17.0s · 返回「成功」 |
| `POST /api/ai/clarify`(stream) | 371 字 · `==OPTS==/==REQ==` 合规 ✅ · 无英文残留 ✅ · 51.7s(首字 49.7s) |
| `POST /api/run` → 轮询 | queued(5s) → running(30s) → **done**(50s) · `model_id` 落库 = deepseek-v4.1-flash ✅ |
### ⭐⭐ 关键架构发现:完整流程 = 两段,模型来源互不相干
| 环节 | 接口 | 谁在跑 | 模型来源 |
|:--|:--|:--|:--|
| 需求打磨(弹窗对话) | `POST /api/ai/clarify` | **本地 CLI**(本方案) | `config.json → ai.cli.model` |
| 提交创作任务(S1-S11 生成脚本) | `POST /api/run` | **WorkBuddy 宿主**(写 automation,桌面端调度器执行) | **`sessions` 表最近活跃「手动会话」的 `model`**,兜底 `auto` |
- 🔴 **改 `ai.cli.model` 不影响创作任务的模型。**
- 创作任务模型是「跟随用户在 WorkBuddy UI 最近选过的模型」→ 从工作台侧看是**浮动的、不确定的**。
- 取证 SQL:`SELECT model_id, COUNT(*) FROM automations GROUP BY model_id ORDER BY 2 DESC;`
本机实测分布:`hy4-preview` **30** 条 / `deepseek-v4.1-flash` **6** 条(我们这次会话是 flash,所以新任务落到 flash)。
- **想让创作固定用某模型**:在 UI 把当前会话模型切到它;或改造 `/api/run` 支持显式 `model_id`(属行为变更,未做)。
### `deepseek-v4.1-flash` 的实测速度(澄清一个反直觉点)
- 首字 **49.7s**,总 51.7s;对比 GPT-5.5 首字 20.0s/总 42.2s。
- **"flash" 不等于首字更快** —— 它仍是推理型模型;但总耗时接近。选它主要看积分成本。
- 三个模型(deepseek-v4.1-flash / GPT-5.5 / Opus-4.8)**格式合规都 ✅、都无英文残留**,质量层面可用性一致。
### 顺带确认的死代码
- **`POST /api/ai/topics` 是遗留死代码** —— 前端从不调用(选题列表走 `GET /api/dsh/topics`)。
它仍写死 Dify,未配 `DIFY_MCN_CYLG_KEY` 会 500,但**不影响真实流程**。已记入文档 §11。
### 清理
- 自检 automation `automation-1790682160345`(名为「链路自检·deepseek-v4.1-flash(可删除)」)
已用 `automation_update mode=delete` 走正规通道删除(未用 sqlite 直改)。
- 临时脚本 `_t_flash.py` 已删。