• 承接 310a2b2 的两个遗留:
    
    ① 四类被错标成心跳
       company-heartbeat-service._sendAgentChat 给五类调用(心跳巡检 / Pipeline 步骤 /
       日程 / 告警 / 日报)无差别打 isHeartbeat:true,于是对话列表里全都显示成
       「⏱️ 自主心跳巡检」、meta.type 一律 'heartbeat'。改成显式传 kind,五类各自标注。
       isHeartbeat 仍然发,但只对真心跳为真,老消费方行为不变。
    
    ② 310a2b2 让 Pipeline 重试变成了冷启动
       Pipeline 步骤的 convId 是 `pipeline-{runId}-step-{i}`,被驳回重做时**复用同一个**。
       310a2b2 按 isHeartbeat 一刀切回收,等于每次重试都要走 buildPrompt 场景 C 重注入历史。
       现在回收策略挂在 CALL_KINDS 表上,判据只有一条:**convId 会不会被下一轮复用** ——
       会复用的(pipeline / 人类对话)留着走 TTL,不复用的(心跳 / 日程 / 告警 / 日报)跑完即回收。
       不按「是不是后台调用」分:五类后台调用里 pipeline 就是要留的那一类。
    
    守卫补到 12 条(原 3 + kind 标注 5 + 回收策略 4)。对 310a2b2 跑过:8 红 4 绿。
    convId 一律用固定字面量而非拼时间戳 —— 撞上 test-id-stability 那道防线后按它的判据改的,
    固定之后第二次跑还顺带走了「复用已有对话」那条分支。
    
    文档:
      - CLAUDE.md 注意事项加一条(新增会话创建路径时必须同时定回收路径,附症状与守卫位置)
      - 28-followups 守卫清单补第五条,并把「前四条守单一持有者」与第五条「守资源生命周期」
        的形状差异写明;顺手修掉表格已有 4 行、结尾却仍写「这三条」的失配
      - .env.example 补 AGENT_SESSION_IDLE_MS
    
    全量单测 667/667 绿。重启后 /api/health 正常,心跳照常挂载。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »

  • ## 问题
    
    server/middleware/jwt-auth.js 的兜底密钥 'lemon-dev-secret-change-in-production'
    提交在仓库里,而 .env.example **没有 JWT_SECRET 这一项** —— npm run setup 就是
    从 .env.example 生成 .env(CLAUDE.md「快速启动」),所以照文档装出来的部署
    必然用这个公开密钥签会话,且启动时零告警。
    
    危害不止「密钥弱」。extractUser 拿 decoded.userId 回库重查用户
    (所以伪造 role:'admin' 这条路本身是堵住的,这个设计是对的),但
    user-db 的 id 是自增小整数、不是 uuid:拿已知密钥签 {userId: N}、从 1 试起
    就能命中真实账号,拿到的是该账号在库里的真实 role。命中 admin 就是
    requireAdmin 守着的整个 /api/users/* 全部敞开。不需要密码、不需要 OAuth,
    只要读过这个仓库。
    
    本机 .env 有 64 字符密钥,未受影响;受影响的是默认部署路径。
    
    ## 三处改动
    
    1. .env.example 补 JWT_SECRET(含 openssl rand -hex 32 生成命令与危害说明)
    2. jwt-auth.js:NODE_ENV=production 且未设 JWT_SECRET 时**拒绝启动**
       (签名密钥缺失不是可以边跑边补的事);开发环境保留兜底但每次起进程吼一次。
       三种情形都实测过:生产无密钥→抛错拒启;生产有密钥→正常且不告警;开发无密钥→放行+告警。
    3. 新增 guard server/tests/unit/env-example-secrets.test.js:
       「安全类 env + **非空**硬编码兜底」必须在 .env.example 里有一行。
       只管非空兜底是刻意的 —— 空串兜底(FEISHU_ENCRYPT_KEY、OPEN_API_KEYS)是
       「未配置」的正当写法,缺了 fail closed;把它们也算进来只会制造噪音,
       然后被加白名单,然后白名单腐烂。危险的是「缺了也能跑、还跑得像配好了」那一类。
    
    ## 顺带修掉一个能把全新克隆直接搞坏的
    
    .env.example 写 PORT=3000,而 config.js 默认 3010、web/vite.config.js 的代理
    目标硬编码 3010 → npm run setup 装出来的环境里前端每个 API 调用都 502,
    而症状离原因很远。改成 3010,并加一条 guard 钉住「example / config 默认 /
    vite 代理」三者一致。
    
    ## 验证
    
    857 全绿(85 文件),既有断言未改。3 处变异精确变红后逐字还原。
    其中 S3 那条变异(把兜底常量改成内联字面量)是必要的:guard 第一版只认内联
    字面量,而我重构时把兜底提成了命名常量 DEV_FALLBACK_SECRET,于是 guard 对它
    诞生的那个 case 恰好失明 —— 被它自己的自检当场抓到,遂加上同文件常量绑定解析。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »

  • - Agent as Micro-Container: each agent lives in data/agents/{id}/ with full encapsulation
    - Persona vs Capability split: agent.md (identity) + .claude/skills/main_skill/SKILL.md (workflow)
    - Conversations co-located: data/agents/{id}/conversations/ replaces global dir
    - Meta Skills renamed: meta_hunt, meta_digest, meta_creator, meta_evolving, meta_benchmark, meta_research
    - Dynamic Hot-Reload: AgentManager.getAgentConfig() falls back to filesystem scan
    - Parallel Sub-Agent orchestration via Promise.all with multi-terminal UI
    - Removed SQLite dependency, global skills/ directory, and legacy conversations/
    - Full architecture docs: 18-local-filesystem-first-agent-encapsulation.md
    yibo authored
     
    Browse File »