Files
mcn-short-video/.workbuddy/memory/2026-09-29.md
T
maogeigei 80072ff542 chore(repo): 归集 09-14~09-29 工作产出(技能/知识库/工作台/报告)并收敛临时产物
- skill: 素材库分块定义(00~11)、05_极致事件知识库 4 篇、08_对话风格总纲、mcn-video-prompt 提示词质量门禁与外部语料检索流程

- workbench: mcn-work-shop 新增 cli-backend.js(本地 CLI 接入)、工作台视觉交互规范;移除旧 UI 规范

- docs: 根目录极致事件/素材卡/审计方案报告与热门短视频清单入库

- memory: 补 09-14~09-29 日志与自动化任务记忆

- chore: .gitignore 排除 tools/、.tmp-chrome-*/、_k_test.cjs
2026-09-29 18:56:24 +08:00

371 lines
28 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 下)已全部分批删除。