• 控制台原先只能看引擎的事件轨(stream.log / timeline.ndjson),里面是结构化事件、
    自由文本还被截断到 600 字;引擎更是根本不消费流式 chunk(onAcpEvent 只取
    session/result 的 usage)。所以 LLM 的逐字输出从来不在它的轨道上。
    
    新端点 GET /api/graph-runs/:id/nodes/:nodeRunId/transcript **惰性读** Claude CLI
    自己的会话文件:不进 SSE、不进节点行、不落第二份库 —— 那正是引擎当初决定不存全文的
    三条理由要避免的(输出无上限而 getRunState 每次轮询 SELECT * 全量回传 /
    产物已有 output_ref 这个唯一真相源 / fail_reason 是选边匹配键不能掺自由文本)。
    这里没有推翻那个设计,只是多开一个读取入口。
    
    目录推导与条目规范化抽到 server/services/agent-transcript.js,
    scripts/tail-agent.js 改为复用它 —— 一份实现两个入口。今天已经修过三次
    「两份必须保持一致的实现漂了」,不想制造第四次。
    
    ## 界面上明说的三个前提(不藏)
    
    1. transcript 按 (公司, 角色) 分、**不按节点分**,只能用节点 started_at/completed_at
       截时间窗口 —— 近似而非精确对齐,窗口边界原样显示。
    2. <env_status>(harness 每轮注入的环境块,几 KB 机器噪音)服务端剥掉,
       但**剥了几处如实显示**。静默删内容比留噪音更坏。
    3. 默认只看 agent 说的话;喂进去的内容(提示词全文、工具返回)要勾选才出来 ——
       提示词里有写入边界与公司上下文,不默认摊开,但可以看。
    
    subgraph 节点不给这个 tab(它自己不调 agent,活在子图里)—— 给了就是个空 tab,
    而空 tab 会被读成「没记录」。正在跑的节点靠「刷新」取最新,**不轮询**
    (整份会话几百 KB,跟着 run 状态轮询太贵)。
    
    ## 三处「看着对、实际错」都有用例钉住
    
    ① listRoles 的目录前缀差一个 '-':path.join 丢掉空的最后一段,切出来的角色名
       全带前导 '-'(实测 '-sdd-planning')。我是靠肉眼看输出发现的,所以补回归测试。
    ② 路由取本体取错层:本体绑在定义层节点上,graph_run_nodes 只有 def_node_id,
       第一版直接读运行时行 → SqliteError: no such column。
    ③ 找不到文件时回 ok:false + 期望目录,不回空列表 ——
       空列表会被读成「这个 agent 没说过话」,而真相是「我们没找到文件」。
    
    另外测试当场抓到我自己那个诚实机制的漏洞:整条正文都是 env_status 的条目会被剥空,
    normalize 早返回 null 导致它剥掉的那一处**没计入总数** —— 号称如实回报却少报。
    改成 parts 空但剥过东西时仍返回,由 readEntries 先记账再决定入不入列表。
    
    ## 验证
    
    880 全绿(88 文件)。3 处变异精确变红后逐字还原。
    运行中的实例上走查过:org-planner-sdd 的原文逐条带时间戳显示,
    第一条就是它去读上游 prd.md / contract.json —— R-19 那条链路被 agent 真的用上了。
    subgraph 节点(org-planner)确认没有这个 tab。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 控制台「运行日志」看不到 LLM 逐字输出,不是坏了,是引擎按设计不存:
    
    1. **引擎根本不消费流式 chunk**:defaultAgentExecutor 的 onAcpEvent 只处理
       session/result(累 usage)与 mock/files,文本 chunk 一个字都不接。
    2. **事件里的自由文本截断到 600 字**(EVENT_TEXT_MAX),graph-engine.js 那段注释
       写了三条理由:完整输出无上限而 getRunState 每次轮询 SELECT * 全量回传;
       完整产物已有地址(output_ref)不该有第二个真相源;fail_reason 是选边的匹配键
       不能掺自由文本。
    所以 stream.log / timeline.ndjson 里只有结构化事件 + 短摘要。
    
    但 Claude CLI 自己把完整会话写在
      ~/.claude/projects/<cwd 路径的 / 换成 ->/<sessionId>.jsonl
    而 agent 的 cwd 就是 data/companies/<公司>/agents/<角色>,目录名可推。
    这个脚本把那份 jsonl 渲染成可读的实时流:
    
      node scripts/tail-agent.js <公司>                  列出哪些角色有 transcript
      node scripts/tail-agent.js <公司> <角色>            跟看(增量轮询,Ctrl+C 退出)
      node scripts/tail-agent.js <公司> <角色> --last 40  只看最后 40 条
    
    只渲染 assistant 的 text 当「LLM 输出」:user 侧的 text 是喂进去的东西(提示词全文、
    每轮注入的 <env_status>),跟看时纯噪音 —— 第一版没滤,实测满屏 env_status。
    user 侧只留 tool_result(agent 到底读到了什么,这个有用)。
    
    在运行中的实例上验过:mycompany/sdd-planning 正在跑时列表、--last、跟看三种都对,
    角色名写错时明确报「目录不存在 + 先跑列表看有哪些」,不静默空跑。
    
    ponytail: projects 目录名按 CLI 现行的「/ → -」规则推导,规则变了会找不到目录
    (会明说,不会假装没输出)。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »