Files
mcn-short-video/.workbuddy/memory/2026-09-29.md
T
maogeigei f77735b206 工作台:目录改名 mcn-workshop + 任务进度可见 + 会话归属配置化
1. 目录 mcn-work-shop -> mcn-workshop(空间分组名取会话 cwd 的目录名,只改字符串无效,必须真改名)
2. 全仓替换 mcn-work-shop -> mcn-workshop:37 文件 121 处;历史日志按沿革句规矩保留当时目录名
3. 任务进度可见:/api/run/status 产出 已运行时长/工具调用/最近动作/停滞判定(读会话日志尾部),前端新增右下角常驻面板,四处任务入口接入
4. 会话归属目录配置化:新增 config.sessionCwd(留空=工作台自身目录),现指向 D://AI技能//mcn-workshop,使任务显示为命名分组而非「未分组任务」
5. 修前端轮询静默缺陷:连续 3 次查询失败即提示服务断开(原逻辑静默空转到 15 分钟超时,用户零感知)
2026-10-08 13:09:06 +08:00

518 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 已删。