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 分钟超时,用户零感知)
38 KiB
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 /runsbody 字段表:id✅type✅(message|action)payload.textsource.{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,全部一致):
POST /runs→ 202 acceptedGET /runs/{id}→ 恒active:true(30 分钟超时也不变)GET /runs/{id}/stream→ 连响应头都不返回(不是 404/超时,是挂住)GET /sessions/{id}/replay→ 只有user_message_chunk,无任何 assistant 事件;snapshot.busy=falseGET /stats/session→apiDuration:0、tokenUsageByModel:{}→ 一个 token 都没消耗,模型调用从未发出GET /workers、GET /metrics→ 超时(HTTP 层部分阻塞)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/totalTokensGET /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)
POST /api/v1/acp/connect(带 X-CodeBuddy-Request + Bearer)→{connectionId, sessionToken}- 之后所有请求必须带 header
acp-connection-id: <cid> - ⭐
Accept: application/json, text/event-stream必须同时含两者,否则 406"Not Acceptable: Client must accept both application/json and text/event-stream" POST /api/v1/acp— JSON-RPC 2.0:initializeparams{protocolVersion:1, clientInfo:{name,version}, clientCapabilities:{fs,terminal}}session/newparams{cwd: "<path>", mcpServers: []}← ⚠️ 字段名是 cwd,不是 workingDirectory(传错报Invalid input: expected string, received undefined)session/promptparams{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
- 响应是 SSE 流(
:ok心跳 +event: message+data: {jsonrpc...}) - 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 - 实测: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 接入方案本身完全可行:
--serveHTTP 层 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/iOAioa/国际版不设(最常漏,漏了就连错端点) - 优先级链实现(
getAuthenticationAttributesSync):envCODEBUDDY_AUTH_TOKEN→ACC_PRODUCT_CONFIG_V3/V2JSON →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、apiKeySourcetype:"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 即可出内容
剩余动作(待用户)
- 去 https://copilot.tencent.com/profile/ 拿 API Key
config.json:ai.backend→"cli",ai.cli.apiKey→ 你的 key- 自检
curl "http://localhost:8900/api/ai/status?probe=1" - (待定) 前端
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/iOAioa/国际版不设(最常漏,漏了就连错端点) - 优先级链实现(
getAuthenticationAttributesSync):envCODEBUDDY_AUTH_TOKEN→ACC_PRODUCT_CONFIG_V3/V2JSON →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、apiKeySourcetype:"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 即可出内容
剩余动作(待用户)
- 去 https://copilot.tencent.com/profile/ 拿 API Key
config.json:ai.backend→"cli",ai.cli.apiKey→ 你的 key- 自检
curl "http://localhost:8900/api/ai/status?probe=1" - (待定) 前端
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→ 400Custom 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硬顶。
⭐ 两个文本污染(必须处理)
- 报错当回答:未认证时 CLI 把报错走文本通道发出 →
isCliBoilerplate()拦截。 - 工具调用前的过程话术(本轮新发现):模型搜前先吐一句
I'll search for ... before responding.→ 被当正文显示。 修法:runCli记录每次tool_use的位置(toolMarks),只保留最后一次工具调用之后的文本; 因为这段已流式推给前端,server.js成功分支比对r.text !== acc时补发{replace}整体覆盖。 (前端data-pages.jsL653-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 后缀。
本轮验证方法(可复用)
测试脚本自检三件事,比只看耗时更有价值:
==OPTS==/==REQ==双标记是否齐全(格式合规,前端要靠它解析候选与需求清单)- 英文过程话术残留(正则扫整行纯英文 ≥25 字符的段落)
- 首字延迟 + 总耗时分开记(首字反映推理档位,总耗时反映输出+搜索)
当前最终配置
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-preview30 条 /deepseek-v4.1-flash6 条(我们这次会话是 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已删。